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