Showing
21 changed files
with
199 additions
and
174 deletions
1 | <script lang="ts" setup> | 1 | <script lang="ts" setup> |
2 | -//#region --Props--. | 2 | +import type { MovieInterface } from "~/interfaces/movie"; |
3 | +// #region --Props--. | ||
3 | import { useDateFormat } from "@vueuse/core"; | 4 | import { useDateFormat } from "@vueuse/core"; |
4 | import { FilmIcon } from "lucide-vue-next"; | 5 | import { FilmIcon } from "lucide-vue-next"; |
5 | -import type { MovieInterface } from "~/interfaces/movie"; | 6 | +// #endregion |
6 | -//#endregion | ||
7 | 7 | ||
8 | -//#region --Props--. | 8 | +// #region --Props--. |
9 | /** Typescript typage */ | 9 | /** Typescript typage */ |
10 | defineProps<{ | 10 | defineProps<{ |
11 | movie: MovieInterface; | 11 | movie: MovieInterface; |
@@ -18,7 +18,7 @@ defineProps<{ | @@ -18,7 +18,7 @@ defineProps<{ | ||
18 | // nullable: false, | 18 | // nullable: false, |
19 | // }, | 19 | // }, |
20 | // }); | 20 | // }); |
21 | -//#endregion | 21 | +// #endregion |
22 | </script> | 22 | </script> |
23 | 23 | ||
24 | <template> | 24 | <template> |
1 | <script lang="ts" setup> | 1 | <script lang="ts" setup> |
2 | -//#region --Import--. | 2 | +// #region --Import--. |
3 | import type { MovieCommentInterface } from "~/interfaces/movieComment"; | 3 | import type { MovieCommentInterface } from "~/interfaces/movieComment"; |
4 | import { MessageSquareIcon } from "lucide-vue-next"; | 4 | import { MessageSquareIcon } from "lucide-vue-next"; |
5 | -//#endregion | 5 | +// #endregion |
6 | 6 | ||
7 | -//#region --Props--. | 7 | +// #region --Props--. |
8 | /** Typescript typage */ | 8 | /** Typescript typage */ |
9 | const props = defineProps<{ | 9 | const props = defineProps<{ |
10 | comments: Array<MovieCommentInterface>; | 10 | comments: Array<MovieCommentInterface>; |
@@ -17,9 +17,9 @@ const props = defineProps<{ | @@ -17,9 +17,9 @@ const props = defineProps<{ | ||
17 | // nullable: false, | 17 | // nullable: false, |
18 | // }, | 18 | // }, |
19 | // }); | 19 | // }); |
20 | -//#endregion | 20 | +// #endregion |
21 | 21 | ||
22 | -//#region --Watch--. | 22 | +// #region --Watch--. |
23 | watch( | 23 | watch( |
24 | () => props.comments, | 24 | () => props.comments, |
25 | (comments) => { | 25 | (comments) => { |
@@ -34,7 +34,7 @@ watch( | @@ -34,7 +34,7 @@ watch( | ||
34 | }, | 34 | }, |
35 | { immediate: true }, | 35 | { immediate: true }, |
36 | ); | 36 | ); |
37 | -//#endregion | 37 | +// #endregion |
38 | </script> | 38 | </script> |
39 | 39 | ||
40 | <template> | 40 | <template> |
1 | <script lang="ts" setup> | 1 | <script lang="ts" setup> |
2 | -//#region --import--. | 2 | +import type { MovieInterface } from "~/interfaces/movie"; |
3 | +import { SearchXIcon } from "lucide-vue-next"; | ||
4 | +// #region --import--. | ||
3 | import { onBeforeUnmount, ref } from "vue"; | 5 | import { onBeforeUnmount, ref } from "vue"; |
4 | import { useTMDB } from "~/composables/tMDB"; | 6 | import { useTMDB } from "~/composables/tMDB"; |
5 | import { Movie } from "~/models/movie"; | 7 | import { Movie } from "~/models/movie"; |
6 | -import { SearchXIcon } from "lucide-vue-next"; | 8 | +// #endregion |
7 | -import type { MovieInterface } from "~/interfaces/movie"; | ||
8 | -//#endregion | ||
9 | 9 | ||
10 | -//#region --Declaration--. | 10 | +// #region --Declaration--. |
11 | const { fetchPopularMovies, searchMovies } = useTMDB(); | 11 | const { fetchPopularMovies, searchMovies } = useTMDB(); |
12 | -//#endregion | 12 | +// #endregion |
13 | 13 | ||
14 | -//#region --Data/refs--. | 14 | +// #region --Data/refs--. |
15 | const isInitialLoading = ref(true); | 15 | const isInitialLoading = ref(true); |
16 | const isLoadingMore = ref(false); | 16 | const isLoadingMore = ref(false); |
17 | const currentPage = ref(1); | 17 | const currentPage = ref(1); |
@@ -21,20 +21,20 @@ const searchQuery = ref(""); | @@ -21,20 +21,20 @@ const searchQuery = ref(""); | ||
21 | const loadMoreTrigger = ref<HTMLElement | null>(null); | 21 | const loadMoreTrigger = ref<HTMLElement | null>(null); |
22 | /** Instance de IntersectionObserver */ | 22 | /** Instance de IntersectionObserver */ |
23 | const observer = ref<IntersectionObserver | null>(null); | 23 | const observer = ref<IntersectionObserver | null>(null); |
24 | -//#endregion | 24 | +// #endregion |
25 | 25 | ||
26 | -//#region --Computed--. | 26 | +// #region --Computed--. |
27 | const movies = computed(() => { | 27 | const movies = computed(() => { |
28 | return useRepo(Movie).query().orderBy("popularity", "desc").get() as unknown as MovieInterface[]; | 28 | return useRepo(Movie).query().orderBy("popularity", "desc").get() as unknown as MovieInterface[]; |
29 | }); | 29 | }); |
30 | -//#endregion | 30 | +// #endregion |
31 | 31 | ||
32 | -//#region --Function--. | 32 | +// #region --Function--. |
33 | /** | 33 | /** |
34 | * Fetch popular movies | 34 | * Fetch popular movies |
35 | * @param page | 35 | * @param page |
36 | */ | 36 | */ |
37 | -const fetchMovies = async (page: number) => { | 37 | +async function fetchMovies(page: number) { |
38 | try { | 38 | try { |
39 | isLoadingMore.value = true; | 39 | isLoadingMore.value = true; |
40 | const data = await fetchPopularMovies(page); | 40 | const data = await fetchPopularMovies(page); |
@@ -42,26 +42,29 @@ const fetchMovies = async (page: number) => { | @@ -42,26 +42,29 @@ const fetchMovies = async (page: number) => { | ||
42 | if (isInitialLoading.value) { | 42 | if (isInitialLoading.value) { |
43 | // First fetch, erase old data before save. | 43 | // First fetch, erase old data before save. |
44 | useRepo(Movie).fresh(data.results); | 44 | useRepo(Movie).fresh(data.results); |
45 | - } else { | 45 | + } |
46 | + else { | ||
46 | // Add to store collection. | 47 | // Add to store collection. |
47 | useRepo(Movie).save(data.results); | 48 | useRepo(Movie).save(data.results); |
48 | } | 49 | } |
49 | totalPages.value = data.total_pages; | 50 | totalPages.value = data.total_pages; |
50 | currentPage.value = page; | 51 | currentPage.value = page; |
51 | - } catch (error) { | 52 | + } |
53 | + catch (error) { | ||
52 | console.error("Error fetching popular movies:", error); | 54 | console.error("Error fetching popular movies:", error); |
53 | - } finally { | 55 | + } |
56 | + finally { | ||
54 | isInitialLoading.value = false; | 57 | isInitialLoading.value = false; |
55 | isLoadingMore.value = false; | 58 | isLoadingMore.value = false; |
56 | } | 59 | } |
57 | -}; | 60 | +} |
58 | 61 | ||
59 | /** | 62 | /** |
60 | * Search movies | 63 | * Search movies |
61 | * @param query | 64 | * @param query |
62 | * @param page | 65 | * @param page |
63 | */ | 66 | */ |
64 | -const search = async (query: string, page: number) => { | 67 | +async function search(query: string, page: number) { |
65 | // If empty search, fetch popular movies. | 68 | // If empty search, fetch popular movies. |
66 | if (!query.trim()) { | 69 | if (!query.trim()) { |
67 | await fetchMovies(1); | 70 | await fetchMovies(1); |
@@ -77,19 +80,22 @@ const search = async (query: string, page: number) => { | @@ -77,19 +80,22 @@ const search = async (query: string, page: number) => { | ||
77 | if (isInitialLoading.value) { | 80 | if (isInitialLoading.value) { |
78 | // First fetch, erase old data before save. | 81 | // First fetch, erase old data before save. |
79 | useRepo(Movie).fresh(data.results); | 82 | useRepo(Movie).fresh(data.results); |
80 | - } else { | 83 | + } |
84 | + else { | ||
81 | // Add to store collection. | 85 | // Add to store collection. |
82 | useRepo(Movie).save(data.results); | 86 | useRepo(Movie).save(data.results); |
83 | } | 87 | } |
84 | totalPages.value = data.total_pages; | 88 | totalPages.value = data.total_pages; |
85 | currentPage.value = page; | 89 | currentPage.value = page; |
86 | - } catch (error) { | 90 | + } |
91 | + catch (error) { | ||
87 | console.error("Error searching movies:", error); | 92 | console.error("Error searching movies:", error); |
88 | - } finally { | 93 | + } |
94 | + finally { | ||
89 | isInitialLoading.value = false; | 95 | isInitialLoading.value = false; |
90 | isLoadingMore.value = false; | 96 | isLoadingMore.value = false; |
91 | } | 97 | } |
92 | -}; | 98 | +} |
93 | 99 | ||
94 | function createIntersectionObserver() { | 100 | function createIntersectionObserver() { |
95 | return new IntersectionObserver( | 101 | return new IntersectionObserver( |
@@ -99,7 +105,8 @@ function createIntersectionObserver() { | @@ -99,7 +105,8 @@ function createIntersectionObserver() { | ||
99 | if (searchQuery.value) { | 105 | if (searchQuery.value) { |
100 | // Continue searching query if already active. | 106 | // Continue searching query if already active. |
101 | search(searchQuery.value, currentPage.value + 1); | 107 | search(searchQuery.value, currentPage.value + 1); |
102 | - } else { | 108 | + } |
109 | + else { | ||
103 | // Continue fetching popular movies. | 110 | // Continue fetching popular movies. |
104 | fetchMovies(currentPage.value + 1); | 111 | fetchMovies(currentPage.value + 1); |
105 | } | 112 | } |
@@ -122,9 +129,9 @@ function handleClearSearchEvent() { | @@ -122,9 +129,9 @@ function handleClearSearchEvent() { | ||
122 | fetchMovies(1); | 129 | fetchMovies(1); |
123 | } | 130 | } |
124 | 131 | ||
125 | -//#endregion | 132 | +// #endregion |
126 | 133 | ||
127 | -//#region --Global event--. | 134 | +// #region --Global event--. |
128 | onMounted(() => { | 135 | onMounted(() => { |
129 | // First loading. | 136 | // First loading. |
130 | fetchMovies(1); | 137 | fetchMovies(1); |
@@ -146,7 +153,7 @@ onBeforeUnmount(() => { | @@ -146,7 +153,7 @@ onBeforeUnmount(() => { | ||
146 | observer.value.disconnect(); | 153 | observer.value.disconnect(); |
147 | } | 154 | } |
148 | }); | 155 | }); |
149 | -//#endregion | 156 | +// #endregion |
150 | </script> | 157 | </script> |
151 | 158 | ||
152 | <template> | 159 | <template> |
@@ -157,8 +164,8 @@ onBeforeUnmount(() => { | @@ -157,8 +164,8 @@ onBeforeUnmount(() => { | ||
157 | <!-- Barre de recherche --> | 164 | <!-- Barre de recherche --> |
158 | <ui-components-search-bar | 165 | <ui-components-search-bar |
159 | placeholder="Rechercher un film..." | 166 | placeholder="Rechercher un film..." |
160 | - @event:search="handleSearchEvent" | 167 | + @event-search="handleSearchEvent" |
161 | - @event:clear_search="handleClearSearchEvent" | 168 | + @event-clear-search="handleClearSearchEvent" |
162 | /> | 169 | /> |
163 | 170 | ||
164 | <!-- Loading Skeleton --> | 171 | <!-- Loading Skeleton --> |
1 | <script lang="ts" setup> | 1 | <script lang="ts" setup> |
2 | -//#region --Import--. | 2 | +// #region --Import--. |
3 | import type { Genre } from "~/interfaces/movie"; | 3 | import type { Genre } from "~/interfaces/movie"; |
4 | -//#endregion | 4 | +// #endregion |
5 | 5 | ||
6 | -//#region --Props--. | 6 | +// #region --Props--. |
7 | defineProps({ | 7 | defineProps({ |
8 | genres: { | 8 | genres: { |
9 | type: Array<Genre>, | 9 | type: Array<Genre>, |
@@ -11,7 +11,7 @@ defineProps({ | @@ -11,7 +11,7 @@ defineProps({ | ||
11 | nullable: false, | 11 | nullable: false, |
12 | }, | 12 | }, |
13 | }); | 13 | }); |
14 | -//#endregion | 14 | +// #endregion |
15 | </script> | 15 | </script> |
16 | 16 | ||
17 | <template> | 17 | <template> |
1 | <script lang="ts" setup> | 1 | <script lang="ts" setup> |
2 | -//#region --Props--. | 2 | +// #region --Props--. |
3 | /** Typescript typage */ | 3 | /** Typescript typage */ |
4 | defineProps<{ | 4 | defineProps<{ |
5 | score: number; | 5 | score: number; |
@@ -18,20 +18,20 @@ defineProps<{ | @@ -18,20 +18,20 @@ defineProps<{ | ||
18 | // nullable: false, | 18 | // nullable: false, |
19 | // }, | 19 | // }, |
20 | // }); | 20 | // }); |
21 | -//#endregion | 21 | +// #endregion |
22 | 22 | ||
23 | -//#region --Function--. | 23 | +// #region --Function--. |
24 | /** | 24 | /** |
25 | * Format vote count if > 1000. | 25 | * Format vote count if > 1000. |
26 | * @param count | 26 | * @param count |
27 | */ | 27 | */ |
28 | -const formatVoteCount = (count: number) => { | 28 | +function formatVoteCount(count: number) { |
29 | if (count >= 1000) { | 29 | if (count >= 1000) { |
30 | return `${(count / 1000).toFixed(1)}k votes`; | 30 | return `${(count / 1000).toFixed(1)}k votes`; |
31 | } | 31 | } |
32 | return `${count} votes`; | 32 | return `${count} votes`; |
33 | -}; | 33 | +} |
34 | -//#endregion | 34 | +// #endregion |
35 | </script> | 35 | </script> |
36 | 36 | ||
37 | <template> | 37 | <template> |
1 | <script lang="ts" setup> | 1 | <script lang="ts" setup> |
2 | -//#region --Import--. | 2 | +import type { Comment } from "~/type/commentForm"; |
3 | +// #region --Import--. | ||
3 | import { useVuelidate } from "@vuelidate/core"; | 4 | import { useVuelidate } from "@vuelidate/core"; |
4 | import { helpers, maxLength, maxValue, minLength, minValue, required } from "@vuelidate/validators"; | 5 | import { helpers, maxLength, maxValue, minLength, minValue, required } from "@vuelidate/validators"; |
5 | -import type { Comment } from "~/type/commentForm"; | 6 | +// #endregion |
6 | -//#endregion | ||
7 | 7 | ||
8 | -//#region --Emit--. | 8 | +// #region --Props--. |
9 | -const emit = defineEmits(["event:submit"]); | ||
10 | -//#endregion | ||
11 | - | ||
12 | -//#region --Props--. | ||
13 | defineProps({ | 9 | defineProps({ |
14 | isSubmitting: { | 10 | isSubmitting: { |
15 | type: Boolean, | 11 | type: Boolean, |
@@ -18,9 +14,13 @@ defineProps({ | @@ -18,9 +14,13 @@ defineProps({ | ||
18 | default: false, | 14 | default: false, |
19 | }, | 15 | }, |
20 | }); | 16 | }); |
21 | -//#endregion | 17 | +// #endregion |
18 | + | ||
19 | +// #region --Emit--. | ||
20 | +const emit = defineEmits(["eventSubmit"]); | ||
21 | +// #endregion | ||
22 | 22 | ||
23 | -//#region --Data/ref--. | 23 | +// #region --Data/ref--. |
24 | const initialState: Comment = { | 24 | const initialState: Comment = { |
25 | username: "", | 25 | username: "", |
26 | message: "", | 26 | message: "", |
@@ -35,6 +35,7 @@ const rules = { | @@ -35,6 +35,7 @@ const rules = { | ||
35 | maxLength: helpers.withMessage("Le nom d'utilisateur ne peut pas dépasser 50 caractères", maxLength(50)), | 35 | maxLength: helpers.withMessage("Le nom d'utilisateur ne peut pas dépasser 50 caractères", maxLength(50)), |
36 | alpha: helpers.withMessage( | 36 | alpha: helpers.withMessage( |
37 | "Le nom d'utilisateur ne peut contenir que des lettres", | 37 | "Le nom d'utilisateur ne peut contenir que des lettres", |
38 | + // eslint-disable-next-line regexp/no-obscure-range | ||
38 | helpers.regex(/^[a-zA-ZÀ-ÿ\s]+$/), | 39 | helpers.regex(/^[a-zA-ZÀ-ÿ\s]+$/), |
39 | ), | 40 | ), |
40 | }, | 41 | }, |
@@ -54,15 +55,15 @@ const formData = reactive({ | @@ -54,15 +55,15 @@ const formData = reactive({ | ||
54 | ...initialState, | 55 | ...initialState, |
55 | }); | 56 | }); |
56 | const v$ = useVuelidate(rules, formData); | 57 | const v$ = useVuelidate(rules, formData); |
57 | -//#endregion | 58 | +// #endregion |
58 | 59 | ||
59 | // const errormessages = computed(() => { | 60 | // const errormessages = computed(() => { |
60 | // return v$.value.message.$errors.map((e) => e.$message); | 61 | // return v$.value.message.$errors.map((e) => e.$message); |
61 | // }); | 62 | // }); |
62 | 63 | ||
63 | -//#region --Function--. | 64 | +// #region --Function--. |
64 | async function submitComment() { | 65 | async function submitComment() { |
65 | - emit("event:submit", formData); | 66 | + emit("eventSubmit", formData); |
66 | } | 67 | } |
67 | 68 | ||
68 | function clear() { | 69 | function clear() { |
@@ -80,7 +81,7 @@ function handleMessageEvent(event: string) { | @@ -80,7 +81,7 @@ function handleMessageEvent(event: string) { | ||
80 | // console.log(formData.message.replace(/(<([^>]+)>)/ig, '')); | 81 | // console.log(formData.message.replace(/(<([^>]+)>)/ig, '')); |
81 | } | 82 | } |
82 | 83 | ||
83 | -//#endregion | 84 | +// #endregion |
84 | </script> | 85 | </script> |
85 | 86 | ||
86 | <template> | 87 | <template> |
@@ -105,7 +106,7 @@ function handleMessageEvent(event: string) { | @@ -105,7 +106,7 @@ function handleMessageEvent(event: string) { | ||
105 | @blur="v$.rating.$touch" | 106 | @blur="v$.rating.$touch" |
106 | @input="v$.rating.$touch" | 107 | @input="v$.rating.$touch" |
107 | /> | 108 | /> |
108 | - <!-- <pre>{{ errormessages }}</pre>--> | 109 | + <!-- <pre>{{ errormessages }}</pre> --> |
109 | <ui-components-tiny-mce-field-editor | 110 | <ui-components-tiny-mce-field-editor |
110 | :error-message="v$?.message?.$errors[0]?.$message ? (v$.message.$errors[0].$message as string) : ''" | 111 | :error-message="v$?.message?.$errors[0]?.$message ? (v$.message.$errors[0].$message as string) : ''" |
111 | :model-value="formData.message" | 112 | :model-value="formData.message" |
1 | -import { describe, expect, it } from "vitest"; | ||
2 | import { mount } from "@vue/test-utils"; | 1 | import { mount } from "@vue/test-utils"; |
2 | +import { describe, expect, it } from "vitest"; | ||
3 | 3 | ||
4 | import HelloWorld from "./HelloWorld.vue"; | 4 | import HelloWorld from "./HelloWorld.vue"; |
5 | 5 | ||
6 | -describe("HelloWorld", () => { | 6 | +describe("helloWorld", () => { |
7 | it("component renders Hello world properly", () => { | 7 | it("component renders Hello world properly", () => { |
8 | const wrapper = mount(HelloWorld); | 8 | const wrapper = mount(HelloWorld); |
9 | expect(wrapper.text()).toContain("Hello world"); | 9 | expect(wrapper.text()).toContain("Hello world"); |
1 | <script lang="ts" setup> | 1 | <script lang="ts" setup> |
2 | -//#region --Props--. | 2 | +// #region --Props--. |
3 | defineProps({ | 3 | defineProps({ |
4 | src: { | 4 | src: { |
5 | type: String, | 5 | type: String, |
@@ -12,11 +12,11 @@ defineProps({ | @@ -12,11 +12,11 @@ defineProps({ | ||
12 | nullable: false, | 12 | nullable: false, |
13 | }, | 13 | }, |
14 | }); | 14 | }); |
15 | -//#endregion | 15 | +// #endregion |
16 | 16 | ||
17 | -//#region --Declaration--. | 17 | +// #region --Declaration--. |
18 | const w: Window = window; | 18 | const w: Window = window; |
19 | -//#endregion | 19 | +// #endregion |
20 | </script> | 20 | </script> |
21 | 21 | ||
22 | <template> | 22 | <template> |
1 | <script lang="ts" setup> | 1 | <script lang="ts" setup> |
2 | -//#region --Props--. | 2 | +// #region --Props--. |
3 | defineProps({ | 3 | defineProps({ |
4 | isLoading: { | 4 | isLoading: { |
5 | type: Boolean, | 5 | type: Boolean, |
@@ -13,7 +13,7 @@ defineProps({ | @@ -13,7 +13,7 @@ defineProps({ | ||
13 | default: false, | 13 | default: false, |
14 | }, | 14 | }, |
15 | }); | 15 | }); |
16 | -//#endregion | 16 | +// #endregion |
17 | </script> | 17 | </script> |
18 | 18 | ||
19 | <template> | 19 | <template> |
1 | <script setup lang="ts"> | 1 | <script setup lang="ts"> |
2 | -//#region --Props--. | 2 | +// #region --Props--. |
3 | import { FilmIcon } from "lucide-vue-next"; | 3 | import { FilmIcon } from "lucide-vue-next"; |
4 | 4 | ||
5 | defineProps({ | 5 | defineProps({ |
@@ -14,7 +14,7 @@ defineProps({ | @@ -14,7 +14,7 @@ defineProps({ | ||
14 | nullable: false, | 14 | nullable: false, |
15 | }, | 15 | }, |
16 | }); | 16 | }); |
17 | -//#endregion | 17 | +// #endregion |
18 | </script> | 18 | </script> |
19 | 19 | ||
20 | <template> | 20 | <template> |
1 | <script lang="ts" setup> | 1 | <script lang="ts" setup> |
2 | -//#region --import--. | 2 | +import { useDebounceFn } from "@vueuse/core"; |
3 | +// #region --import--. | ||
3 | import { SearchIcon, XIcon } from "lucide-vue-next"; | 4 | import { SearchIcon, XIcon } from "lucide-vue-next"; |
4 | import { ref } from "vue"; | 5 | import { ref } from "vue"; |
5 | -import { useDebounceFn } from "@vueuse/core"; | 6 | +// #endregion |
6 | -//#endregion | ||
7 | 7 | ||
8 | -//#region --Emits--. | 8 | +// #region --Props--. |
9 | -const emit = defineEmits(["event:search", "event:clear_search"]); | ||
10 | -//#endregion | ||
11 | - | ||
12 | -//#region --Props--. | ||
13 | defineProps({ | 9 | defineProps({ |
14 | placeholder: { | 10 | placeholder: { |
15 | type: String, | 11 | type: String, |
@@ -18,25 +14,29 @@ defineProps({ | @@ -18,25 +14,29 @@ defineProps({ | ||
18 | default: "", | 14 | default: "", |
19 | }, | 15 | }, |
20 | }); | 16 | }); |
21 | -//#endregion | 17 | +// #endregion |
18 | + | ||
19 | +// #region --Emits--. | ||
20 | +const emit = defineEmits(["eventSearch", "eventClearSearch"]); | ||
21 | +// #endregion | ||
22 | 22 | ||
23 | -//#region --Data/refs--. | 23 | +// #region --Data/refs--. |
24 | const searchQuery = ref(""); | 24 | const searchQuery = ref(""); |
25 | -//#endregion | 25 | +// #endregion |
26 | 26 | ||
27 | -//#region --Function--. | 27 | +// #region --Function--. |
28 | /** | 28 | /** |
29 | * Debounced function | 29 | * Debounced function |
30 | */ | 30 | */ |
31 | const handleSearchEvent = useDebounceFn(() => { | 31 | const handleSearchEvent = useDebounceFn(() => { |
32 | - emit("event:search", searchQuery.value); | 32 | + emit("eventSearch", searchQuery.value); |
33 | }, 500); | 33 | }, 500); |
34 | 34 | ||
35 | function handleClearSearchEvent() { | 35 | function handleClearSearchEvent() { |
36 | searchQuery.value = ""; | 36 | searchQuery.value = ""; |
37 | - emit("event:clear_search"); | 37 | + emit("eventClearSearch"); |
38 | } | 38 | } |
39 | -//#endregion | 39 | +// #endregion |
40 | </script> | 40 | </script> |
41 | 41 | ||
42 | <template> | 42 | <template> |
1 | <script lang="ts" setup> | 1 | <script lang="ts" setup> |
2 | -//#region --Props--. | 2 | +// #region --Props--. |
3 | defineProps({ | 3 | defineProps({ |
4 | isInitialLoading: { | 4 | isInitialLoading: { |
5 | type: Boolean, | 5 | type: Boolean, |
@@ -13,7 +13,7 @@ defineProps({ | @@ -13,7 +13,7 @@ defineProps({ | ||
13 | default: 12, | 13 | default: 12, |
14 | }, | 14 | }, |
15 | }); | 15 | }); |
16 | -//#endregion | 16 | +// #endregion |
17 | </script> | 17 | </script> |
18 | 18 | ||
19 | <template> | 19 | <template> |
1 | <script lang="ts" setup> | 1 | <script lang="ts" setup> |
2 | -//#region --Import--. | 2 | +// #region --Import--. |
3 | import Editor from "@tinymce/tinymce-vue"; | 3 | import Editor from "@tinymce/tinymce-vue"; |
4 | import { ref, watch } from "vue"; | 4 | import { ref, watch } from "vue"; |
5 | -//#endregion | 5 | +// #endregion |
6 | 6 | ||
7 | -//#region --Declaration--. | 7 | +// #region --Props--. |
8 | -const runtimeConfig = useRuntimeConfig(); | 8 | +const props = defineProps<{ |
9 | -//#endregion | 9 | + modelValue: string; |
10 | + errorMessage: string; | ||
11 | +}>(); | ||
12 | +// #endregion | ||
10 | 13 | ||
11 | -//#region --Emit--. | 14 | +// #region --Emit--. |
12 | const emit = defineEmits<{ | 15 | const emit = defineEmits<{ |
13 | (e: "update:modelValue", value: string): void; | 16 | (e: "update:modelValue", value: string): void; |
14 | }>(); | 17 | }>(); |
15 | -//#endregion | 18 | +// #endregion |
16 | 19 | ||
17 | -//#region --Props--. | 20 | +// #region --Declaration--. |
18 | -const props = defineProps<{ | 21 | +const runtimeConfig = useRuntimeConfig(); |
19 | - modelValue: string; | 22 | +// #endregion |
20 | - errorMessage: string; | ||
21 | -}>(); | ||
22 | -//#endregion | ||
23 | 23 | ||
24 | -//#region --Data/ref--. | 24 | +// #region --Data/ref--. |
25 | const content = ref(props.modelValue); | 25 | const content = ref(props.modelValue); |
26 | const init = { | 26 | const init = { |
27 | height: 300, | 27 | height: 300, |
@@ -48,10 +48,10 @@ const init = { | @@ -48,10 +48,10 @@ const init = { | ||
48 | "wordcount", | 48 | "wordcount", |
49 | ], | 49 | ], |
50 | toolbar: | 50 | toolbar: |
51 | - "undo redo | blocks | bold italic underline strikethrough |" + | 51 | + "undo redo | blocks | bold italic underline strikethrough |" |
52 | - "bold italic forecolor | alignleft aligncenter " + | 52 | + + "bold italic forecolor | alignleft aligncenter " |
53 | - "alignright alignjustify | bullist numlist outdent indent | " + | 53 | + + "alignright alignjustify | bullist numlist outdent indent | " |
54 | - "removeformat | help", | 54 | + + "removeformat | help", |
55 | content_style: "body { font-family:Helvetica,Arial,sans-serif; font-size:14px }", | 55 | content_style: "body { font-family:Helvetica,Arial,sans-serif; font-size:14px }", |
56 | skin: "oxide-dark", | 56 | skin: "oxide-dark", |
57 | content_css: "dark", | 57 | content_css: "dark", |
@@ -59,9 +59,9 @@ const init = { | @@ -59,9 +59,9 @@ const init = { | ||
59 | // valid_elements: [], | 59 | // valid_elements: [], |
60 | // entity_encoding : "raw", | 60 | // entity_encoding : "raw", |
61 | }; | 61 | }; |
62 | -//#endregion | 62 | +// #endregion |
63 | 63 | ||
64 | -//#region --Watch--. | 64 | +// #region --Watch--. |
65 | watch(content, (newValue) => { | 65 | watch(content, (newValue) => { |
66 | emit("update:modelValue", newValue); | 66 | emit("update:modelValue", newValue); |
67 | }); | 67 | }); |
@@ -74,12 +74,12 @@ watch( | @@ -74,12 +74,12 @@ watch( | ||
74 | } | 74 | } |
75 | }, | 75 | }, |
76 | ); | 76 | ); |
77 | -//#endregion | 77 | +// #endregion |
78 | </script> | 78 | </script> |
79 | 79 | ||
80 | <template> | 80 | <template> |
81 | <div> | 81 | <div> |
82 | - <editor | 82 | + <Editor |
83 | v-model="content" | 83 | v-model="content" |
84 | :api-key="runtimeConfig.public.apiTinyMceSecret" | 84 | :api-key="runtimeConfig.public.apiTinyMceSecret" |
85 | :init="init" | 85 | :init="init" |
1 | import type { RuntimeConfig } from "nuxt/schema"; | 1 | import type { RuntimeConfig } from "nuxt/schema"; |
2 | 2 | ||
3 | -export const useTMDB = function () { | 3 | +export function useTMDB() { |
4 | const runtimeconfig: RuntimeConfig = useRuntimeConfig(); | 4 | const runtimeconfig: RuntimeConfig = useRuntimeConfig(); |
5 | const apiUrl = runtimeconfig.public.apiTMDBUrl; | 5 | const apiUrl = runtimeconfig.public.apiTMDBUrl; |
6 | const apiKey = runtimeconfig.public.apiTMDBSecret; | 6 | const apiKey = runtimeconfig.public.apiTMDBSecret; |
@@ -14,10 +14,12 @@ export const useTMDB = function () { | @@ -14,10 +14,12 @@ export const useTMDB = function () { | ||
14 | const response = await fetch(`${apiUrl}/movie/popular?api_key=${apiKey}&language=fr-FR&page=${page}`); | 14 | const response = await fetch(`${apiUrl}/movie/popular?api_key=${apiKey}&language=fr-FR&page=${page}`); |
15 | if (!response.ok) { | 15 | if (!response.ok) { |
16 | console.error("An error occurred when fetching popular movies:"); | 16 | console.error("An error occurred when fetching popular movies:"); |
17 | - } else { | 17 | + } |
18 | + else { | ||
18 | return await response.json(); | 19 | return await response.json(); |
19 | } | 20 | } |
20 | - } catch (error) { | 21 | + } |
22 | + catch (error) { | ||
21 | console.error("Error fetching popular movies:", error); | 23 | console.error("Error fetching popular movies:", error); |
22 | } | 24 | } |
23 | }; | 25 | }; |
@@ -34,10 +36,12 @@ export const useTMDB = function () { | @@ -34,10 +36,12 @@ export const useTMDB = function () { | ||
34 | ); | 36 | ); |
35 | if (!response.ok) { | 37 | if (!response.ok) { |
36 | console.error("An error occurred when searching movies:"); | 38 | console.error("An error occurred when searching movies:"); |
37 | - } else { | 39 | + } |
40 | + else { | ||
38 | return await response.json(); | 41 | return await response.json(); |
39 | } | 42 | } |
40 | - } catch (error) { | 43 | + } |
44 | + catch (error) { | ||
41 | console.error("Error searching movies:", error); | 45 | console.error("Error searching movies:", error); |
42 | } | 46 | } |
43 | }; | 47 | }; |
@@ -51,10 +55,12 @@ export const useTMDB = function () { | @@ -51,10 +55,12 @@ export const useTMDB = function () { | ||
51 | const response = await fetch(`${apiUrl}/movie/${id}?api_key=${apiKey}&language=fr-FR`); | 55 | const response = await fetch(`${apiUrl}/movie/${id}?api_key=${apiKey}&language=fr-FR`); |
52 | if (!response.ok) { | 56 | if (!response.ok) { |
53 | console.error("An error occurred when fetching movie details:"); | 57 | console.error("An error occurred when fetching movie details:"); |
54 | - } else { | 58 | + } |
59 | + else { | ||
55 | return await response.json(); | 60 | return await response.json(); |
56 | } | 61 | } |
57 | - } catch (error) { | 62 | + } |
63 | + catch (error) { | ||
58 | console.error("Error fetching details:", error); | 64 | console.error("Error fetching details:", error); |
59 | } | 65 | } |
60 | }; | 66 | }; |
@@ -67,13 +73,15 @@ export const useTMDB = function () { | @@ -67,13 +73,15 @@ export const useTMDB = function () { | ||
67 | const response = await fetch(`${apiUrl}/movie/${id}/credits?api_key=${apiKey}&language=fr-FR`); | 73 | const response = await fetch(`${apiUrl}/movie/${id}/credits?api_key=${apiKey}&language=fr-FR`); |
68 | if (!response.ok) { | 74 | if (!response.ok) { |
69 | console.error("An error occurred when fetching movie credits:"); | 75 | console.error("An error occurred when fetching movie credits:"); |
70 | - } else { | 76 | + } |
77 | + else { | ||
71 | return await response.json(); | 78 | return await response.json(); |
72 | } | 79 | } |
73 | - } catch (error) { | 80 | + } |
81 | + catch (error) { | ||
74 | console.error("Error fetching movie credits:", error); | 82 | console.error("Error fetching movie credits:", error); |
75 | } | 83 | } |
76 | }; | 84 | }; |
77 | 85 | ||
78 | return { fetchPopularMovies, searchMovies, fetchMovieDetails, fetchMovieCredits }; | 86 | return { fetchPopularMovies, searchMovies, fetchMovieDetails, fetchMovieCredits }; |
79 | -}; | 87 | +} |
@@ -7,10 +7,10 @@ export interface CreditInterface { | @@ -7,10 +7,10 @@ export interface CreditInterface { | ||
7 | character?: string; | 7 | character?: string; |
8 | } | 8 | } |
9 | 9 | ||
10 | -export type CreditsResponse = { | 10 | +export interface CreditsResponse { |
11 | id: number; | 11 | id: number; |
12 | cast: CreditInterface[]; | 12 | cast: CreditInterface[]; |
13 | crew: CreditInterface[]; | 13 | crew: CreditInterface[]; |
14 | movie_id: unknown; | 14 | movie_id: unknown; |
15 | movie: MovieInterface; | 15 | movie: MovieInterface; |
16 | -}; | 16 | +} |
@@ -20,7 +20,7 @@ export interface MovieInterface { | @@ -20,7 +20,7 @@ export interface MovieInterface { | ||
20 | credit: CreditsResponse; | 20 | credit: CreditsResponse; |
21 | } | 21 | } |
22 | 22 | ||
23 | -export type Genre = { | 23 | +export interface Genre { |
24 | id: number; | 24 | id: number; |
25 | name: string; | 25 | name: string; |
26 | -}; | 26 | +} |
1 | <script lang="ts" setup> | 1 | <script lang="ts" setup> |
2 | -//#region --import--. | 2 | +import type { WhereSecondaryClosure } from "pinia-orm"; |
3 | +import type { CreditsResponse } from "~/interfaces/credit"; | ||
4 | +import type { MovieInterface } from "~/interfaces/movie"; | ||
5 | +import type { MovieCommentInterface } from "~/interfaces/movieComment"; | ||
6 | +// #region --import--. | ||
3 | import { AlertTriangleIcon, ArrowLeftIcon } from "lucide-vue-next"; | 7 | import { AlertTriangleIcon, ArrowLeftIcon } from "lucide-vue-next"; |
4 | -import { useTMDB } from "~/composables/tMDB"; | ||
5 | import { computed, onMounted, ref } from "vue"; | 8 | import { computed, onMounted, ref } from "vue"; |
6 | -import { Movie } from "~/models/movie"; | 9 | +import { useTMDB } from "~/composables/tMDB"; |
7 | -import type { MovieInterface } from "~/interfaces/movie"; | ||
8 | import { Credit } from "~/models/credit"; | 10 | import { Credit } from "~/models/credit"; |
9 | -import type { CreditsResponse } from "~/interfaces/credit"; | 11 | +import { Movie } from "~/models/movie"; |
10 | -import type { MovieCommentInterface } from "~/interfaces/movieComment"; | ||
11 | import { MovieComment } from "~/models/movieComment"; | 12 | import { MovieComment } from "~/models/movieComment"; |
12 | -import type { WhereSecondaryClosure } from "pinia-orm"; | 13 | +// #endregion |
13 | -//#endregion | ||
14 | 14 | ||
15 | -//#region --Declaration--. | 15 | +// #region --Declaration--. |
16 | const { fetchMovieDetails, fetchMovieCredits } = useTMDB(); | 16 | const { fetchMovieDetails, fetchMovieCredits } = useTMDB(); |
17 | -//#endregion | 17 | +// #endregion |
18 | 18 | ||
19 | -//#region --Declaration--. | 19 | +// #region --Declaration--. |
20 | const { currentRoute } = useRouter(); | 20 | const { currentRoute } = useRouter(); |
21 | -//#endregion | 21 | +// #endregion |
22 | 22 | ||
23 | -//#region --Data/ref--. | 23 | +// #region --Data/ref--. |
24 | const isLoading = ref(true); | 24 | const isLoading = ref(true); |
25 | const isSubmitting = ref(false); | 25 | const isSubmitting = ref(false); |
26 | -//#endregion | 26 | +// #endregion |
27 | 27 | ||
28 | -//#region --Computed--. | 28 | +// #region --Computed--. |
29 | const movieId = computed(() => { | 29 | const movieId = computed(() => { |
30 | if (currentRoute.value.params.id) { | 30 | if (currentRoute.value.params.id) { |
31 | if (typeof currentRoute.value.params.id === "string") { | 31 | if (typeof currentRoute.value.params.id === "string") { |
32 | if (typeof Number(+currentRoute.value.params.id) === "number") { | 32 | if (typeof Number(+currentRoute.value.params.id) === "number") { |
33 | return +currentRoute.value.params.id as number; | 33 | return +currentRoute.value.params.id as number; |
34 | - } else { | 34 | + } |
35 | + else { | ||
35 | return currentRoute.value.params.id as string; | 36 | return currentRoute.value.params.id as string; |
36 | } | 37 | } |
37 | - } else { | 38 | + } |
39 | + else { | ||
38 | return null; | 40 | return null; |
39 | } | 41 | } |
40 | - } else { | 42 | + } |
43 | + else { | ||
41 | return null; | 44 | return null; |
42 | } | 45 | } |
43 | }); | 46 | }); |
@@ -49,7 +52,8 @@ const movie = computed(() => { | @@ -49,7 +52,8 @@ const movie = computed(() => { | ||
49 | .where("id", movieId.value as WhereSecondaryClosure<never> | null | undefined) | 52 | .where("id", movieId.value as WhereSecondaryClosure<never> | null | undefined) |
50 | .withAll() | 53 | .withAll() |
51 | .first() as unknown as MovieInterface; | 54 | .first() as unknown as MovieInterface; |
52 | - } else { | 55 | + } |
56 | + else { | ||
53 | return null; | 57 | return null; |
54 | } | 58 | } |
55 | }); | 59 | }); |
@@ -59,8 +63,9 @@ const movie = computed(() => { | @@ -59,8 +63,9 @@ const movie = computed(() => { | ||
59 | */ | 63 | */ |
60 | const director = computed(() => { | 64 | const director = computed(() => { |
61 | if (unref(movie)?.credit?.crew) { | 65 | if (unref(movie)?.credit?.crew) { |
62 | - return movie.value?.credit.crew.find((person) => person.job === "Director"); | 66 | + return movie.value?.credit.crew.find(person => person.job === "Director"); |
63 | - } else { | 67 | + } |
68 | + else { | ||
64 | return null; | 69 | return null; |
65 | } | 70 | } |
66 | }); | 71 | }); |
@@ -78,39 +83,42 @@ const comments = computed(() => { | @@ -78,39 +83,42 @@ const comments = computed(() => { | ||
78 | .orderBy("createdAt", "desc") | 83 | .orderBy("createdAt", "desc") |
79 | .get(); | 84 | .get(); |
80 | }); | 85 | }); |
81 | -//#endregion | 86 | +// #endregion |
82 | 87 | ||
83 | -//#region --Function--. | 88 | +// #region --Function--. |
84 | /** | 89 | /** |
85 | * Fetch movie details | 90 | * Fetch movie details |
86 | */ | 91 | */ |
87 | -const fetchDetails = async (id: number | string) => { | 92 | +async function fetchDetails(id: number | string) { |
88 | try { | 93 | try { |
89 | isLoading.value = true; | 94 | isLoading.value = true; |
90 | 95 | ||
91 | const data = await fetchMovieDetails(id); | 96 | const data = await fetchMovieDetails(id); |
92 | // Add to store collection. | 97 | // Add to store collection. |
93 | useRepo(Movie).save(data); | 98 | useRepo(Movie).save(data); |
94 | - } catch (error) { | 99 | + } |
100 | + catch (error) { | ||
95 | console.error("Error fetching movie details:", error); | 101 | console.error("Error fetching movie details:", error); |
96 | - } finally { | 102 | + } |
103 | + finally { | ||
97 | isLoading.value = false; | 104 | isLoading.value = false; |
98 | } | 105 | } |
99 | -}; | 106 | +} |
100 | 107 | ||
101 | /** | 108 | /** |
102 | * Format runtime | 109 | * Format runtime |
103 | * @param minutes | 110 | * @param minutes |
104 | */ | 111 | */ |
105 | -const formatRuntime = (minutes: number) => { | 112 | +function formatRuntime(minutes: number) { |
106 | - if (!minutes) return "Durée inconnue"; | 113 | + if (!minutes) |
114 | + return "Durée inconnue"; | ||
107 | // Find nb hours. | 115 | // Find nb hours. |
108 | const hours = Math.floor(minutes / 60); | 116 | const hours = Math.floor(minutes / 60); |
109 | // Find last minutes. | 117 | // Find last minutes. |
110 | const mins = minutes % 60; | 118 | const mins = minutes % 60; |
111 | 119 | ||
112 | return `${hours}h ${mins}min`; | 120 | return `${hours}h ${mins}min`; |
113 | -}; | 121 | +} |
114 | 122 | ||
115 | async function fetchCredits(id: number | string) { | 123 | async function fetchCredits(id: number | string) { |
116 | try { | 124 | try { |
@@ -118,12 +126,13 @@ async function fetchCredits(id: number | string) { | @@ -118,12 +126,13 @@ async function fetchCredits(id: number | string) { | ||
118 | data.movie_id = id; | 126 | data.movie_id = id; |
119 | // Add to store collection. | 127 | // Add to store collection. |
120 | useRepo(Credit).save(data); | 128 | useRepo(Credit).save(data); |
121 | - } catch (error) { | 129 | + } |
130 | + catch (error) { | ||
122 | console.error("Error fetching movie credits:", error); | 131 | console.error("Error fetching movie credits:", error); |
123 | } | 132 | } |
124 | } | 133 | } |
125 | 134 | ||
126 | -//Ce n'est pas le film du siècle cependant, il est suffisamment intéressant pour passer une bonne soirée ! | 135 | +// Ce n'est pas le film du siècle cependant, il est suffisamment intéressant pour passer une bonne soirée ! |
127 | function handleSubmitEvent(event: MovieCommentInterface) { | 136 | function handleSubmitEvent(event: MovieCommentInterface) { |
128 | isSubmitting.value = true; | 137 | isSubmitting.value = true; |
129 | event.movie_id = unref(movieId); | 138 | event.movie_id = unref(movieId); |
@@ -132,9 +141,9 @@ function handleSubmitEvent(event: MovieCommentInterface) { | @@ -132,9 +141,9 @@ function handleSubmitEvent(event: MovieCommentInterface) { | ||
132 | isSubmitting.value = false; | 141 | isSubmitting.value = false; |
133 | } | 142 | } |
134 | 143 | ||
135 | -//#endregion | 144 | +// #endregion |
136 | 145 | ||
137 | -//#region --Global event--. | 146 | +// #region --Global event--. |
138 | onMounted(() => { | 147 | onMounted(() => { |
139 | // Fetch data on component mount. | 148 | // Fetch data on component mount. |
140 | if (unref(movieId)) { | 149 | if (unref(movieId)) { |
@@ -144,7 +153,7 @@ onMounted(() => { | @@ -144,7 +153,7 @@ onMounted(() => { | ||
144 | } | 153 | } |
145 | // loadComments() | 154 | // loadComments() |
146 | }); | 155 | }); |
147 | -//#endregion | 156 | +// #endregion |
148 | </script> | 157 | </script> |
149 | 158 | ||
150 | <template> | 159 | <template> |
@@ -245,7 +254,7 @@ onMounted(() => { | @@ -245,7 +254,7 @@ onMounted(() => { | ||
245 | <h3 class="text-xl font-bold mt-8 mb-4"> | 254 | <h3 class="text-xl font-bold mt-8 mb-4"> |
246 | Ajouter un commentaire | 255 | Ajouter un commentaire |
247 | </h3> | 256 | </h3> |
248 | - <form-movie-comment-form @event:submit="handleSubmitEvent" /> | 257 | + <form-movie-comment-form @event-submit="handleSubmitEvent" /> |
249 | 258 | ||
250 | <!-- Liste des commentaires --> | 259 | <!-- Liste des commentaires --> |
251 | <movie-comment-list :comments="comments as unknown as MovieCommentInterface[]" /> | 260 | <movie-comment-list :comments="comments as unknown as MovieCommentInterface[]" /> |
1 | -//#region --Import--. | 1 | +import type { Genre } from "~/interfaces/movie"; |
2 | -import { describe, expect, it } from "vitest"; | ||
3 | import { mount } from "@vue/test-utils"; | 2 | import { mount } from "@vue/test-utils"; |
3 | +// #region --Import--. | ||
4 | +import { describe, expect, it } from "vitest"; | ||
4 | import MovieGender from "../../components/details/MovieGender.vue"; | 5 | import MovieGender from "../../components/details/MovieGender.vue"; |
5 | -import type { Genre } from "~/interfaces/movie"; | 6 | +// #endregion |
6 | -//#endregion | ||
7 | 7 | ||
8 | -describe("MovieGender", () => { | 8 | +describe("movieGender", () => { |
9 | it("affiche correctement les genres", () => { | 9 | it("affiche correctement les genres", () => { |
10 | // Données de test. | 10 | // Données de test. |
11 | const genres: Genre[] = [ | 11 | const genres: Genre[] = [ |
1 | -//#region --Import--. | ||
2 | -import { describe, expect, it } from "vitest"; | ||
3 | import { mount } from "@vue/test-utils"; | 1 | import { mount } from "@vue/test-utils"; |
2 | +// #region --Import--. | ||
3 | +import { describe, expect, it } from "vitest"; | ||
4 | import ScoreAndVote from "../../components/details/ScoreAndVote.vue"; | 4 | import ScoreAndVote from "../../components/details/ScoreAndVote.vue"; |
5 | -//#endregion | 5 | +// #endregion |
6 | 6 | ||
7 | -describe("ScoreAndVote", () => { | 7 | +describe("scoreAndVote", () => { |
8 | it("affiche correctement le score", () => { | 8 | it("affiche correctement le score", () => { |
9 | // Monter le composant avec ses props. | 9 | // Monter le composant avec ses props. |
10 | const wrapper = mount(ScoreAndVote, { | 10 | const wrapper = mount(ScoreAndVote, { |
1 | -import { defineVitestConfig } from "@nuxt/test-utils/config"; | ||
2 | import { fileURLToPath } from "node:url"; | 1 | import { fileURLToPath } from "node:url"; |
2 | +import { defineVitestConfig } from "@nuxt/test-utils/config"; | ||
3 | 3 | ||
4 | export default defineVitestConfig({ | 4 | export default defineVitestConfig({ |
5 | /** | 5 | /** |
-
Please register or login to post a comment