mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-30 17:53:31 -04:00 
			
		
		
		
	fix: Better UX and Error Handling For Adding Timeline Events (#5798)
This commit is contained in:
		| @@ -3,6 +3,7 @@ | |||||||
|     <div> |     <div> | ||||||
|       <BaseDialog |       <BaseDialog | ||||||
|         v-model="madeThisDialog" |         v-model="madeThisDialog" | ||||||
|  |         :loading="madeThisFormLoading" | ||||||
|         :icon="$globals.icons.chefHat" |         :icon="$globals.icons.chefHat" | ||||||
|         :title="$t('recipe.made-this')" |         :title="$t('recipe.made-this')" | ||||||
|         :submit-text="$t('recipe.add-to-timeline')" |         :submit-text="$t('recipe.add-to-timeline')" | ||||||
| @@ -119,8 +120,9 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { whenever } from "@vueuse/core"; | import { whenever } from "@vueuse/core"; | ||||||
| import { useUserApi } from "~/composables/api"; | import { useUserApi } from "~/composables/api"; | ||||||
|  | import { alert } from "~/composables/use-toast"; | ||||||
| import { useHouseholdSelf } from "~/composables/use-households"; | import { useHouseholdSelf } from "~/composables/use-households"; | ||||||
| import type { Recipe, RecipeTimelineEventIn } from "~/lib/api/types/recipe"; | import type { Recipe, RecipeTimelineEventIn, RecipeTimelineEventOut } from "~/lib/api/types/recipe"; | ||||||
| import type { VForm } from "~/types/auto-forms"; | import type { VForm } from "~/types/auto-forms"; | ||||||
|  |  | ||||||
| export default defineNuxtComponent({ | export default defineNuxtComponent({ | ||||||
| @@ -196,12 +198,25 @@ export default defineNuxtComponent({ | |||||||
|       newTimelineEventImagePreviewUrl.value = URL.createObjectURL(fileObject); |       newTimelineEventImagePreviewUrl.value = URL.createObjectURL(fileObject); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const state = reactive({ datePickerMenu: false }); |     const state = reactive({ datePickerMenu: false, madeThisFormLoading: false }); | ||||||
|  |  | ||||||
|  |     function resetMadeThisForm() { | ||||||
|  |       state.madeThisFormLoading = false; | ||||||
|  |  | ||||||
|  |       newTimelineEvent.value.eventMessage = ""; | ||||||
|  |       newTimelineEvent.value.timestamp = undefined; | ||||||
|  |       clearImage(); | ||||||
|  |       madeThisDialog.value = false; | ||||||
|  |       domMadeThisForm.value?.reset(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     async function createTimelineEvent() { |     async function createTimelineEvent() { | ||||||
|       if (!(newTimelineEventTimestampString.value && props.recipe?.id && props.recipe?.slug)) { |       if (!(newTimelineEventTimestampString.value && props.recipe?.id && props.recipe?.slug)) { | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  |       state.madeThisFormLoading = true; | ||||||
|  |  | ||||||
|       newTimelineEvent.value.recipeId = props.recipe.id; |       newTimelineEvent.value.recipeId = props.recipe.id; | ||||||
|       // Note: $auth.user is now a ref |       // Note: $auth.user is now a ref | ||||||
|       newTimelineEvent.value.subject = i18n.t("recipe.user-made-this", { user: $auth.user.value?.fullName }); |       newTimelineEvent.value.subject = i18n.t("recipe.user-made-this", { user: $auth.user.value?.fullName }); | ||||||
| @@ -210,34 +225,60 @@ export default defineNuxtComponent({ | |||||||
|       // we choose the end of day so it always comes after "new recipe" events |       // we choose the end of day so it always comes after "new recipe" events | ||||||
|       newTimelineEvent.value.timestamp = new Date(newTimelineEventTimestampString.value + "T23:59:59").toISOString(); |       newTimelineEvent.value.timestamp = new Date(newTimelineEventTimestampString.value + "T23:59:59").toISOString(); | ||||||
|  |  | ||||||
|       const eventResponse = await userApi.recipes.createTimelineEvent(newTimelineEvent.value); |       let newEvent: RecipeTimelineEventOut | null = null; | ||||||
|       const newEvent = eventResponse.data; |       try { | ||||||
|  |         const eventResponse = await userApi.recipes.createTimelineEvent(newTimelineEvent.value); | ||||||
|  |         newEvent = eventResponse.data; | ||||||
|  |         if (!newEvent) { | ||||||
|  |           throw new Error("No event created"); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       catch (error) { | ||||||
|  |         console.error("Failed to create timeline event:", error); | ||||||
|  |         alert.error(i18n.t("recipe.failed-to-add-to-timeline")); | ||||||
|  |         resetMadeThisForm(); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |  | ||||||
|       // we also update the recipe's last made value |       // we also update the recipe's last made value | ||||||
|       if (!lastMade.value || newTimelineEvent.value.timestamp > lastMade.value) { |       if (!lastMade.value || newTimelineEvent.value.timestamp > lastMade.value) { | ||||||
|         lastMade.value = newTimelineEvent.value.timestamp; |         try { | ||||||
|         await userApi.recipes.updateLastMade(props.recipe.slug, newTimelineEvent.value.timestamp); |           lastMade.value = newTimelineEvent.value.timestamp; | ||||||
|       } |           await userApi.recipes.updateLastMade(props.recipe.slug, newTimelineEvent.value.timestamp); | ||||||
|  |         } | ||||||
|       // update the image, if provided |         catch (error) { | ||||||
|       if (newTimelineEventImage.value && newEvent) { |           console.error("Failed to update last made date:", error); | ||||||
|         const imageResponse = await userApi.recipes.updateTimelineEventImage( |           alert.error(i18n.t("recipe.failed-to-update-recipe")); | ||||||
|           newEvent.id, |  | ||||||
|           newTimelineEventImage.value, |  | ||||||
|           newTimelineEventImageName.value, |  | ||||||
|         ); |  | ||||||
|         if (imageResponse.data) { |  | ||||||
|           newEvent.image = imageResponse.data.image; |  | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       // reset form |       // update the image, if provided | ||||||
|       newTimelineEvent.value.eventMessage = ""; |       let imageError = false; | ||||||
|       newTimelineEvent.value.timestamp = undefined; |       if (newTimelineEventImage.value) { | ||||||
|       clearImage(); |         try { | ||||||
|       madeThisDialog.value = false; |           const imageResponse = await userApi.recipes.updateTimelineEventImage( | ||||||
|       domMadeThisForm.value?.reset(); |             newEvent.id, | ||||||
|  |             newTimelineEventImage.value, | ||||||
|  |             newTimelineEventImageName.value, | ||||||
|  |           ); | ||||||
|  |           if (imageResponse.data) { | ||||||
|  |             newEvent.image = imageResponse.data.image; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         catch (error) { | ||||||
|  |           imageError = true; | ||||||
|  |           console.error("Failed to upload image for timeline event:", error); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (imageError) { | ||||||
|  |         alert.error(i18n.t("recipe.added-to-timeline-but-failed-to-add-image")); | ||||||
|  |       } | ||||||
|  |       else { | ||||||
|  |         alert.success(i18n.t("recipe.added-to-timeline")); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       resetMadeThisForm(); | ||||||
|       context.emit("eventCreated", newEvent); |       context.emit("eventCreated", newEvent); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -82,7 +82,7 @@ | |||||||
|             <BaseButton |             <BaseButton | ||||||
|               v-if="canSubmit" |               v-if="canSubmit" | ||||||
|               type="submit" |               type="submit" | ||||||
|               :disabled="submitDisabled" |               :disabled="submitDisabled || loading" | ||||||
|               @click="submitEvent" |               @click="submitEvent" | ||||||
|             > |             > | ||||||
|               {{ submitText }} |               {{ submitText }} | ||||||
|   | |||||||
| @@ -579,6 +579,10 @@ | |||||||
|     "made-this": "I Made This", |     "made-this": "I Made This", | ||||||
|     "how-did-it-turn-out": "How did it turn out?", |     "how-did-it-turn-out": "How did it turn out?", | ||||||
|     "user-made-this": "{user} made this", |     "user-made-this": "{user} made this", | ||||||
|  |     "added-to-timeline": "Added to timeline", | ||||||
|  |     "failed-to-add-to-timeline": "Failed to add to timeline", | ||||||
|  |     "failed-to-update-recipe": "Failed to update recipe", | ||||||
|  |     "added-to-timeline-but-failed-to-add-image": "Added to timeline, but failed to add image", | ||||||
|     "api-extras-description": "Recipes extras are a key feature of the Mealie API. They allow you to create custom JSON key/value pairs within a recipe, to reference from 3rd party applications. You can use these keys to provide information, for example to trigger automations or custom messages to relay to your desired device.", |     "api-extras-description": "Recipes extras are a key feature of the Mealie API. They allow you to create custom JSON key/value pairs within a recipe, to reference from 3rd party applications. You can use these keys to provide information, for example to trigger automations or custom messages to relay to your desired device.", | ||||||
|     "message-key": "Message Key", |     "message-key": "Message Key", | ||||||
|     "parse": "Parse", |     "parse": "Parse", | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user