Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Pagination with 'Load More' Button for Movie List #25

Merged
merged 2 commits into from
Sep 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions .firebase/hosting.ZGlzdA.cache
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
index.html,1725779587375,dc7935b63b15f8c37564e1857e678ef911d5704e023b98b8bb0588ecdda65881
assets/fa-v4compatibility-BX8XWJtE.woff2,1725779587375,8945f97a3a1bbd891397f2f7900844dbf1808e9e3924900a31d4acafb11a9db2
assets/fa-v4compatibility-B9MWI-E6.ttf,1725779587375,d5040b176e185deb1562d78b306cffe3336502f43ad77337eb9b8e464e1ab757
data/cartoons.json,1725779587066,0a612d3f9f67b6cc4880a3b4dda7463f88df97d9fed28738159fef6af3c97f96
assets/FavoriteComponent-5XtouwUL.js,1725779587375,1fc064a67a914175b593a9f90464cef27dba4282a07b8efeef8092f4d649c8c5
assets/fa-regular-400-DgEfZSYE.woff2,1725779587377,fb31b3f693b818441c452f39a682d8ad81967e3474b6a2266ddb885177cd98d1
assets/fa-regular-400-Bf3rG5Nx.ttf,1725779587375,61e9221981e1ad8a27ea29b9aed2d9d1615450c3f99b3a6175c2ef238fa67eb4
assets/fa-brands-400-O7nZalfM.woff2,1725779587378,5446f4c298ee6694ba92500f91741668e1ff9c8c08cb018a4f414fb663acae9e
assets/fa-solid-900-DOQJEhcS.woff2,1725779587374,7a9c4ef0dd299dfd976428e4cb9c86fc13ebd4eed7604f035476e69af3f56b9d
assets/fa-brands-400-Dur5g48u.ttf,1725779587375,8a036bca81921a2f3fc90c860fde21f0e8066cdf3507cf9999dd2a47cac380b1
assets/index-CpPOpnWm.css,1725779587375,7184ecfbfc3244c34b03b37fdd5394a015b997a9dc8d3050be17b245bb836fcc
assets/index-DAXziCis.js,1725779587375,c1d8da4668c02b8e86b30261a447e0eda8e0163a34a25f6c74db5c4f6626139b
assets/fa-solid-900-BV3CbEM2.ttf,1725779587375,af1fb4d30a49d562da382fdc49f386e2c8075bbbe26a5ecbbcdcf8994aaa423d
index.html,1725781490346,59577291b801e90646eef6fe3012872b1192012537baa7907654d55383187d19
assets/fa-v4compatibility-BX8XWJtE.woff2,1725781490346,8945f97a3a1bbd891397f2f7900844dbf1808e9e3924900a31d4acafb11a9db2
data/cartoons.json,1725781490034,0a612d3f9f67b6cc4880a3b4dda7463f88df97d9fed28738159fef6af3c97f96
assets/fa-v4compatibility-B9MWI-E6.ttf,1725781490346,d5040b176e185deb1562d78b306cffe3336502f43ad77337eb9b8e464e1ab757
assets/FavoriteComponent-CvQGpqgv.js,1725781490347,761549a095836de93560964a12856d6203d0cdf3bb078a5d8f9b5a82054c95fc
assets/fa-regular-400-DgEfZSYE.woff2,1725781490346,fb31b3f693b818441c452f39a682d8ad81967e3474b6a2266ddb885177cd98d1
assets/fa-regular-400-Bf3rG5Nx.ttf,1725781490346,61e9221981e1ad8a27ea29b9aed2d9d1615450c3f99b3a6175c2ef238fa67eb4
assets/fa-brands-400-O7nZalfM.woff2,1725781490344,5446f4c298ee6694ba92500f91741668e1ff9c8c08cb018a4f414fb663acae9e
assets/fa-solid-900-DOQJEhcS.woff2,1725781490346,7a9c4ef0dd299dfd976428e4cb9c86fc13ebd4eed7604f035476e69af3f56b9d
assets/fa-brands-400-Dur5g48u.ttf,1725781490346,8a036bca81921a2f3fc90c860fde21f0e8066cdf3507cf9999dd2a47cac380b1
assets/index-DTRffuzk.css,1725781490347,764766dafbe4568dc75f3e2def3adeff518cf7f79041485b6ba51743808b1995
assets/index-BLQvRMyn.js,1725781490347,285b70fcadad118d2aaaa52653a0d6035bcd6c3c7cdc4828a003c6112f25bdc8
assets/fa-solid-900-BV3CbEM2.ttf,1725781490346,af1fb4d30a49d562da382fdc49f386e2c8075bbbe26a5ecbbcdcf8994aaa423d
103 changes: 57 additions & 46 deletions src/components/home/HomeComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,24 @@
import { ref, onMounted, computed } from 'vue';
import { useRouter } from 'vue-router';
import { useFavoritesStore } from '@/stores/useFavoritesStore.js';
import IconClose from '@/components/icons/IconClose.vue';
import IconFavorite from '@/components/icons/IconFavorite.vue';
import IconFavoriteDone from '@/components/icons/IconFavoriteDone.vue';

