@@ -23,25 +23,36 @@ import ProductPreviewTrigger from "../shared/ProductPreviewTrigger.vue";
2323const {t} = useI18n ();
2424const props = defineProps <{ product: Product }>();
2525
26- const initialForm = ref ({
26+ type ProductContentFormState = {
27+ name: string ;
28+ subtitle: string ;
29+ shortDescription: string ;
30+ description: string ;
31+ urlKey: string ;
32+ };
33+
34+ const emptyHtml = ' <p><br></p>' ;
35+
36+ const createEmptyFormState = (): ProductContentFormState => ({
2737 name: ' ' ,
2838 subtitle: ' ' ,
29- shortDescription: ' ' ,
30- description: ' ' ,
39+ shortDescription: emptyHtml ,
40+ description: emptyHtml ,
3141 urlKey: ' '
3242});
3343
34- const form = reactive ({... initialForm .value });
44+ const initialForm = ref (createEmptyFormState ());
45+ const form = reactive (createEmptyFormState ());
3546const currentLanguage = ref (null );
3647const currentSalesChannel = ref <' default' | string >(' default' );
3748const mutation = ref (null );
3849const translationId = ref (null );
39- const oldLang = ref (currentLanguage .value );
40- const oldChannel = ref (currentSalesChannel .value );
4150const salesChannels = ref <any []>([]);
4251const fieldErrors = ref <Record <string , string >>({});
4352const bulletPointsRef = ref <any >(null );
4453const defaultLanguageCode = ref (' en' );
54+ const formRenderKey = ref (0 );
55+ let latestFormRequestId = 0 ;
4556
4657const currentChannelType = computed (() => {
4758 if (currentSalesChannel .value === ' default' ) return ' default' ;
@@ -51,6 +62,31 @@ const currentChannelType = computed(() => {
5162
5263const fieldRules = computed (() => getContentFieldRules (currentChannelType .value ));
5364
65+ const applyFormState = (nextState : ProductContentFormState ) => {
66+ form .name = nextState .name ;
67+ form .subtitle = nextState .subtitle ;
68+ form .shortDescription = nextState .shortDescription ;
69+ form .description = nextState .description ;
70+ form .urlKey = nextState .urlKey ;
71+ };
72+
73+ const resetFormState = () => {
74+ applyFormState (createEmptyFormState ());
75+ fieldErrors .value = {};
76+ translationId .value = null ;
77+ mutation .value = createProductTranslationMutation ;
78+ bulletPointsRef .value = null ;
79+ formRenderKey .value += 1 ;
80+ };
81+
82+ const mapTranslationToFormState = (translation : Partial <ProductContentFormState > | null | undefined ): ProductContentFormState => ({
83+ name: translation ?.name || ' ' ,
84+ subtitle: translation ?.subtitle || ' ' ,
85+ shortDescription: translation ?.shortDescription || emptyHtml ,
86+ description: translation ?.description || emptyHtml ,
87+ urlKey: translation ?.urlKey || ' ' ,
88+ });
89+
5490
5591const handleLanguageOptionsLoaded = (rawData ) => {
5692 if (! rawData ?.languages ?.length ) {
@@ -84,6 +120,13 @@ onMounted(async () => {
84120});
85121
86122const setFormAndMutation = async (language , channel ) => {
123+ if (! language ) {
124+ return ;
125+ }
126+
127+ const requestId = ++ latestFormRequestId ;
128+ resetFormState ();
129+
87130 try {
88131 if (channel === ' default' ) {
89132 // Query where salesChannel is null (Default)
@@ -93,25 +136,16 @@ const setFormAndMutation = async (language, channel) => {
93136 fetchPolicy: ' network-only'
94137 });
95138
139+ if (requestId !== latestFormRequestId ) {
140+ return ;
141+ }
142+
96143 if (data && data .productTranslations .edges .length === 1 ) {
97144 const translation = data .productTranslations .edges [0 ].node ;
98- form .name = translation .name ;
99- form .subtitle = translation .subtitle ;
100- form .shortDescription = translation .shortDescription ;
101- form .description = translation .description ;
102- form .urlKey = translation .urlKey ;
145+ applyFormState (mapTranslationToFormState (translation ));
103146 translationId .value = translation .id ;
104147 mutation .value = updateProductTranslationMutation ;
105- } else {
106- form .name = ' ' ;
107- form .subtitle = ' ' ;
108- form .shortDescription = ' <p><br></p>' ;
109- form .description = ' <p><br></p>' ;
110- form .urlKey = ' ' ;
111- translationId .value = null ;
112- mutation .value = createProductTranslationMutation ;
113148 }
114-
115149 } else {
116150 // Query with specific salesChannelId
117151 const { data } = await apolloClient .query ({
@@ -124,29 +158,29 @@ const setFormAndMutation = async (language, channel) => {
124158 fetchPolicy: ' network-only'
125159 });
126160
161+ if (requestId !== latestFormRequestId ) {
162+ return ;
163+ }
164+
127165 if (data && data .productTranslations .edges .length === 1 ) {
128166 const translation = data .productTranslations .edges [0 ].node ;
129- form .name = translation .name ;
130- form .subtitle = translation .subtitle ;
131- form .shortDescription = translation .shortDescription ;
132- form .description = translation .description ;
133- form .urlKey = translation .urlKey ;
167+ applyFormState (mapTranslationToFormState (translation ));
134168 translationId .value = translation .id ;
135169 mutation .value = updateProductTranslationMutation ;
136- } else {
137- form .name = ' ' ;
138- form .subtitle = ' ' ;
139- form .shortDescription = ' <p><br></p>' ;
140- form .description = ' <p><br></p>' ;
141- form .urlKey = ' ' ;
142- translationId .value = null ;
143- mutation .value = createProductTranslationMutation ;
144170 }
145171 }
146172 } catch (error ) {
173+ if (requestId !== latestFormRequestId ) {
174+ return ;
175+ }
176+
147177 console .error (" Error fetching translation:" , error );
148178 }
149179
180+ if (requestId !== latestFormRequestId ) {
181+ return ;
182+ }
183+
150184 await nextTick ();
151185 initialForm .value = { ... form };
152186};
@@ -155,48 +189,38 @@ const setFormAndMutation = async (language, channel) => {
155189watch (currentLanguage , async (newLanguage , oldLanguage ) => {
156190 if (oldLanguage === null ) {
157191 await setFormAndMutation (newLanguage , currentSalesChannel .value );
158- oldLang .value = newLanguage ;
159192 }
160193});
161194
162- watch (currentSalesChannel , async (newChannel , oldChannelVal ) => {
163- if (oldChannelVal === null ) {
164- await setFormAndMutation (currentLanguage .value , newChannel );
165- oldChannel .value = newChannel ;
166- }
167- });
168-
169- watch (currentChannelType , (newType ) => {
170- if (! getContentFieldRules (newType ).bulletPoints ) {
195+ const handleLanguageSelection = async (newLanguage ) => {
196+ if (newLanguage === currentLanguage .value ) {
171197 return ;
172198 }
173- });
174-
175- const handleLanguageSelection = async (newLanguage ) => {
176199
177200 if (JSON .stringify (form ) !== JSON .stringify (initialForm .value )) {
178201 const confirmChange = confirm (t (' products.translation.confirmLanguageChange' ));
179202 if (! confirmChange ) {
180- currentLanguage .value = oldLang .value ;
181203 return ;
182204 }
183205 }
184206
185- oldLang .value = newLanguage ;
207+ currentLanguage .value = newLanguage ;
186208 await setFormAndMutation (newLanguage , currentSalesChannel .value );
187209};
188210
189211const handleSalesChannelSelection = async (newChannel ) => {
212+ if (newChannel === currentSalesChannel .value ) {
213+ return ;
214+ }
190215
191216 if (JSON .stringify (form ) !== JSON .stringify (initialForm .value )) {
192217 const confirmChange = confirm (t (' products.products.messages.unsavedChanges' ));
193218 if (! confirmChange ) {
194- currentSalesChannel .value = oldChannel .value ;
195219 return ;
196220 }
197221 }
198222
199- oldChannel .value = newChannel ;
223+ currentSalesChannel .value = newChannel ;
200224 await setFormAndMutation (currentLanguage .value , newChannel );
201225};
202226
@@ -278,49 +302,42 @@ const shortDescriptionToolbarOptions = [
278302
279303<template >
280304
281- <Flex end class =" gap-2 flex-wrap" >
282- <FlexCell >
283- <ProductPreviewTrigger
284- :product =" product"
285- :sales-channel-id =" currentSalesChannel"
286- :sales-channels =" salesChannels"
287- :language-code =" currentLanguage"
288- />
289- </FlexCell >
290- <FlexCell >
291- <AdvancedContentGenerator
292- :product-ids =" [product.id]"
293- :initial-sales-channel-ids =" currentSalesChannel !== 'default' ? [currentSalesChannel] : []"
294- :small =" false"
295- />
296- </FlexCell >
297- <FlexCell >
298- <ProductContentImportModal
299- :product-ids =" [product.id]"
300- :current-language =" currentLanguage"
301- :current-sales-channel =" currentSalesChannel"
302- :sales-channels =" salesChannels"
303- @imported =" handleImportCompleted"
304- />
305- </FlexCell >
306- <FlexCell >
307- <ApolloMutation v-if =" mutation" :mutation =" mutation" :variables =" getVariables()" >
308- <template v-slot =" { mutate , loading } " >
309- <Button :customClass =" 'btn btn-primary'" :disabled =" loading" @click =" handleSave(mutate)" >
310- {{ t('shared.button.save') }}
311- </Button >
312- </template >
313- </ApolloMutation >
314- </FlexCell >
315- <FlexCell >
305+ <div class =" flex flex-wrap items-center justify-end gap-2" >
306+ <ProductPreviewTrigger
307+ :product =" product"
308+ :sales-channel-id =" currentSalesChannel"
309+ :sales-channels =" salesChannels"
310+ :language-code =" currentLanguage"
311+ />
312+ <AdvancedContentGenerator
313+ :product-ids =" [product.id]"
314+ :initial-sales-channel-ids =" currentSalesChannel !== 'default' ? [currentSalesChannel] : []"
315+ :small =" false"
316+ />
317+ <ProductContentImportModal
318+ :product-ids =" [product.id]"
319+ :current-language =" currentLanguage"
320+ :current-sales-channel =" currentSalesChannel"
321+ :sales-channels =" salesChannels"
322+ btn-class =" btn-outline-primary"
323+ @imported =" handleImportCompleted"
324+ />
325+ <ApolloMutation v-if =" mutation" :mutation =" mutation" :variables =" getVariables()" >
326+ <template v-slot =" { mutate , loading } " >
327+ <Button :customClass =" 'btn btn-primary'" :disabled =" loading" @click =" handleSave(mutate)" >
328+ {{ t('shared.button.save') }}
329+ </Button >
330+ </template >
331+ </ApolloMutation >
332+ <div class =" shrink-0" >
316333 <LanguageSelector
317- v- model =" currentLanguage"
334+ : model-value =" currentLanguage"
318335 selector-class =" w-full min-w-[12rem] sm:min-w-[14rem]"
319336 @loaded =" handleLanguageOptionsLoaded"
320337 @update:modelValue =" handleLanguageSelection"
321338 />
322- </FlexCell >
323- </Flex >
339+ </div >
340+ </div >
324341
325342 <Flex v-if =" currentSalesChannel !== 'default'" class =" mt-2 block lg:hidden" >
326343 <FlexCell >
@@ -338,13 +355,17 @@ const shortDescriptionToolbarOptions = [
338355 mt-4
339356 grid gap-4
340357 grid-cols-1
341- lg:grid-cols-[220px_minmax (0,1fr)]
342- xl:grid-cols-[360px_minmax (0,1fr)]
343- 2xl:grid-cols-[420px_minmax (0,1fr)]
358+ lg:grid-cols-[200px_minmax (0,1fr)]
359+ xl:grid-cols-[280px_minmax (0,1fr)]
360+ 2xl:grid-cols-[340px_minmax (0,1fr)]
344361 "
345362 >
346363 <div >
347- <SalesChannelTabs v-model =" currentSalesChannel" :channels =" salesChannels" @update:modelValue =" handleSalesChannelSelection" />
364+ <SalesChannelTabs
365+ :model-value =" currentSalesChannel"
366+ :channels =" salesChannels"
367+ @update:modelValue =" handleSalesChannelSelection"
368+ />
348369 </div >
349370
350371 <!-- Product Content Form -->
@@ -355,6 +376,7 @@ const shortDescriptionToolbarOptions = [
355376 >
356377 <div class =" p-2 bg-white" >
357378 <ProductContentForm
379+ :key =" formRenderKey"
358380 :form =" form"
359381 :field-errors =" fieldErrors"
360382 :product-id =" product.id"
0 commit comments