Showing
14 changed files
with
544 additions
and
30 deletions
| 1 | +0.2.0: | ||
| 2 | +- Mise en place du CHANGELOG_RELEASE. | ||
| 3 | +- Ajout page index. | ||
| 4 | +- Modification app.vue afin d'initialiser l'app avec vuetify et NuxtPage pour démarrer sur la page index. | ||
| 5 | +- Création du composant MoviesList. | ||
| 6 | +- Création du composant SearchBar. | ||
| 7 | +- Création du composant SkeletonMoviesLoader. | ||
| 8 | +- Installation et paramétrage de pinia-orm. | ||
| 9 | +- Ajout du model Movie. | ||
| 10 | +- Création de la page movies/[id] vierge pour le détail d'un film. | ||
| 11 | + | ||
| 1 | 0.1.2: | 12 | 0.1.2: |
| 2 | - Ajout fichier .prettierignore. | 13 | - Ajout fichier .prettierignore. |
| 3 | - Installation module vuetify + modif script lint dans package.json. | 14 | - Installation module vuetify + modif script lint dans package.json. |
CHANGELOG_RELEASE.md
0 → 100644
| 1 | +------ Dispo à la prochaine release ------------ |
| 1 | +<script lang="ts" setup></script> | ||
| 2 | + | ||
| 1 | <template> | 3 | <template> |
| 2 | - <div> | 4 | + <v-locale-provider> |
| 3 | - <NuxtRouteAnnouncer /> | 5 | + <v-app> |
| 4 | - <NuxtWelcome /> | 6 | + <v-main class="min-h-screen bg-gray-900 text-white"> |
| 5 | - </div> | 7 | + <NuxtPage /> |
| 8 | + </v-main> | ||
| 9 | + </v-app> | ||
| 10 | + </v-locale-provider> | ||
| 6 | </template> | 11 | </template> |
| 12 | + | ||
| 13 | +<style scoped></style> |
components/MoviesList.vue
0 → 100644
| 1 | +<script lang="ts" setup> | ||
| 2 | +//#region --import--. | ||
| 3 | +import SearchBar from "~/components/SearchBar.vue"; | ||
| 4 | +import { onBeforeUnmount, ref } from "vue"; | ||
| 5 | +import { useTMDB } from "~/composables/tMDB"; | ||
| 6 | +import { Movie } from "~/models/movie"; | ||
| 7 | +import { FilmIcon, SearchXIcon } from "lucide-vue-next"; | ||
| 8 | +import type { MovieInterface } from "~/interfaces/movie"; | ||
| 9 | +import { useDateFormat } from "@vueuse/core"; | ||
| 10 | +//#endregion | ||
| 11 | + | ||
| 12 | +//#region --Declaration--. | ||
| 13 | +const { fetchPopularMovies, searchMovies } = useTMDB(); | ||
| 14 | +//#endregion | ||
| 15 | + | ||
| 16 | +//#region --Data/refs--. | ||
| 17 | +const isInitialLoading = ref(true); | ||
| 18 | +const isLoadingMore = ref(false); | ||
| 19 | +const currentPage = ref(1); | ||
| 20 | +const totalPages = ref(0); | ||
| 21 | +const searchQuery = ref(""); | ||
| 22 | +/** Elément observé pour le défilement infini. */ | ||
| 23 | +const loadMoreTrigger = ref<HTMLElement | null>(null); | ||
| 24 | +/** Instance de IntersectionObserver */ | ||
| 25 | +const observer = ref<IntersectionObserver | null>(null); | ||
| 26 | +//#endregion | ||
| 27 | + | ||
| 28 | +//#region --Computed--. | ||
| 29 | +const movies = computed(() => { | ||
| 30 | + return useRepo(Movie).query().orderBy("popularity", "desc").get() as unknown as MovieInterface[]; | ||
| 31 | +}); | ||
| 32 | +//#endregion | ||
| 33 | + | ||
| 34 | +//#region --Function--. | ||
| 35 | +/** | ||
| 36 | + * Fetch popular movies | ||
| 37 | + * @param page | ||
| 38 | + */ | ||
| 39 | +const fetchMovies = async (page: number) => { | ||
| 40 | + try { | ||
| 41 | + isLoadingMore.value = true; | ||
| 42 | + const data = await fetchPopularMovies(page); | ||
| 43 | + // Save in Movie model. | ||
| 44 | + if (isInitialLoading.value) { | ||
| 45 | + // First fetch, erase old data before save. | ||
| 46 | + useRepo(Movie).fresh(data.results); | ||
| 47 | + } else { | ||
| 48 | + // Add to store collection. | ||
| 49 | + useRepo(Movie).save(data.results); | ||
| 50 | + } | ||
| 51 | + totalPages.value = data.total_pages; | ||
| 52 | + currentPage.value = page; | ||
| 53 | + } catch (error) { | ||
| 54 | + console.error("Error fetching popular movies:", error); | ||
| 55 | + } finally { | ||
| 56 | + isInitialLoading.value = false; | ||
| 57 | + isLoadingMore.value = false; | ||
| 58 | + } | ||
| 59 | +}; | ||
| 60 | + | ||
| 61 | +/** | ||
| 62 | + * Search movies | ||
| 63 | + * @param query | ||
| 64 | + * @param page | ||
| 65 | + */ | ||
| 66 | +const search = async (query: string, page: number) => { | ||
| 67 | + // If empty search, fetch popular movies. | ||
| 68 | + if (!query.trim()) { | ||
| 69 | + await fetchMovies(1); | ||
| 70 | + return; | ||
| 71 | + } | ||
| 72 | + try { | ||
| 73 | + isLoadingMore.value = true; | ||
| 74 | + if (page === 1) { | ||
| 75 | + isInitialLoading.value = true; | ||
| 76 | + } | ||
| 77 | + const data = await searchMovies(query, page); | ||
| 78 | + // Save in Movie model. | ||
| 79 | + if (isInitialLoading.value) { | ||
| 80 | + // First fetch, erase old data before save. | ||
| 81 | + useRepo(Movie).fresh(data.results); | ||
| 82 | + } else { | ||
| 83 | + // Add to store collection. | ||
| 84 | + useRepo(Movie).save(data.results); | ||
| 85 | + } | ||
| 86 | + totalPages.value = data.total_pages; | ||
| 87 | + currentPage.value = page; | ||
| 88 | + } catch (error) { | ||
| 89 | + console.error("Error searching movies:", error); | ||
| 90 | + } finally { | ||
| 91 | + isInitialLoading.value = false; | ||
| 92 | + isLoadingMore.value = false; | ||
| 93 | + } | ||
| 94 | +}; | ||
| 95 | + | ||
| 96 | +function createIntersectionObserver() { | ||
| 97 | + return new IntersectionObserver( | ||
| 98 | + (entries) => { | ||
| 99 | + const [entry] = entries; | ||
| 100 | + if (entry.isIntersecting && !isLoadingMore.value && currentPage.value < totalPages.value) { | ||
| 101 | + if (searchQuery.value) { | ||
| 102 | + // Continue searching query if already active. | ||
| 103 | + search(searchQuery.value, currentPage.value + 1) | ||
| 104 | + } else { | ||
| 105 | + // Continue fetching popular movies. | ||
| 106 | + fetchMovies(currentPage.value + 1); | ||
| 107 | + } | ||
| 108 | + } | ||
| 109 | + }, | ||
| 110 | + { threshold: 1.0 }, | ||
| 111 | + ); | ||
| 112 | +} | ||
| 113 | + | ||
| 114 | +function handleSearchEvent(event: string) { | ||
| 115 | + currentPage.value = 1; | ||
| 116 | + searchQuery.value = event; | ||
| 117 | + search(event, 1); | ||
| 118 | +} | ||
| 119 | + | ||
| 120 | +function handleClearSearchEvent() { | ||
| 121 | + searchQuery.value = ''; | ||
| 122 | + currentPage.value = 1; | ||
| 123 | + // Fetch popular movies after clear. | ||
| 124 | + fetchMovies(1); | ||
| 125 | +} | ||
| 126 | + | ||
| 127 | +//#endregion | ||
| 128 | + | ||
| 129 | +//#region --Global event--. | ||
| 130 | +onMounted(() => { | ||
| 131 | + // First loading. | ||
| 132 | + fetchMovies(1); | ||
| 133 | + // Création et stockage dans la ref de l'instance IntersectionObserver. | ||
| 134 | + observer.value = createIntersectionObserver(); | ||
| 135 | + if (loadMoreTrigger.value) { | ||
| 136 | + // Début d'observation de la div pour le défilement infini. | ||
| 137 | + observer.value.observe(loadMoreTrigger.value); | ||
| 138 | + } | ||
| 139 | + | ||
| 140 | + if (loadMoreTrigger.value) { | ||
| 141 | + observer.value.observe(loadMoreTrigger.value); | ||
| 142 | + } | ||
| 143 | +}); | ||
| 144 | + | ||
| 145 | +onBeforeUnmount(() => { | ||
| 146 | + // Disconnect the observer when the component is unmounted. | ||
| 147 | + if (observer.value) { | ||
| 148 | + observer.value.disconnect(); | ||
| 149 | + } | ||
| 150 | +}); | ||
| 151 | +//#endregion | ||
| 152 | +</script> | ||
| 153 | + | ||
| 154 | +<template> | ||
| 155 | + <section> | ||
| 156 | + <h1 class="text-4xl font-bold mb-8 text-center">Découvrez les films populaires</h1> | ||
| 157 | + <!-- Barre de recherche --> | ||
| 158 | + <search-bar | ||
| 159 | + placeholder="Rechercher un film..." | ||
| 160 | + @event:search="handleSearchEvent" | ||
| 161 | + @event:clear_search="handleClearSearchEvent" | ||
| 162 | + /> | ||
| 163 | + <!-- Loading Skeleton --> | ||
| 164 | + <skeleton-movies-loader v-if="isInitialLoading" :is-initial-loading="isInitialLoading" :skeleton-number="20" /> | ||
| 165 | + <!-- Liste des films --> | ||
| 166 | + <div v-else-if="movies.length > 0" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6"> | ||
| 167 | + <div | ||
| 168 | + v-for="movie in movies" | ||
| 169 | + :key="movie.id" | ||
| 170 | + class="bg-gray-800 rounded-lg overflow-hidden shadow-lg transition-transform duration-300 hover:scale-105 cursor-pointer" | ||
| 171 | + @click="navigateTo(`/movies/${movie.id}`)" | ||
| 172 | + > | ||
| 173 | + <div class="relative pb-[150%]"> | ||
| 174 | + <img | ||
| 175 | + v-if="movie.poster_path" | ||
| 176 | + :alt="movie.title" | ||
| 177 | + :src="`https://image.tmdb.org/t/p/w500${movie.poster_path}`" | ||
| 178 | + class="absolute inset-0 w-full h-full object-cover" | ||
| 179 | + /> | ||
| 180 | + <div v-else class="absolute inset-0 w-full h-full bg-gray-700 flex items-center justify-center"> | ||
| 181 | + <FilmIcon :size="48" class="text-gray-500" /> | ||
| 182 | + </div> | ||
| 183 | + <div | ||
| 184 | + class="absolute top-2 right-2 bg-primary text-white rounded-full w-10 h-10 flex items-center justify-center font-bold" | ||
| 185 | + > | ||
| 186 | + {{ movie.vote_average.toFixed(1) }} | ||
| 187 | + </div> | ||
| 188 | + </div> | ||
| 189 | + <div class="p-4"> | ||
| 190 | + <h2 class="text-lg font-bold mb-1 line-clamp-1">{{ movie.title }}</h2> | ||
| 191 | + <p class="text-sm text-gray-400">{{ useDateFormat(movie.release_date, "DD-MM-YYYY") }}</p> | ||
| 192 | + </div> | ||
| 193 | + </div> | ||
| 194 | + </div> | ||
| 195 | + | ||
| 196 | + <!-- Message si aucun film trouvé --> | ||
| 197 | + <section v-else-if="searchQuery && !movies.length" class="text-center py-12"> | ||
| 198 | + <SearchXIcon :size="64" class="mx-auto mb-4 text-gray-600" /> | ||
| 199 | + <h3 class="text-xl font-bold mb-2">Aucun film trouvé</h3> | ||
| 200 | + <p class="text-gray-400">Essayez avec un autre terme de recherche</p> | ||
| 201 | + </section> | ||
| 202 | + | ||
| 203 | + <!-- Loader pour le chargement de plus de films --> | ||
| 204 | + <section v-if="isLoadingMore && !isInitialLoading" class="flex justify-center mt-8"> | ||
| 205 | + <div class="w-10 h-10 border-4 border-primary border-t-transparent rounded-full animate-spin" /> | ||
| 206 | + </section> | ||
| 207 | + | ||
| 208 | + <!-- Élément observé pour le défilement infini --> | ||
| 209 | + <div ref="loadMoreTrigger" class="h-10 mt-4" /> | ||
| 210 | + </section> | ||
| 211 | +</template> | ||
| 212 | + | ||
| 213 | +<style scoped></style> |
components/SearchBar.vue
0 → 100644
| 1 | +<script lang="ts" setup> | ||
| 2 | +//#region --import--. | ||
| 3 | +import { SearchIcon, XIcon } from "lucide-vue-next"; | ||
| 4 | +import { ref } from "vue"; | ||
| 5 | +import { useDebounceFn } from "@vueuse/core"; | ||
| 6 | +//#endregion | ||
| 7 | + | ||
| 8 | +//#region --Emits--. | ||
| 9 | +const emit = defineEmits(['event:search', 'event:clear_search']); | ||
| 10 | +//#endregion | ||
| 11 | + | ||
| 12 | +//#region --Props--. | ||
| 13 | +defineProps({ | ||
| 14 | + placeholder: { | ||
| 15 | + type: String, | ||
| 16 | + required: false, | ||
| 17 | + nullable: false, | ||
| 18 | + default: "", | ||
| 19 | + }, | ||
| 20 | +}); | ||
| 21 | +//#endregion | ||
| 22 | + | ||
| 23 | +//#region --Data/refs--. | ||
| 24 | +const searchQuery = ref(""); | ||
| 25 | +//#endregion | ||
| 26 | + | ||
| 27 | +//#region --Function--. | ||
| 28 | +/** | ||
| 29 | + * Debounced function | ||
| 30 | + */ | ||
| 31 | +const handleSearchEvent = useDebounceFn(() => { | ||
| 32 | + emit('event:search', searchQuery.value); | ||
| 33 | +}, 500); | ||
| 34 | + | ||
| 35 | +function handleClearSearchEvent() { | ||
| 36 | + searchQuery.value = ''; | ||
| 37 | + emit('event:clear_search') | ||
| 38 | +} | ||
| 39 | +//#endregion | ||
| 40 | +</script> | ||
| 41 | + | ||
| 42 | +<template> | ||
| 43 | + <!-- Barre de recherche --> | ||
| 44 | + <section class="mb-8"> | ||
| 45 | + <div class="relative max-w-xl mx-auto"> | ||
| 46 | + <input | ||
| 47 | + v-model="searchQuery" | ||
| 48 | + :placeholder="placeholder" | ||
| 49 | + class="w-full px-4 py-3 bg-gray-800 rounded-full text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary" | ||
| 50 | + type="text" | ||
| 51 | + @input="handleSearchEvent" | ||
| 52 | + /> | ||
| 53 | + <button | ||
| 54 | + v-if="searchQuery" | ||
| 55 | + class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-white" | ||
| 56 | + @click="handleClearSearchEvent" | ||
| 57 | + > | ||
| 58 | + <XIcon :size="20" /> | ||
| 59 | + </button> | ||
| 60 | + <button v-else class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400"> | ||
| 61 | + <SearchIcon :size="20" /> | ||
| 62 | + </button> | ||
| 63 | + </div> | ||
| 64 | + </section> | ||
| 65 | +</template> | ||
| 66 | + | ||
| 67 | +<style scoped></style> |
components/SkeletonMoviesLoader.vue
0 → 100644
| 1 | +<script lang="ts" setup> | ||
| 2 | +//#region --Props--. | ||
| 3 | +defineProps({ | ||
| 4 | + isInitialLoading: { | ||
| 5 | + type: Boolean, | ||
| 6 | + required: true, | ||
| 7 | + nullable: false, | ||
| 8 | + }, | ||
| 9 | + skeletonNumber: { | ||
| 10 | + type: Number, | ||
| 11 | + required: false, | ||
| 12 | + nullable: false, | ||
| 13 | + default: 12, | ||
| 14 | + }, | ||
| 15 | +}); | ||
| 16 | +//#endregion | ||
| 17 | +</script> | ||
| 18 | + | ||
| 19 | +<template> | ||
| 20 | + <!-- Skeleton loader pendant le chargement initial --> | ||
| 21 | + <section v-if="isInitialLoading" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6"> | ||
| 22 | + <div v-for="i in skeletonNumber" :key="i" class="bg-gray-800 rounded-lg overflow-hidden shadow-lg animate-pulse"> | ||
| 23 | + <div class="h-80 bg-gray-700" /> | ||
| 24 | + <div class="p-4"> | ||
| 25 | + <div class="h-6 bg-gray-700 rounded mb-3" /> | ||
| 26 | + <div class="h-4 bg-gray-700 rounded w-2/3" /> | ||
| 27 | + </div> | ||
| 28 | + </div> | ||
| 29 | + </section> | ||
| 30 | +</template> | ||
| 31 | + | ||
| 32 | +<style scoped></style> |
| @@ -2,9 +2,47 @@ import type { RuntimeConfig } from "nuxt/schema"; | @@ -2,9 +2,47 @@ import type { RuntimeConfig } from "nuxt/schema"; | ||
| 2 | 2 | ||
| 3 | export const useTMDB = function() { | 3 | export const useTMDB = function() { |
| 4 | const runtimeconfig: RuntimeConfig = useRuntimeConfig(); | 4 | const runtimeconfig: RuntimeConfig = useRuntimeConfig(); |
| 5 | - | ||
| 6 | const apiUrl = runtimeconfig.public.apiTMDBUrl; | 5 | const apiUrl = runtimeconfig.public.apiTMDBUrl; |
| 7 | const apiKey = runtimeconfig.public.apiTMDBSecret; | 6 | const apiKey = runtimeconfig.public.apiTMDBSecret; |
| 8 | 7 | ||
| 9 | - return {apiUrl, apiKey} | 8 | + /** |
| 9 | + * Fetch popular movies. | ||
| 10 | + * @param page | ||
| 11 | + */ | ||
| 12 | + const fetchPopularMovies = async (page: number) => { | ||
| 13 | + try { | ||
| 14 | + const response = await fetch( | ||
| 15 | + `${apiUrl}/movie/popular?api_key=${apiKey}&language=fr-FR&page=${page}`, | ||
| 16 | + ); | ||
| 17 | + if (!response.ok) { | ||
| 18 | + console.error("An error occurred when fetching popular movies:"); | ||
| 19 | + } else { | ||
| 20 | + return await response.json(); | ||
| 21 | + } | ||
| 22 | + } catch (error) { | ||
| 23 | + console.error("Error fetching popular movies:", error); | ||
| 24 | + } | ||
| 25 | + }; | ||
| 26 | + | ||
| 27 | + /** | ||
| 28 | + * Search movies | ||
| 29 | + * @param query | ||
| 30 | + * @param page | ||
| 31 | + */ | ||
| 32 | + const searchMovies = async (query: string, page: number) => { | ||
| 33 | + try { | ||
| 34 | + const response = await fetch( | ||
| 35 | + `${apiUrl}/search/movie?api_key=${apiKey}&language=fr-FR&query=${encodeURIComponent(query)}&page=${page}`, | ||
| 36 | + ); | ||
| 37 | + if (!response.ok) { | ||
| 38 | + console.error("An error occurred when searching movies:"); | ||
| 39 | + } else { | ||
| 40 | + return await response.json(); | ||
| 41 | + } | ||
| 42 | + } catch (error) { | ||
| 43 | + console.error("Error searching movies:", error); | ||
| 44 | + } | ||
| 45 | + }; | ||
| 46 | + | ||
| 47 | + return { fetchPopularMovies, searchMovies } | ||
| 10 | } | 48 | } |
interfaces/movie.ts
0 → 100644
models/movie.ts
0 → 100644
| 1 | +import { Model } from "pinia-orm"; | ||
| 2 | + | ||
| 3 | +export class Movie extends Model { | ||
| 4 | + /** | ||
| 5 | + * | ||
| 6 | + * @return {string} | ||
| 7 | + */ | ||
| 8 | + static get entity() { | ||
| 9 | + return "Movie"; | ||
| 10 | + } | ||
| 11 | + | ||
| 12 | + /** | ||
| 13 | + * | ||
| 14 | + * @return {string} | ||
| 15 | + */ | ||
| 16 | + static get primaryKey() { | ||
| 17 | + return "id"; | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + static fields() { | ||
| 21 | + return { | ||
| 22 | + // Attributs. | ||
| 23 | + id: this.number(null), | ||
| 24 | + adult: this.boolean(false), | ||
| 25 | + backdrop_pat: this.string(null), | ||
| 26 | + genre_ids: this.attr([]), | ||
| 27 | + original_language: this.string(null), | ||
| 28 | + original_title: this.string(null), | ||
| 29 | + overview: this.string(null), | ||
| 30 | + popularity: this.number(null), | ||
| 31 | + poster_path: this.string(null), | ||
| 32 | + release_date: this.string(null), | ||
| 33 | + title: this.string(null), | ||
| 34 | + video: this.boolean(false), | ||
| 35 | + vote_average: this.number(null), | ||
| 36 | + vote_count: this.number(null), | ||
| 37 | + // Relations. | ||
| 38 | + }; | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + static piniaOptions = { | ||
| 42 | + persist: true, | ||
| 43 | + }; | ||
| 44 | + | ||
| 45 | +} |
| @@ -35,6 +35,16 @@ export default defineNuxtConfig({ | @@ -35,6 +35,16 @@ export default defineNuxtConfig({ | ||
| 35 | ], | 35 | ], |
| 36 | }, | 36 | }, |
| 37 | ], | 37 | ], |
| 38 | + [ | ||
| 39 | + "@pinia-orm/nuxt", | ||
| 40 | + { | ||
| 41 | + autoImports: [ | ||
| 42 | + // automatically imports `useRepo`. | ||
| 43 | + "useRepo", // import { useRepo } from 'pinia-orm'. | ||
| 44 | + ["useRepo", "usePinaRepo"], // import { useRepo as usePinaRepo } from 'pinia-orm'. | ||
| 45 | + ], | ||
| 46 | + }, | ||
| 47 | + ], | ||
| 38 | "pinia-plugin-persistedstate/nuxt", | 48 | "pinia-plugin-persistedstate/nuxt", |
| 39 | "@nuxt/scripts", | 49 | "@nuxt/scripts", |
| 40 | "@nuxt/test-utils", | 50 | "@nuxt/test-utils", |
| 1 | { | 1 | { |
| 2 | "name": "nuxt-app", | 2 | "name": "nuxt-app", |
| 3 | - "version": "0.1.0", | 3 | + "version": "0.2.0", |
| 4 | "lockfileVersion": 3, | 4 | "lockfileVersion": 3, |
| 5 | "requires": true, | 5 | "requires": true, |
| 6 | "packages": { | 6 | "packages": { |
| 7 | "": { | 7 | "": { |
| 8 | "name": "nuxt-app", | 8 | "name": "nuxt-app", |
| 9 | - "version": "0.1.0", | 9 | + "version": "0.2.0", |
| 10 | "hasInstallScript": true, | 10 | "hasInstallScript": true, |
| 11 | "dependencies": { | 11 | "dependencies": { |
| 12 | "@nuxt/eslint": "^1.3.0", | 12 | "@nuxt/eslint": "^1.3.0", |
| @@ -15,14 +15,15 @@ | @@ -15,14 +15,15 @@ | ||
| 15 | "@nuxt/scripts": "^0.11.6", | 15 | "@nuxt/scripts": "^0.11.6", |
| 16 | "@nuxt/test-utils": "^3.17.2", | 16 | "@nuxt/test-utils": "^3.17.2", |
| 17 | "@nuxt/ui": "^2.22.0", | 17 | "@nuxt/ui": "^2.22.0", |
| 18 | - "@pinia/nuxt": "^0.11.0", | 18 | + "@pinia-orm/nuxt": "^1.10.2", |
| 19 | + "@pinia/nuxt": "^0.9.0", | ||
| 19 | "@unhead/vue": "^2.0.8", | 20 | "@unhead/vue": "^2.0.8", |
| 20 | "@vueuse/core": "^13.1.0", | 21 | "@vueuse/core": "^13.1.0", |
| 21 | "@vueuse/nuxt": "^13.1.0", | 22 | "@vueuse/nuxt": "^13.1.0", |
| 22 | "eslint": "^9.25.1", | 23 | "eslint": "^9.25.1", |
| 23 | "lucide-vue-next": "^0.503.0", | 24 | "lucide-vue-next": "^0.503.0", |
| 24 | "nuxt": "^3.16.2", | 25 | "nuxt": "^3.16.2", |
| 25 | - "pinia": "^3.0.2", | 26 | + "pinia": "^2.3.1", |
| 26 | "pinia-plugin-persistedstate": "^4.2.0", | 27 | "pinia-plugin-persistedstate": "^4.2.0", |
| 27 | "vue": "^3.5.13", | 28 | "vue": "^3.5.13", |
| 28 | "vue-router": "^4.5.0", | 29 | "vue-router": "^4.5.0", |
| @@ -3820,10 +3821,35 @@ | @@ -3820,10 +3821,35 @@ | ||
| 3820 | "url": "https://opencollective.com/parcel" | 3821 | "url": "https://opencollective.com/parcel" |
| 3821 | } | 3822 | } |
| 3822 | }, | 3823 | }, |
| 3824 | + "node_modules/@pinia-orm/normalizr": { | ||
| 3825 | + "version": "1.10.2", | ||
| 3826 | + "resolved": "https://registry.npmjs.org/@pinia-orm/normalizr/-/normalizr-1.10.2.tgz", | ||
| 3827 | + "integrity": "sha512-lgcCb7ST/leYXJwUT/y7RvTn+5U6OOmvSUuNGs/Mpqrx99IG3R9DSWA3w7n/wl7yDt5+35J0ERK3bebQW1STsQ==", | ||
| 3828 | + "license": "MIT", | ||
| 3829 | + "funding": { | ||
| 3830 | + "url": "https://github.com/sponsors/codedredd" | ||
| 3831 | + } | ||
| 3832 | + }, | ||
| 3833 | + "node_modules/@pinia-orm/nuxt": { | ||
| 3834 | + "version": "1.10.2", | ||
| 3835 | + "resolved": "https://registry.npmjs.org/@pinia-orm/nuxt/-/nuxt-1.10.2.tgz", | ||
| 3836 | + "integrity": "sha512-I/dNHuFR2V8K9X6oi5P+EKCbiSRSenbMfMXySmxQOqaMU81f2XVaPVTaq9dgomo9i5mE7x/QqRK49ne8av51ag==", | ||
| 3837 | + "license": "MIT", | ||
| 3838 | + "dependencies": { | ||
| 3839 | + "@nuxt/kit": "^3.12.3", | ||
| 3840 | + "pinia-orm": "1.10.2" | ||
| 3841 | + }, | ||
| 3842 | + "funding": { | ||
| 3843 | + "url": "https://github.com/sponsors/codedredd" | ||
| 3844 | + }, | ||
| 3845 | + "peerDependencies": { | ||
| 3846 | + "@pinia/nuxt": "<=0.9.0" | ||
| 3847 | + } | ||
| 3848 | + }, | ||
| 3823 | "node_modules/@pinia/nuxt": { | 3849 | "node_modules/@pinia/nuxt": { |
| 3824 | - "version": "0.11.0", | 3850 | + "version": "0.9.0", |
| 3825 | - "resolved": "https://registry.npmjs.org/@pinia/nuxt/-/nuxt-0.11.0.tgz", | 3851 | + "resolved": "https://registry.npmjs.org/@pinia/nuxt/-/nuxt-0.9.0.tgz", |
| 3826 | - "integrity": "sha512-QGFlUAkeVAhPCTXacrtNP4ti24sGEleVzmxcTALY9IkS6U5OUox7vmNL1pkqBeW39oSNq/UC5m40ofDEPHB1fg==", | 3852 | + "integrity": "sha512-2yeRo7LeyCF68AbNeL3xu2h6uw0617RkcsYxmA8DJM0R0PMdz5wQHnc44KeENQxR/Mrq8T910XVT6buosqsjBQ==", |
| 3827 | "license": "MIT", | 3853 | "license": "MIT", |
| 3828 | "dependencies": { | 3854 | "dependencies": { |
| 3829 | "@nuxt/kit": "^3.9.0" | 3855 | "@nuxt/kit": "^3.9.0" |
| @@ -3832,7 +3858,7 @@ | @@ -3832,7 +3858,7 @@ | ||
| 3832 | "url": "https://github.com/sponsors/posva" | 3858 | "url": "https://github.com/sponsors/posva" |
| 3833 | }, | 3859 | }, |
| 3834 | "peerDependencies": { | 3860 | "peerDependencies": { |
| 3835 | - "pinia": "^3.0.2" | 3861 | + "pinia": "^2.3.0" |
| 3836 | } | 3862 | } |
| 3837 | }, | 3863 | }, |
| 3838 | "node_modules/@pkgjs/parseargs": { | 3864 | "node_modules/@pkgjs/parseargs": { |
| @@ -13014,12 +13040,13 @@ | @@ -13014,12 +13040,13 @@ | ||
| 13014 | } | 13040 | } |
| 13015 | }, | 13041 | }, |
| 13016 | "node_modules/pinia": { | 13042 | "node_modules/pinia": { |
| 13017 | - "version": "3.0.2", | 13043 | + "version": "2.3.1", |
| 13018 | - "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.2.tgz", | 13044 | + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz", |
| 13019 | - "integrity": "sha512-sH2JK3wNY809JOeiiURUR0wehJ9/gd9qFN2Y828jCbxEzKEmEt0pzCXwqiSTfuRsK9vQsOflSdnbdBOGrhtn+g==", | 13045 | + "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==", |
| 13020 | "license": "MIT", | 13046 | "license": "MIT", |
| 13021 | "dependencies": { | 13047 | "dependencies": { |
| 13022 | - "@vue/devtools-api": "^7.7.2" | 13048 | + "@vue/devtools-api": "^6.6.3", |
| 13049 | + "vue-demi": "^0.14.10" | ||
| 13023 | }, | 13050 | }, |
| 13024 | "funding": { | 13051 | "funding": { |
| 13025 | "url": "https://github.com/sponsors/posva" | 13052 | "url": "https://github.com/sponsors/posva" |
| @@ -13034,6 +13061,22 @@ | @@ -13034,6 +13061,22 @@ | ||
| 13034 | } | 13061 | } |
| 13035 | } | 13062 | } |
| 13036 | }, | 13063 | }, |
| 13064 | + "node_modules/pinia-orm": { | ||
| 13065 | + "version": "1.10.2", | ||
| 13066 | + "resolved": "https://registry.npmjs.org/pinia-orm/-/pinia-orm-1.10.2.tgz", | ||
| 13067 | + "integrity": "sha512-Q8QwFFdAmhc347QY6ndXtLZX4kE+46dUQbyy0ha6URmdIaz1jf8FbZEJ8BhHMLGPx+PeO/QJraxvvETx62lMQA==", | ||
| 13068 | + "license": "MIT", | ||
| 13069 | + "dependencies": { | ||
| 13070 | + "@pinia-orm/normalizr": "1.10.2", | ||
| 13071 | + "vue-demi": "^0.14.10" | ||
| 13072 | + }, | ||
| 13073 | + "funding": { | ||
| 13074 | + "url": "https://github.com/sponsors/codedredd" | ||
| 13075 | + }, | ||
| 13076 | + "peerDependencies": { | ||
| 13077 | + "pinia": "^2.1.7" | ||
| 13078 | + } | ||
| 13079 | + }, | ||
| 13037 | "node_modules/pinia-plugin-persistedstate": { | 13080 | "node_modules/pinia-plugin-persistedstate": { |
| 13038 | "version": "4.2.0", | 13081 | "version": "4.2.0", |
| 13039 | "resolved": "https://registry.npmjs.org/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-4.2.0.tgz", | 13082 | "resolved": "https://registry.npmjs.org/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-4.2.0.tgz", |
| @@ -13058,15 +13101,6 @@ | @@ -13058,15 +13101,6 @@ | ||
| 13058 | } | 13101 | } |
| 13059 | } | 13102 | } |
| 13060 | }, | 13103 | }, |
| 13061 | - "node_modules/pinia/node_modules/@vue/devtools-api": { | ||
| 13062 | - "version": "7.7.5", | ||
| 13063 | - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.5.tgz", | ||
| 13064 | - "integrity": "sha512-HYV3tJGARROq5nlVMJh5KKHk7GU8Au3IrrmNNqr978m0edxgpHgYPDoNUGrvEgIbObz09SQezFR3A1EVmB5WZg==", | ||
| 13065 | - "license": "MIT", | ||
| 13066 | - "dependencies": { | ||
| 13067 | - "@vue/devtools-kit": "^7.7.5" | ||
| 13068 | - } | ||
| 13069 | - }, | ||
| 13070 | "node_modules/pirates": { | 13104 | "node_modules/pirates": { |
| 13071 | "version": "4.0.7", | 13105 | "version": "4.0.7", |
| 13072 | "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", | 13106 | "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", |
| @@ -17155,6 +17189,32 @@ | @@ -17155,6 +17189,32 @@ | ||
| 17155 | "ufo": "^1.5.4" | 17189 | "ufo": "^1.5.4" |
| 17156 | } | 17190 | } |
| 17157 | }, | 17191 | }, |
| 17192 | + "node_modules/vue-demi": { | ||
| 17193 | + "version": "0.14.10", | ||
| 17194 | + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", | ||
| 17195 | + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", | ||
| 17196 | + "hasInstallScript": true, | ||
| 17197 | + "license": "MIT", | ||
| 17198 | + "bin": { | ||
| 17199 | + "vue-demi-fix": "bin/vue-demi-fix.js", | ||
| 17200 | + "vue-demi-switch": "bin/vue-demi-switch.js" | ||
| 17201 | + }, | ||
| 17202 | + "engines": { | ||
| 17203 | + "node": ">=12" | ||
| 17204 | + }, | ||
| 17205 | + "funding": { | ||
| 17206 | + "url": "https://github.com/sponsors/antfu" | ||
| 17207 | + }, | ||
| 17208 | + "peerDependencies": { | ||
| 17209 | + "@vue/composition-api": "^1.0.0-rc.1", | ||
| 17210 | + "vue": "^3.0.0-0 || ^2.6.0" | ||
| 17211 | + }, | ||
| 17212 | + "peerDependenciesMeta": { | ||
| 17213 | + "@vue/composition-api": { | ||
| 17214 | + "optional": true | ||
| 17215 | + } | ||
| 17216 | + } | ||
| 17217 | + }, | ||
| 17158 | "node_modules/vue-devtools-stub": { | 17218 | "node_modules/vue-devtools-stub": { |
| 17159 | "version": "0.1.0", | 17219 | "version": "0.1.0", |
| 17160 | "resolved": "https://registry.npmjs.org/vue-devtools-stub/-/vue-devtools-stub-0.1.0.tgz", | 17220 | "resolved": "https://registry.npmjs.org/vue-devtools-stub/-/vue-devtools-stub-0.1.0.tgz", |
| 1 | { | 1 | { |
| 2 | "name": "nuxt-app", | 2 | "name": "nuxt-app", |
| 3 | - "version": "0.1.0", | 3 | + "version": "0.2.0", |
| 4 | "private": true, | 4 | "private": true, |
| 5 | "type": "module", | 5 | "type": "module", |
| 6 | "scripts": { | 6 | "scripts": { |
| @@ -21,14 +21,15 @@ | @@ -21,14 +21,15 @@ | ||
| 21 | "@nuxt/scripts": "^0.11.6", | 21 | "@nuxt/scripts": "^0.11.6", |
| 22 | "@nuxt/test-utils": "^3.17.2", | 22 | "@nuxt/test-utils": "^3.17.2", |
| 23 | "@nuxt/ui": "^2.22.0", | 23 | "@nuxt/ui": "^2.22.0", |
| 24 | - "@pinia/nuxt": "^0.11.0", | 24 | + "@pinia-orm/nuxt": "^1.10.2", |
| 25 | + "@pinia/nuxt": "^0.9.0", | ||
| 25 | "@unhead/vue": "^2.0.8", | 26 | "@unhead/vue": "^2.0.8", |
| 26 | "@vueuse/core": "^13.1.0", | 27 | "@vueuse/core": "^13.1.0", |
| 27 | "@vueuse/nuxt": "^13.1.0", | 28 | "@vueuse/nuxt": "^13.1.0", |
| 28 | "eslint": "^9.25.1", | 29 | "eslint": "^9.25.1", |
| 29 | "lucide-vue-next": "^0.503.0", | 30 | "lucide-vue-next": "^0.503.0", |
| 30 | "nuxt": "^3.16.2", | 31 | "nuxt": "^3.16.2", |
| 31 | - "pinia": "^3.0.2", | 32 | + "pinia": "^2.3.1", |
| 32 | "pinia-plugin-persistedstate": "^4.2.0", | 33 | "pinia-plugin-persistedstate": "^4.2.0", |
| 33 | "vue": "^3.5.13", | 34 | "vue": "^3.5.13", |
| 34 | "vue-router": "^4.5.0", | 35 | "vue-router": "^4.5.0", |
pages/index.vue
0 → 100644
-
Please register or login to post a comment