Skip to content

Commit 5ab6ebc

Browse files
A lot of improvements
1 parent 5000d7b commit 5ab6ebc

File tree

33 files changed

+1525
-380
lines changed

33 files changed

+1525
-380
lines changed

src/core/integrations/integrations/integrations-show/containers/property-select-values/containers/mirakl-property-select-values/MiraklPropertySelectValues.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const emit = defineEmits(['pull-data']);
1616
const { t } = useI18n();
1717
1818
const searchConfig = computed(() => miraklPropertySelectValuesSearchConfigConstructor(t));
19-
const listingConfig = computed(() => miraklPropertySelectValuesListingConfigConstructor(t, props.id));
19+
const listingConfig = computed(() => miraklPropertySelectValuesListingConfigConstructor(t, props.id, props.salesChannelId));
2020
const fixedFilterVariables = computed(() => ({
2121
isPropertyValue: true,
2222
}));

src/core/integrations/integrations/integrations-show/containers/property-select-values/containers/mirakl-property-select-values/MiraklSelectValueEditProperty.vue

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ const config: RemoteSelectValueEditPropertyConfig = {
5555
valueQuery: getMiraklPropertySelectValueQuery,
5656
valueDataKey: 'miraklPropertySelectValue',
5757
mapValueData: valueData => ({
58+
valueProxyId: valueData?.proxyId || null,
59+
salesChannelId: valueData?.salesChannel?.id || null,
5860
form: {
5961
remoteProperty: valueData?.remoteProperty?.name || '',
6062
boolValue: valueData?.boolValue ?? null,
@@ -182,8 +184,8 @@ const config: RemoteSelectValueEditPropertyConfig = {
182184
},
183185
},
184186
notMappedBanner: {
185-
titleKey: 'integrations.show.propertySelectValues.notMappedBanner.title',
186-
contentKey: 'integrations.show.propertySelectValues.notMappedBanner.content',
187+
titleKey: 'integrations.show.propertySelectValues.notMappedBannerMirakl.title',
188+
contentKey: 'integrations.show.propertySelectValues.notMappedBannerMirakl.content',
187189
linkPath: ctx => ({
188190
name: 'integrations.remoteProperties.edit',
189191
params: { type: ctx.type, id: ctx.propertyId! },
@@ -205,6 +207,15 @@ const config: RemoteSelectValueEditPropertyConfig = {
205207
},
206208
}
207209
: null,
210+
duplicateMapping: {
211+
titleKey: 'integrations.show.propertySelectValues.duplicate.mirakl.title',
212+
descriptionKey: 'integrations.show.propertySelectValues.duplicate.mirakl.description',
213+
promptKey: 'integrations.show.propertySelectValues.duplicate.mirakl.prompt',
214+
selectorHelpKey: 'integrations.show.propertySelectValues.duplicate.mirakl.help',
215+
submitLabelKey: 'integrations.show.propertySelectValues.duplicate.mirakl.submit',
216+
successMessageKey: 'integrations.show.propertySelectValues.duplicate.mirakl.success',
217+
isVisible: ctx => Boolean(ctx.salesChannelId && ctx.valueProxyId && ctx.localPropertyId),
218+
},
208219
listRoute: ctx => ({
209220
name: 'integrations.integrations.show',
210221
params: { type: ctx.type, id: ctx.integrationId },

src/core/integrations/integrations/integrations-show/containers/property-select-values/containers/mirakl-property-select-values/configs.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export const miraklPropertySelectValuesSearchConfigConstructor = (t: Function):
7777
export const miraklPropertySelectValuesListingConfigConstructor = (
7878
t: Function,
7979
specificIntegrationId: string,
80+
salesChannelId: string,
8081
): ListingConfig => ({
8182
headers: [
8283
t('integrations.show.propertySelectValues.labels.localizedValue'),
@@ -102,7 +103,7 @@ export const miraklPropertySelectValuesListingConfigConstructor = (
102103
addActions: true,
103104
addEdit: true,
104105
addShow: true,
105-
urlQueryParams: { integrationId: specificIntegrationId },
106+
urlQueryParams: { integrationId: specificIntegrationId, salesChannelId },
106107
editUrlName: 'integrations.remotePropertySelectValues.edit',
107108
showUrlName: 'integrations.remotePropertySelectValues.edit',
108109
addDelete: false,

src/core/integrations/integrations/integrations-show/containers/property-select-values/containers/remote-property-select-values/components/RemoteSelectValueEditProperty.vue

Lines changed: 173 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
} from '../sharedImports';
2222
import { Tabs } from '../../../../../../../../../shared/components/molecules/tabs';
2323
import { CollaborationTab } from '../../../../../../../../../shared/components/organisms/collaboration-tab';
24+
import { duplicateSalesChannelSelectValueMappingMutation } from '../../../../../../../../../shared/api/mutations/salesChannels.js';
2425
import type { FormConfig, QueryFormField, ChoiceFormField } from '../sharedImports';
2526
import type {
2627
MapPropertyDataResult,
@@ -39,9 +40,11 @@ const { t } = useI18n();
3940
const valueId = ref(String(route.params.id));
4041
const type = ref(String(route.params.type));
4142
const integrationId = route.query.integrationId ? route.query.integrationId.toString() : '';
42-
const salesChannelId = route.query.salesChannelId ? route.query.salesChannelId.toString() : '';
43+
const routeSalesChannelId = route.query.salesChannelId ? route.query.salesChannelId.toString() : '';
4344
const isWizard = route.query.wizard === '1';
4445
46+
const valueProxyId = ref<string | null>(null);
47+
const resolvedSalesChannelId = ref<string | null>(routeSalesChannelId || null);
4548
const propertyId = ref<string | null>(null);
4649
const propertyName = ref('');
4750
const propertyType = ref<string | null>(null);
@@ -56,8 +59,9 @@ const propertyMapped = ref(true);
5659
const placeholderContext: RemoteSelectValueEditPropertyContext = {
5760
type: type.value,
5861
valueId: valueId.value,
62+
valueProxyId: null,
5963
integrationId,
60-
salesChannelId,
64+
salesChannelId: routeSalesChannelId,
6165
isWizard,
6266
propertyId: null,
6367
propertyName: '',
@@ -84,8 +88,9 @@ placeholderContext.form = form;
8488
const createContext = (): RemoteSelectValueEditPropertyContext => ({
8589
type: type.value,
8690
valueId: valueId.value,
91+
valueProxyId: valueProxyId.value,
8792
integrationId,
88-
salesChannelId,
93+
salesChannelId: resolvedSalesChannelId.value || routeSalesChannelId,
8994
isWizard,
9095
propertyId: propertyId.value,
9196
propertyName: propertyName.value,
@@ -117,6 +122,10 @@ const localInstanceField = ref<QueryFormField | null>(null);
117122
const recommendations = ref<Recommendation[]>([]);
118123
const loadingRecommendations = ref(false);
119124
const formErrors = ref<Record<string, any>>({});
125+
const showDuplicateMapping = ref(false);
126+
const duplicateLocalInstanceId = ref<string | null>(null);
127+
const duplicateErrors = ref<Record<string, any>>({});
128+
const duplicatingMapping = ref(false);
120129
121130
const remoteFields = computed(() => props.config.remoteFields);
122131
const contextState = computed(() => createContext());
@@ -161,6 +170,31 @@ const generateValuePath = computed(() => {
161170
return props.config.generateValuePath ? props.config.generateValuePath(contextState.value) : null;
162171
});
163172
const notMappedBannerLink = computed(() => (props.config.notMappedBanner ? props.config.notMappedBanner.linkPath(contextState.value) : null));
173+
const duplicateSectionVisible = computed(() => {
174+
if (
175+
!props.config.duplicateMapping ||
176+
!localInstanceField.value ||
177+
!valueProxyId.value ||
178+
!resolvedSalesChannelId.value ||
179+
!form[props.config.localInstanceFieldKey]?.id
180+
) {
181+
return false;
182+
}
183+
if (props.config.duplicateMapping.isVisible) {
184+
return props.config.duplicateMapping.isVisible(contextState.value);
185+
}
186+
return !isBooleanMapping.value;
187+
});
188+
const duplicateLocalInstanceField = computed<QueryFormField | null>(() => {
189+
if (!localInstanceField.value) {
190+
return null;
191+
}
192+
193+
return {
194+
...localInstanceField.value,
195+
name: 'duplicateLocalInstance',
196+
} as QueryFormField;
197+
});
164198
165199
const breadcrumbsLinks = computed(() => [
166200
{ path: { name: 'integrations.integrations.list' }, name: t('integrations.title') },
@@ -195,6 +229,12 @@ const applyValueData = (result: MapValueDataResult | null | undefined) => {
195229
if (!result) {
196230
return;
197231
}
232+
if (result.valueProxyId !== undefined) {
233+
valueProxyId.value = result.valueProxyId;
234+
}
235+
if (result.salesChannelId !== undefined && result.salesChannelId) {
236+
resolvedSalesChannelId.value = result.salesChannelId;
237+
}
198238
if (result.form) {
199239
Object.entries(result.form).forEach(([key, value]) => {
200240
if (key === props.config.localInstanceFieldKey && typeof value === 'object' && value !== null) {
@@ -316,12 +356,21 @@ watch(localPropertyId, () => {
316356
317357
watch(
318358
() => form[props.config.localInstanceFieldKey]?.id,
319-
() => {
320-
const currentId = form[props.config.localInstanceFieldKey]?.id;
359+
(currentId) => {
321360
recommendations.value = recommendations.value.filter(recommendation => recommendation.id !== currentId);
361+
if (duplicateLocalInstanceId.value && duplicateLocalInstanceId.value === currentId) {
362+
duplicateLocalInstanceId.value = null;
363+
}
364+
delete duplicateErrors.value.localInstance;
322365
}
323366
);
324367
368+
watch(duplicateSectionVisible, (visible) => {
369+
if (!visible) {
370+
resetDuplicateSection();
371+
}
372+
});
373+
325374
const selectRecommendation = (id: string) => {
326375
if (!form[props.config.localInstanceFieldKey]) {
327376
form[props.config.localInstanceFieldKey] = { id };
@@ -331,6 +380,64 @@ const selectRecommendation = (id: string) => {
331380
recommendations.value = recommendations.value.filter(recommendation => recommendation.id !== id);
332381
};
333382
383+
const resetDuplicateSection = () => {
384+
showDuplicateMapping.value = false;
385+
duplicateLocalInstanceId.value = null;
386+
duplicateErrors.value = {};
387+
};
388+
389+
const submitDuplicateMapping = async () => {
390+
if (!props.config.duplicateMapping || !valueProxyId.value || !resolvedSalesChannelId.value) {
391+
return;
392+
}
393+
394+
duplicateErrors.value = {};
395+
396+
if (!duplicateLocalInstanceId.value) {
397+
duplicateErrors.value.localInstance = t('integrations.show.propertySelectValues.duplicate.validation.required');
398+
return;
399+
}
400+
401+
if (duplicateLocalInstanceId.value === form[props.config.localInstanceFieldKey]?.id) {
402+
duplicateErrors.value.localInstance = t('integrations.show.propertySelectValues.duplicate.validation.sameValue');
403+
return;
404+
}
405+
406+
try {
407+
duplicatingMapping.value = true;
408+
const { data } = await apolloClient.mutate({
409+
mutation: duplicateSalesChannelSelectValueMappingMutation,
410+
variables: {
411+
salesChannel: { id: resolvedSalesChannelId.value },
412+
remotePropertySelectValue: { id: valueProxyId.value },
413+
localInstance: { id: duplicateLocalInstanceId.value },
414+
},
415+
});
416+
417+
const newMappingId = data?.duplicateSalesChannelSelectValueMapping?.id;
418+
if (!newMappingId) {
419+
throw new Error(t('integrations.show.propertySelectValues.duplicate.error'));
420+
}
421+
422+
if (props.config.duplicateMapping.successMessageKey) {
423+
Toast.success(t(props.config.duplicateMapping.successMessageKey));
424+
}
425+
426+
await router.push({
427+
name: 'integrations.remotePropertySelectValues.edit',
428+
params: { type: type.value, id: newMappingId },
429+
query: { integrationId, salesChannelId: resolvedSalesChannelId.value },
430+
});
431+
} catch (error: any) {
432+
const graphQLError = error?.graphQLErrors?.[0]?.message;
433+
const message = graphQLError || error?.message || t('integrations.show.propertySelectValues.duplicate.error');
434+
duplicateErrors.value.__all__ = message;
435+
Toast.error(String(message));
436+
} finally {
437+
duplicatingMapping.value = false;
438+
}
439+
};
440+
334441
const handleSubmitErrors = (errors: Record<string, any>) => {
335442
formErrors.value = errors || {};
336443
const messages = Object.values(formErrors.value).filter(Boolean);
@@ -429,7 +536,7 @@ onMounted(async () => {
429536
enhancedConfig.value.submitUrl = {
430537
name: 'integrations.remotePropertySelectValues.edit',
431538
params: { type: type.value, id: nextId },
432-
query: { integrationId, salesChannelId, wizard: '1' },
539+
query: { integrationId, salesChannelId: resolvedSalesChannelId.value || routeSalesChannelId, wizard: '1' },
433540
};
434541
enhancedConfig.value.submitLabel = t(props.config.wizardSubmitLabelKey ?? 'integrations.show.mapping.saveAndMapNext');
435542
return;
@@ -572,6 +679,66 @@ onMounted(async () => {
572679
<p v-else class="text-sm text-gray-500">{{ t('integrations.show.propertySelectValues.recommendation.none') }}</p>
573680
</div>
574681
</div>
682+
<div
683+
v-if="duplicateSectionVisible && duplicateLocalInstanceField && props.config.duplicateMapping"
684+
class="mt-5 rounded border border-gray-200 bg-gray-50 p-5"
685+
>
686+
<div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
687+
<div class="space-y-2">
688+
<div class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-1 text-xs font-semibold uppercase tracking-[0.12em] text-gray-500">
689+
{{ t('integrations.show.propertySelectValues.duplicate.eyebrow') }}
690+
</div>
691+
<div>
692+
<h3 class="text-base font-semibold text-gray-900">{{ t(props.config.duplicateMapping.titleKey) }}</h3>
693+
<p class="mt-1 max-w-2xl text-sm leading-6 text-gray-500">{{ t(props.config.duplicateMapping.descriptionKey) }}</p>
694+
</div>
695+
</div>
696+
<div class="flex w-full flex-col gap-2 sm:w-auto sm:flex-row">
697+
<button
698+
type="button"
699+
class="inline-flex items-center justify-center rounded-xl border px-4 py-2 text-sm font-medium transition"
700+
:class="showDuplicateMapping ? 'border-gray-200 bg-white text-gray-600 hover:border-gray-300 hover:bg-gray-50' : 'border-emerald-200 bg-emerald-50 text-emerald-700 hover:border-emerald-300 hover:bg-emerald-100'"
701+
@click="showDuplicateMapping = true"
702+
>
703+
{{ t('shared.labels.yes') }}
704+
</button>
705+
<button
706+
type="button"
707+
class="inline-flex items-center justify-center rounded-xl border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-600 transition hover:border-gray-300 hover:bg-gray-50"
708+
@click="resetDuplicateSection"
709+
>
710+
{{ t('shared.labels.no') }}
711+
</button>
712+
</div>
713+
</div>
714+
715+
<div v-if="showDuplicateMapping" class="mt-4 space-y-3 border-t border-gray-200 pt-4">
716+
<div>
717+
<Label class="font-semibold block text-sm leading-6 text-gray-900">{{ t('integrations.show.propertySelectValues.duplicate.selectorLabel') }}</Label>
718+
<p class="mt-1 text-sm leading-6 text-gray-500">{{ t(props.config.duplicateMapping.promptKey) }}</p>
719+
</div>
720+
<FieldQuery
721+
:field="duplicateLocalInstanceField as QueryFormField"
722+
v-model="duplicateLocalInstanceId"
723+
@update:modelValue="duplicateLocalInstanceId = $event"
724+
/>
725+
<p v-if="props.config.duplicateMapping.selectorHelpKey" class="mt-2 text-sm leading-6 text-gray-400">
726+
{{ t(props.config.duplicateMapping.selectorHelpKey) }}
727+
</p>
728+
<p v-if="duplicateErrors.localInstance" class="mt-2 text-sm leading-6 text-red-600">{{ duplicateErrors.localInstance }}</p>
729+
<p v-if="duplicateErrors.__all__" class="mt-2 text-sm leading-6 text-red-600">{{ duplicateErrors.__all__ }}</p>
730+
<div class="flex justify-end">
731+
<button
732+
type="button"
733+
class="inline-flex items-center justify-center rounded-xl bg-gray-900 px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-gray-800 disabled:cursor-not-allowed disabled:bg-gray-300"
734+
:disabled="duplicatingMapping || !duplicateLocalInstanceId"
735+
@click="submitDuplicateMapping"
736+
>
737+
{{ duplicatingMapping ? t('integrations.show.propertySelectValues.duplicate.submitting') : t(props.config.duplicateMapping.submitLabelKey) }}
738+
</button>
739+
</div>
740+
</div>
741+
</div>
575742
</div>
576743
<div class="col-span-full">
577744
<slot name="additional-fields" :form="form" :context="contextState" />

src/core/integrations/integrations/integrations-show/containers/property-select-values/containers/remote-property-select-values/types.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export interface RemoteFieldConfig {
1616
export interface RemoteSelectValueEditPropertyContext {
1717
type: string;
1818
valueId: string;
19+
valueProxyId: string | null;
1920
integrationId: string;
2021
salesChannelId: string;
2122
isWizard: boolean;
@@ -34,6 +35,8 @@ export interface RemoteSelectValueEditPropertyContext {
3435

3536
export interface MapValueDataResult {
3637
form?: Record<string, any>;
38+
valueProxyId?: string | null;
39+
salesChannelId?: string | null;
3740
propertyId?: string | null;
3841
propertyName?: string;
3942
propertyType?: string | null;
@@ -99,6 +102,15 @@ export interface RemoteSelectValueEditPropertyConfig {
99102
mapResult: (data: any, currentId: string | null) => Recommendation[];
100103
};
101104
generateValuePath?: (ctx: RemoteSelectValueEditPropertyContext) => Url | null;
105+
duplicateMapping?: {
106+
titleKey: string;
107+
descriptionKey: string;
108+
promptKey: string;
109+
selectorHelpKey?: string;
110+
submitLabelKey: string;
111+
successMessageKey?: string;
112+
isVisible?: (ctx: RemoteSelectValueEditPropertyContext) => boolean;
113+
};
102114
listRoute: (ctx: RemoteSelectValueEditPropertyContext) => Url;
103115
wizard?: {
104116
query: DocumentNode;

src/core/products/products/product-show/containers/tabs/content/ProductContentPreview.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const props = defineProps<{
99
currentChannel: string;
1010
channels: any[];
1111
bulletPoints?: any[];
12+
sticky?: boolean;
1213
}>();
1314
1415
const cleanHostname = (hostname: string, type: string) => {
@@ -77,7 +78,10 @@ const previewUrl = computed(() => {
7778

7879

7980
<template>
80-
<div class="sticky w-full top-20 rounded shadow bg-white border p-0 max-h-[520px] overflow-y-auto custom-scrollbar">
81+
<div
82+
class="w-full rounded border bg-white p-0 shadow overflow-y-auto custom-scrollbar"
83+
:class="props.sticky !== false ? 'sticky top-20 max-h-[520px]' : 'max-h-[70vh]'"
84+
>
8185
<!-- Fake Browser Bar -->
8286
<div class="flex items-center bg-gray-100 border-b border-gray-200 px-5 py-2 rounded-t">
8387
<div class="w-3 h-3 rounded-full bg-red-400 mr-2"></div>
@@ -155,4 +159,4 @@ const previewUrl = computed(() => {
155159
margin-left: 1.5rem;
156160
}
157161
158-
</style>
162+
</style>

0 commit comments

Comments
 (0)