fix(frontend): resolve submit spinner hang and data loss issues
- Add try-catch-finally error handling to handleSubmit and deleteEntries
functions to ensure submitting state is always reset, even when API calls
fail or timeout. This fixes the infinite loading spinner bug.
- Preserve genres field after AniList updates, matching the existing tags
preservation pattern. Prevents genres array from being lost after form
submission, which was causing "{#each} only works with iterable values"
error when the page re-rendered.
- Add fallback (|| []) to genres each block to prevent rendering errors
when genres is undefined or null for entries without genre data.
These fixes ensure robust error handling and data consistency during anime
list updates across AniList, MAL, and Simkl services.
Fixes: submit button spinner never stopping after form submission
Fixes: "{#each} only works with iterable values" error on genres display
This commit is contained in:
@@ -23,10 +23,7 @@
|
|||||||
} from "../mal/types/MALTypes";
|
} from "../mal/types/MALTypes";
|
||||||
import type { SimklAnime } from "../simkl/types/simklTypes";
|
import type { SimklAnime } from "../simkl/types/simklTypes";
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
import type {
|
import type { StatusOption, StatusOptions } from "../helperTypes/StatusTypes";
|
||||||
StatusOption,
|
|
||||||
StatusOptions,
|
|
||||||
} from "../helperTypes/StatusTypes";
|
|
||||||
import type { AniListUpdateVariables } from "../anilist/types/AniListTypes";
|
import type { AniListUpdateVariables } from "../anilist/types/AniListTypes";
|
||||||
import { convertDateToAniList } from "../helperFunctions/convertDateToAniList";
|
import { convertDateToAniList } from "../helperFunctions/convertDateToAniList";
|
||||||
import {
|
import {
|
||||||
@@ -81,8 +78,7 @@
|
|||||||
{ id: 5, aniList: "REPEATING", mal: "rewatching", simkl: "watching" },
|
{ id: 5, aniList: "REPEATING", mal: "rewatching", simkl: "watching" },
|
||||||
];
|
];
|
||||||
let startingAnilistStatusOption: StatusOption = statusOptions.filter(
|
let startingAnilistStatusOption: StatusOption = statusOptions.filter(
|
||||||
(option) =>
|
(option) => currentAniListAnime.data.MediaList.status === option.aniList,
|
||||||
currentAniListAnime.data.MediaList.status === option.aniList,
|
|
||||||
)[0];
|
)[0];
|
||||||
let startedAtDate: Date | null = convertAniListDateToDate(
|
let startedAtDate: Date | null = convertAniListDateToDate(
|
||||||
currentAniListAnime.data.MediaList.startedAt,
|
currentAniListAnime.data.MediaList.startedAt,
|
||||||
@@ -113,15 +109,11 @@
|
|||||||
let startDate = "";
|
let startDate = "";
|
||||||
let finishDate = "";
|
let finishDate = "";
|
||||||
if (currentMalAnime.my_list_status.start_date !== "") {
|
if (currentMalAnime.my_list_status.start_date !== "") {
|
||||||
const startArray = re.exec(
|
const startArray = re.exec(currentMalAnime.my_list_status.start_date);
|
||||||
currentMalAnime.my_list_status.start_date,
|
|
||||||
);
|
|
||||||
startDate = `${startArray[2]}-${startArray[3]}-${startArray[1]}`;
|
startDate = `${startArray[2]}-${startArray[3]}-${startArray[1]}`;
|
||||||
}
|
}
|
||||||
if (currentMalAnime.my_list_status.finish_date !== "") {
|
if (currentMalAnime.my_list_status.finish_date !== "") {
|
||||||
const finishArray = re.exec(
|
const finishArray = re.exec(currentMalAnime.my_list_status.finish_date);
|
||||||
currentMalAnime.my_list_status.finish_date,
|
|
||||||
);
|
|
||||||
finishDate = `${finishArray[2]}-${finishArray[3]}-${finishArray[1]}`;
|
finishDate = `${finishArray[2]}-${finishArray[3]}-${finishArray[1]}`;
|
||||||
}
|
}
|
||||||
AddAnimeServiceToTable({
|
AddAnimeServiceToTable({
|
||||||
@@ -198,6 +190,7 @@
|
|||||||
submitData[key] = value;
|
submitData[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
if (
|
if (
|
||||||
isAniListLoggedIn &&
|
isAniListLoggedIn &&
|
||||||
currentAniListAnime.data.MediaList.mediaId !== 0
|
currentAniListAnime.data.MediaList.mediaId !== 0
|
||||||
@@ -212,10 +205,11 @@
|
|||||||
startedAt: convertDateToAniList(startedAtDate),
|
startedAt: convertDateToAniList(startedAtDate),
|
||||||
completedAt: convertDateToAniList(completedAtDate),
|
completedAt: convertDateToAniList(completedAtDate),
|
||||||
};
|
};
|
||||||
await AniListUpdateEntry(body).then(
|
await AniListUpdateEntry(body).then((value: AniListGetSingleAnime) => {
|
||||||
(value: AniListGetSingleAnime) => {
|
|
||||||
value.data.MediaList.media.tags =
|
value.data.MediaList.media.tags =
|
||||||
currentAniListAnime.data.MediaList.media.tags;
|
currentAniListAnime.data.MediaList.media.tags;
|
||||||
|
value.data.MediaList.media.genres =
|
||||||
|
currentAniListAnime.data.MediaList.media.genres;
|
||||||
aniListAnime.update((newValue) => {
|
aniListAnime.update((newValue) => {
|
||||||
newValue = value;
|
newValue = value;
|
||||||
return newValue;
|
return newValue;
|
||||||
@@ -236,8 +230,7 @@
|
|||||||
repeat: currentAniListAnime.data.MediaList.repeat,
|
repeat: currentAniListAnime.data.MediaList.repeat,
|
||||||
notes: currentAniListAnime.data.MediaList.notes,
|
notes: currentAniListAnime.data.MediaList.notes,
|
||||||
});
|
});
|
||||||
},
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (malLoggedIn && currentMalAnime.id !== 0) {
|
if (malLoggedIn && currentMalAnime.id !== 0) {
|
||||||
@@ -254,8 +247,7 @@
|
|||||||
(malAnimeReturn: MalListStatus) => {
|
(malAnimeReturn: MalListStatus) => {
|
||||||
malAnime.update((value) => {
|
malAnime.update((value) => {
|
||||||
value.my_list_status.status = malAnimeReturn.status;
|
value.my_list_status.status = malAnimeReturn.status;
|
||||||
value.my_list_status.is_rewatching =
|
value.my_list_status.is_rewatching = malAnimeReturn.is_rewatching;
|
||||||
malAnimeReturn.is_rewatching;
|
|
||||||
value.my_list_status.score = malAnimeReturn.score;
|
value.my_list_status.score = malAnimeReturn.score;
|
||||||
value.my_list_status.num_episodes_watched =
|
value.my_list_status.num_episodes_watched =
|
||||||
malAnimeReturn.num_episodes_watched;
|
malAnimeReturn.num_episodes_watched;
|
||||||
@@ -282,14 +274,12 @@
|
|||||||
id: `m-${currentMalAnime.id}`,
|
id: `m-${currentMalAnime.id}`,
|
||||||
title: currentMalAnime.title,
|
title: currentMalAnime.title,
|
||||||
service: "MyAnimeList",
|
service: "MyAnimeList",
|
||||||
progress:
|
progress: currentMalAnime.my_list_status.num_episodes_watched,
|
||||||
currentMalAnime.my_list_status.num_episodes_watched,
|
|
||||||
status: currentMalAnime.my_list_status.status,
|
status: currentMalAnime.my_list_status.status,
|
||||||
startedAt: startDate,
|
startedAt: startDate,
|
||||||
completedAt: finishDate,
|
completedAt: finishDate,
|
||||||
score: currentMalAnime.my_list_status.score,
|
score: currentMalAnime.my_list_status.score,
|
||||||
repeat: currentMalAnime.my_list_status
|
repeat: currentMalAnime.my_list_status.num_times_rewatched,
|
||||||
.num_times_rewatched,
|
|
||||||
notes: currentMalAnime.my_list_status.comments,
|
notes: currentMalAnime.my_list_status.comments,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -297,13 +287,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (simklLoggedIn && currentSimklAnime.show.ids.simkl !== 0) {
|
if (simklLoggedIn && currentSimklAnime.show.ids.simkl !== 0) {
|
||||||
if (
|
if (currentSimklAnime.watched_episodes_count !== submitData.episodes) {
|
||||||
currentSimklAnime.watched_episodes_count !== submitData.episodes
|
await SimklSyncEpisodes(currentSimklAnime, submitData.episodes).then(
|
||||||
) {
|
(value: SimklAnime) => {
|
||||||
await SimklSyncEpisodes(
|
|
||||||
currentSimklAnime,
|
|
||||||
submitData.episodes,
|
|
||||||
).then((value: SimklAnime) => {
|
|
||||||
AddAnimeServiceToTable({
|
AddAnimeServiceToTable({
|
||||||
id: `s-${value.show.ids.simkl}`,
|
id: `s-${value.show.ids.simkl}`,
|
||||||
title: value.show.title,
|
title: value.show.title,
|
||||||
@@ -320,14 +306,13 @@
|
|||||||
newValue = value;
|
newValue = value;
|
||||||
return newValue;
|
return newValue;
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentSimklAnime.user_rating !== submitData.rating) {
|
if (currentSimklAnime.user_rating !== submitData.rating) {
|
||||||
await SimklSyncRating(
|
await SimklSyncRating(currentSimklAnime, submitData.rating).then(
|
||||||
currentSimklAnime,
|
(value) => {
|
||||||
submitData.rating,
|
|
||||||
).then((value) => {
|
|
||||||
AddAnimeServiceToTable({
|
AddAnimeServiceToTable({
|
||||||
id: `s-${value.show.ids.simkl}`,
|
id: `s-${value.show.ids.simkl}`,
|
||||||
title: value.show.title,
|
title: value.show.title,
|
||||||
@@ -344,7 +329,8 @@
|
|||||||
newValue = value;
|
newValue = value;
|
||||||
return newValue;
|
return newValue;
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentSimklAnime.status !== submitData.status.simkl) {
|
if (currentSimklAnime.status !== submitData.status.simkl) {
|
||||||
@@ -371,14 +357,18 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error submitting changes:", error);
|
||||||
|
} finally {
|
||||||
submitting.set(false);
|
submitting.set(false);
|
||||||
submitSuccess.set(true);
|
submitSuccess.set(true);
|
||||||
setTimeout(() => submitSuccess.set(false), 2000);
|
setTimeout(() => submitSuccess.set(false), 2000);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteEntries = async () => {
|
const deleteEntries = async () => {
|
||||||
submitting.set(true);
|
submitting.set(true);
|
||||||
|
try {
|
||||||
if (
|
if (
|
||||||
isAniListLoggedIn &&
|
isAniListLoggedIn &&
|
||||||
currentAniListAnime.data.MediaList.mediaId !== 0
|
currentAniListAnime.data.MediaList.mediaId !== 0
|
||||||
@@ -427,9 +417,13 @@
|
|||||||
notes: "",
|
notes: "",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error deleting entries:", error);
|
||||||
|
} finally {
|
||||||
submitting.set(false);
|
submitting.set(false);
|
||||||
submitSuccess.set(true);
|
submitSuccess.set(true);
|
||||||
setTimeout(() => submitSuccess.set(false), 2000);
|
setTimeout(() => submitSuccess.set(false), 2000);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let max = 999;
|
let max = 999;
|
||||||
@@ -442,8 +436,7 @@
|
|||||||
currentAniListAnime.data.MediaList.media.nextAiringEpisode.episode !== 0
|
currentAniListAnime.data.MediaList.media.nextAiringEpisode.episode !== 0
|
||||||
) {
|
) {
|
||||||
max =
|
max =
|
||||||
currentAniListAnime.data.MediaList.media.nextAiringEpisode.episode -
|
currentAniListAnime.data.MediaList.media.nextAiringEpisode.episode - 1;
|
||||||
1;
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -479,17 +472,11 @@
|
|||||||
on:click={() => {
|
on:click={() => {
|
||||||
currentAniListAnime.data.MediaList.progress -= 1;
|
currentAniListAnime.data.MediaList.progress -= 1;
|
||||||
if (
|
if (
|
||||||
currentAniListAnime.data.MediaList
|
currentAniListAnime.data.MediaList.progress <
|
||||||
.progress <
|
currentAniListAnime.data.MediaList.media.episodes
|
||||||
currentAniListAnime.data.MediaList.media
|
|
||||||
.episodes
|
|
||||||
) {
|
) {
|
||||||
startingAnilistStatusOption =
|
startingAnilistStatusOption = statusOptions[0];
|
||||||
statusOptions[0];
|
if (currentAniListAnime.data.MediaList.repeat === 0)
|
||||||
if (
|
|
||||||
currentAniListAnime.data.MediaList
|
|
||||||
.repeat === 0
|
|
||||||
)
|
|
||||||
completedAtDate = null;
|
completedAtDate = null;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -519,22 +506,18 @@
|
|||||||
id="episodes"
|
id="episodes"
|
||||||
class="border border-x-0 p-2.5 h-11 text-center text-sm block w-full placeholder-gray-400 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none
|
class="border border-x-0 p-2.5 h-11 text-center text-sm block w-full placeholder-gray-400 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none
|
||||||
{currentAniListAnime.data.MediaList.progress < 0 ||
|
{currentAniListAnime.data.MediaList.progress < 0 ||
|
||||||
(currentAniListAnime.data.MediaList.media.episodes >
|
(currentAniListAnime.data.MediaList.media.episodes > 0 &&
|
||||||
0 &&
|
|
||||||
currentAniListAnime.data.MediaList.progress >
|
currentAniListAnime.data.MediaList.progress >
|
||||||
currentAniListAnime.data.MediaList.media
|
currentAniListAnime.data.MediaList.media.episodes) ||
|
||||||
.episodes) ||
|
(currentAniListAnime.data.MediaList.media.nextAiringEpisode
|
||||||
(currentAniListAnime.data.MediaList.media
|
.episode > 0 &&
|
||||||
.nextAiringEpisode.episode > 0 &&
|
|
||||||
currentAniListAnime.data.MediaList.progress >
|
currentAniListAnime.data.MediaList.progress >
|
||||||
currentAniListAnime.data.MediaList.media
|
currentAniListAnime.data.MediaList.media.nextAiringEpisode
|
||||||
.nextAiringEpisode.episode -
|
.episode -
|
||||||
1)
|
1)
|
||||||
? 'border-red-500 border-[2px] text-rose-300 focus:ring-red-500 focus:border-red-500'
|
? 'border-red-500 border-[2px] text-rose-300 focus:ring-red-500 focus:border-red-500'
|
||||||
: 'bg-gray-700 hover:bg-gray-600 border-gray-600 text-white focus:ring-blue-500 focus:border-blue-500'} w-24"
|
: 'bg-gray-700 hover:bg-gray-600 border-gray-600 text-white focus:ring-blue-500 focus:border-blue-500'} w-24"
|
||||||
bind:value={
|
bind:value={currentAniListAnime.data.MediaList.progress}
|
||||||
currentAniListAnime.data.MediaList.progress
|
|
||||||
}
|
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
@@ -544,24 +527,15 @@
|
|||||||
on:click={() => {
|
on:click={() => {
|
||||||
currentAniListAnime.data.MediaList.progress += 1;
|
currentAniListAnime.data.MediaList.progress += 1;
|
||||||
if (
|
if (
|
||||||
currentAniListAnime.data.MediaList.media
|
currentAniListAnime.data.MediaList.media.episodes ===
|
||||||
.episodes ===
|
|
||||||
currentAniListAnime.data.MediaList.progress
|
currentAniListAnime.data.MediaList.progress
|
||||||
) {
|
) {
|
||||||
startingAnilistStatusOption =
|
startingAnilistStatusOption = statusOptions[2];
|
||||||
statusOptions[2];
|
|
||||||
completedAtDate = new Date();
|
completedAtDate = new Date();
|
||||||
}
|
}
|
||||||
if (
|
if (currentAniListAnime.data.MediaList.progress - 1 === 0) {
|
||||||
currentAniListAnime.data.MediaList
|
startingAnilistStatusOption = statusOptions[0];
|
||||||
.progress -
|
if (startedAtDate === null) startedAtDate = new Date();
|
||||||
1 ===
|
|
||||||
0
|
|
||||||
) {
|
|
||||||
startingAnilistStatusOption =
|
|
||||||
statusOptions[0];
|
|
||||||
if (startedAtDate === null)
|
|
||||||
startedAtDate = new Date();
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
class="bg-gray-700 hover:bg-gray-600 border-gray-600 border rounded-e-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"
|
class="bg-gray-700 hover:bg-gray-600 border-gray-600 border rounded-e-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"
|
||||||
@@ -584,16 +558,15 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
/ {currentAniListAnime.data.MediaList.media
|
/ {currentAniListAnime.data.MediaList.media.nextAiringEpisode
|
||||||
.nextAiringEpisode.episode !== 0
|
.episode !== 0
|
||||||
? currentAniListAnime.data.MediaList.media
|
? currentAniListAnime.data.MediaList.media.nextAiringEpisode
|
||||||
.nextAiringEpisode.episode - 1
|
.episode - 1
|
||||||
: currentAniListAnime.data.MediaList.media.episodes}
|
: currentAniListAnime.data.MediaList.media.episodes}
|
||||||
</div>
|
</div>
|
||||||
{#if currentAniListAnime.data.MediaList.media.nextAiringEpisode.episode !== 0}
|
{#if currentAniListAnime.data.MediaList.media.nextAiringEpisode.episode !== 0}
|
||||||
<div>
|
<div>
|
||||||
of {currentAniListAnime.data.MediaList.media
|
of {currentAniListAnime.data.MediaList.media.episodes}
|
||||||
.episodes}
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@@ -665,8 +638,7 @@
|
|||||||
name="repeat"
|
name="repeat"
|
||||||
min="0"
|
min="0"
|
||||||
id="repeat"
|
id="repeat"
|
||||||
class="border {currentAniListAnime.data.MediaList
|
class="border {currentAniListAnime.data.MediaList.repeat < 0
|
||||||
.repeat < 0
|
|
||||||
? 'border-red-500 border-[2px] text-rose-300 focus:ring-red-500 focus:border-red-500'
|
? 'border-red-500 border-[2px] text-rose-300 focus:ring-red-500 focus:border-red-500'
|
||||||
: 'border-gray-500 text-white focus:ring-blue-500 focus:border-blue-500'} text-sm rounded-lg block w-24 p-2.5 bg-gray-600 placeholder-gray-400 text-white"
|
: 'border-gray-500 text-white focus:ring-blue-500 focus:border-blue-500'} text-sm rounded-lg block w-24 p-2.5 bg-gray-600 placeholder-gray-400 text-white"
|
||||||
bind:value={currentAniListAnime.data.MediaList.repeat}
|
bind:value={currentAniListAnime.data.MediaList.repeat}
|
||||||
@@ -829,7 +801,7 @@
|
|||||||
<div class="flex m-5">
|
<div class="flex m-5">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-2xl">Genres</h3>
|
<h3 class="text-2xl">Genres</h3>
|
||||||
{#each currentAniListAnime.data.MediaList.media.genres as genre}
|
{#each currentAniListAnime.data.MediaList.media.genres || [] as genre}
|
||||||
<div>
|
<div>
|
||||||
<Badge large border color="blue" class="m-1 w-52">
|
<Badge large border color="blue" class="m-1 w-52">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
Reference in New Issue
Block a user