const welcomeMessage = 'Ласкаво просимо до CartoonJoy';
const searchPlaceholder = 'Пошук мультфільмів...';
const newestOption = 'Найновіші';
const oldestOption = 'Найстаріші';
const featuredMoviesTitle = 'Найкращі мультфільми';
const noResultsMessage = 'По вашому запиту нічого не найшло.';
const loadMoreButtonText = 'Завантажити ще';

const featuredMovies = ref([]);
const searchQuery = ref('');
const sortOption = ref('newest');
const moviesPerPage = ref(12);
const currentStartIndex = ref(0);
const isLoading = ref(false);
const router = useRouter();
const favoritesStore = useFavoritesStore();

Expand Down Expand Up @@ -57,6 +64,19 @@ const sortedMovies = computed(() => {
}
});

const displayedMovies = computed(() => {
return sortedMovies.value.slice(
0,
currentStartIndex.value + moviesPerPage.value
);
});

const hasMoreMovies = computed(() => {
return (
currentStartIndex.value + moviesPerPage.value < sortedMovies.value.length
);
});

const toggleFavorite = (movie) => {
if (favoritesStore.isFavorite(movie.id)) {
favoritesStore.removeFavorite(movie.id);
Expand All @@ -72,6 +92,18 @@ const isFavorite = (movieId) => {
const navigateToMovie = (id) => {
router.push({ name: 'movie', params: { id } });
};

const loadMoreMovies = async () => {
if (hasMoreMovies.value && !isLoading.value) {
isLoading.value = true;
try {
await new Promise((resolve) => setTimeout(resolve, 1000));
currentStartIndex.value += moviesPerPage.value;
} finally {
isLoading.value = false;
}
}
};
</script>

<template>
Expand All @@ -92,20 +124,7 @@ const navigateToMovie = (id) => {
@click="searchQuery = ''"
class="absolute inset-y-0 right-0 px-4 py-2 text-sm bg-white text-black hover:text-white rounded-r-lg flex items-center justify-center hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-600 transition-all hover:transition-shadow"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
class="w-5 h-5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
<IconClose />
</button>
</div>
<select
Expand All @@ -120,11 +139,11 @@ const navigateToMovie = (id) => {
<section class="featured-section">
<h2 class="text-2xl font-semibold mb-4">{{ featuredMoviesTitle }}</h2>
<div
v-if="sortedMovies.length > 0"
v-if="displayedMovies.length > 0"
class="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4"
>
<div
v-for="movie in sortedMovies"
v-for="movie in displayedMovies"
:key="movie.id"
class="relative bg-white p-4 border rounded-lg shadow-md cursor-pointer hover:shadow-lg transition-shadow flex flex-col"
@click="navigateToMovie(movie.id)"
Expand All @@ -139,36 +158,8 @@ const navigateToMovie = (id) => {
@click.stop="toggleFavorite(movie)"
class="absolute top-2 right-2 bg-red-600 text-white p-2 rounded-full shadow-lg border border-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 flex items-center justify-center"
>
<svg
v-if="isFavorite(movie.id)"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
class="w-5 h-5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5.121 9.879a3 3 0 114.242-4.242L12 6.585l2.637-2.948a3 3 0 114.242 4.242L12 14.415l-6.879-6.536z"
/>
</svg>
<svg
v-else
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
class="w-5 h-5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C12.09 3.81 13.76 3 15.5 3 18.58 3 21 5.42 21 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"
/>
</svg>
<IconFavorite v-if="isFavorite(movie.id)" />
<IconFavoriteDone v-else />
</button>
</div>
<div class="flex-1 flex flex-col justify-between">
Expand All @@ -184,6 +175,26 @@ const navigateToMovie = (id) => {
<div v-else>
<p class="text-gray-500">{{ noResultsMessage }}</p>
</div>
<div class="flex justify-center mt-4">
<button
v-if="hasMoreMovies"
@click="loadMoreMovies"
:disabled="isLoading"
class="px-6 py-3 bg-red-600 text-white rounded-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 transition-all duration-300 flex items-center"
>
<span v-if="!isLoading">{{ loadMoreButtonText }}</span>
<span
v-else
class="loader w-5 h-5 border-4 border-t-4 border-white border-opacity-25 rounded-full border-t-red-500 animate-spin"
></span>
</button>
</div>
</section>
</div>
</template>

<style scoped>
.loader {
border-top-color: #fff;
}
</style>
16 changes: 16 additions & 0 deletions src/components/icons/IconClose.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
class="w-5 h-5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</template>
16 changes: 16 additions & 0 deletions src/components/icons/IconFavorite.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
class="w-5 h-5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5.121 9.879a3 3 0 114.242-4.242L12 6.585l2.637-2.948a3 3 0 114.242 4.242L12 14.415l-6.879-6.536z"
/>
</svg>
</template>
16 changes: 16 additions & 0 deletions src/components/icons/IconFavoriteDone.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
class="w-5 h-5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C12.09 3.81 13.76 3 15.5 3 18.58 3 21 5.42 21 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"
/>
</svg>
</template>
Loading