feat: tan stack theme query
This commit is contained in:
		
							parent
							
								
									47a522e443
								
							
						
					
					
						commit
						2b509774b8
					
				
					 6 changed files with 197 additions and 64 deletions
				
			
		|  | @ -23,6 +23,12 @@ export function getThemes(req: Request, res: Response) { | ||||||
| 
 | 
 | ||||||
| export function getThemeByTitle(req: Request, res: Response) { | export function getThemeByTitle(req: Request, res: Response) { | ||||||
|     const themeKey = req.params.theme; |     const themeKey = req.params.theme; | ||||||
|  | 
 | ||||||
|  |     if (!theme) { | ||||||
|  |         res.status(400).json({ error: 'Missing required field: theme' }); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     const theme = themes.find((t) => t.title === themeKey); |     const theme = themes.find((t) => t.title === themeKey); | ||||||
| 
 | 
 | ||||||
|     if (theme) { |     if (theme) { | ||||||
|  |  | ||||||
|  | @ -16,12 +16,14 @@ | ||||||
|         "test:e2e": "playwright test" |         "test:e2e": "playwright test" | ||||||
|     }, |     }, | ||||||
|     "dependencies": { |     "dependencies": { | ||||||
|  |         "@tanstack/react-query": "^5.69.0", | ||||||
|  |         "@tanstack/vue-query": "^5.69.0", | ||||||
|  |         "axios": "^1.8.2", | ||||||
|  |         "oidc-client-ts": "^3.1.0", | ||||||
|         "vue": "^3.5.13", |         "vue": "^3.5.13", | ||||||
|         "vue-i18n": "^11.1.2", |         "vue-i18n": "^11.1.2", | ||||||
|         "vue-router": "^4.5.0", |         "vue-router": "^4.5.0", | ||||||
|         "vuetify": "^3.7.12", |         "vuetify": "^3.7.12" | ||||||
|         "oidc-client-ts": "^3.1.0", |  | ||||||
|         "axios": "^1.8.2" |  | ||||||
|     }, |     }, | ||||||
|     "devDependencies": { |     "devDependencies": { | ||||||
|         "@playwright/test": "^1.50.1", |         "@playwright/test": "^1.50.1", | ||||||
|  |  | ||||||
|  | @ -1,76 +1,52 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
|     import ThemeCard from "@/components/ThemeCard.vue"; | import ThemeCard from "@/components/ThemeCard.vue"; | ||||||
|     import { ref, onMounted, watch } from "vue"; | import { ref, watchEffect, computed } from "vue"; | ||||||
|     import { useI18n } from "vue-i18n"; | import { useI18n } from "vue-i18n"; | ||||||
|     import {AGE_TO_THEMES, THEMESITEMS} from "@/utils/constants.ts"; | import { AGE_TO_THEMES, THEMESITEMS } from "@/utils/constants.ts"; | ||||||
|     import {getAllThemes} from "@/controllers/themes.ts"; | import { useThemeQuery } from "@/queries/themes.ts"; | ||||||
|     import {getThemeController} from "@/controllers/controllers.ts"; |  | ||||||
| 
 | 
 | ||||||
|     // Receive the selectedTheme and selectedAge from the parent component | const props = defineProps({ | ||||||
|     const props = defineProps({ |     selectedTheme: { type: String, required: true }, | ||||||
|         selectedTheme: { |     selectedAge: { type: String, required: true } | ||||||
|             type: String, | }); | ||||||
|             required: true |  | ||||||
|         }, |  | ||||||
|         selectedAge: { |  | ||||||
|             type: String, |  | ||||||
|             required: true |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
| 
 | 
 | ||||||
|     const themeController = getThemeController(); | const { locale } = useI18n(); | ||||||
|  | const language = computed(() => locale.value); | ||||||
| 
 | 
 | ||||||
|     const {locale} = useI18n(); | const { data: allThemes, isLoading, error } = useThemeQuery(language); | ||||||
| 
 | 
 | ||||||
|     const allCards = ref<Array<{ key: string; title: string; description: string; image: string }>>([]); | const allCards = ref([]); | ||||||
|     const cards = ref<Array<{ key: string; title: string; description: string; image: string }>>([]); | const cards = ref([]); | ||||||
| 
 | 
 | ||||||
|     // Fetch all themes based on the current language | watchEffect(() => { | ||||||
|     async function fetchThemes() { |     const themes = allThemes.value ?? []; | ||||||
|         try { |     allCards.value = themes; | ||||||
|             // Get the current selected language |  | ||||||
|             const language = locale.value; |  | ||||||
| 
 | 
 | ||||||
|             // Update the cards value with the fetched themes |     if (props.selectedTheme) { | ||||||
|             allCards.value = await themeController.getAll(language); |         cards.value = themes.filter((theme) => | ||||||
|             cards.value = allCards.value; |             THEMESITEMS[props.selectedTheme]?.includes(theme.key) && | ||||||
|         } catch (error) { |             AGE_TO_THEMES[props.selectedAge]?.includes(theme.key) | ||||||
|             console.error("Error fetching themes:", error); |         ); | ||||||
|         } |     } else { | ||||||
|  |         cards.value = themes; | ||||||
|     } |     } | ||||||
| 
 | }); | ||||||
|     // Fetch on mount |  | ||||||
|     onMounted(fetchThemes); |  | ||||||
| 
 |  | ||||||
|     // Re-fetch themes when language changes |  | ||||||
|     watch(locale, () => { |  | ||||||
|         fetchThemes(); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     // Watch for selectedTheme change and filter themes |  | ||||||
|     watch(() => props.selectedTheme, (newTheme) => { |  | ||||||
|         if (newTheme) { |  | ||||||
|             cards.value = allCards.value.filter(theme => THEMESITEMS[newTheme].includes(theme.key) && AGE_TO_THEMES[props.selectedAge]?.includes(theme.key)); |  | ||||||
|         } else { |  | ||||||
|             cards.value = allCards.value; |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     // Watch for selectedAge change and filter themes |  | ||||||
|     watch(() => props.selectedAge, (newAge) => { |  | ||||||
|         if (newAge) { |  | ||||||
|             cards.value = allCards.value.filter(theme => THEMESITEMS[props.selectedTheme].includes(theme.key) && AGE_TO_THEMES[newAge]?.includes(theme.key)); |  | ||||||
|         } else { |  | ||||||
|             cards.value = allCards.value; |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| <template> | <template> | ||||||
|     <v-container> |     <v-container> | ||||||
|         <v-row> |         <div v-if="isLoading" class="text-center py-10"> | ||||||
|  |             <v-progress-circular indeterminate color="primary" /> | ||||||
|  |             <p>Loading...</p> | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         <div v-else-if="error" class="text-center py-10 text-error"> | ||||||
|  |             <v-icon large>mdi-alert-circle</v-icon> | ||||||
|  |             <p>Error loading: {{ error.message }}</p> | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         <v-row v-else> | ||||||
|             <v-col |             <v-col | ||||||
|                 v-for="card in cards" |                 v-for="card in cards" | ||||||
|                 :key="card.key" |                 :key="card.key" | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ import i18n from "./i18n/i18n.ts"; | ||||||
| // Components
 | // Components
 | ||||||
| import App from "./App.vue"; | import App from "./App.vue"; | ||||||
| import router from "./router"; | import router from "./router"; | ||||||
|  | import { VueQueryPlugin, QueryClient } from '@tanstack/vue-query'; | ||||||
| 
 | 
 | ||||||
| const app = createApp(App); | const app = createApp(App); | ||||||
| 
 | 
 | ||||||
|  | @ -24,6 +25,18 @@ const vuetify = createVuetify({ | ||||||
|     components, |     components, | ||||||
|     directives, |     directives, | ||||||
| }); | }); | ||||||
|  | 
 | ||||||
|  | const queryClient = new QueryClient({ | ||||||
|  |     defaultOptions: { | ||||||
|  |         queries: { | ||||||
|  |             retry: 1, | ||||||
|  |             refetchOnWindowFocus: false, | ||||||
|  |         }, | ||||||
|  |     }, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
| app.use(vuetify); | app.use(vuetify); | ||||||
| app.use(i18n); | app.use(i18n); | ||||||
|  | app.use(VueQueryPlugin, { queryClient }); | ||||||
|  | 
 | ||||||
| app.mount("#app"); | app.mount("#app"); | ||||||
|  |  | ||||||
							
								
								
									
										25
									
								
								frontend/src/queries/themes.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								frontend/src/queries/themes.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | ||||||
|  | import { useQuery } from '@tanstack/vue-query'; | ||||||
|  | import { getThemeController } from '@/controllers/controllers'; | ||||||
|  | import {type MaybeRefOrGetter, toValue} from "vue"; | ||||||
|  | 
 | ||||||
|  | const themeController = getThemeController(); | ||||||
|  | 
 | ||||||
|  | export const useThemeQuery = (language: MaybeRefOrGetter<string>) => { | ||||||
|  |     return useQuery({ | ||||||
|  |         queryKey: ['themes', language], | ||||||
|  |         queryFn: () => { | ||||||
|  |             const lang = toValue(language); | ||||||
|  |             return themeController.getAll(lang); | ||||||
|  |         }, | ||||||
|  |         enabled: () => !!toValue(language), | ||||||
|  |     }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const useThemeHruidsQuery = (themeKey: string | null) => { | ||||||
|  |     return useQuery({ | ||||||
|  |         queryKey: ['theme-hruids', themeKey], | ||||||
|  |         queryFn: () => themeController.getHruidsByKey(themeKey!), | ||||||
|  |         enabled: !!themeKey, | ||||||
|  |     }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
							
								
								
									
										111
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										111
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							|  | @ -91,6 +91,8 @@ | ||||||
|             "name": "dwengo-1-frontend", |             "name": "dwengo-1-frontend", | ||||||
|             "version": "0.1.1", |             "version": "0.1.1", | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
|  |                 "@tanstack/react-query": "^5.69.0", | ||||||
|  |                 "@tanstack/vue-query": "^5.69.0", | ||||||
|                 "axios": "^1.8.2", |                 "axios": "^1.8.2", | ||||||
|                 "oidc-client-ts": "^3.1.0", |                 "oidc-client-ts": "^3.1.0", | ||||||
|                 "vue": "^3.5.13", |                 "vue": "^3.5.13", | ||||||
|  | @ -2532,6 +2534,99 @@ | ||||||
|                 "url": "https://github.com/sponsors/sindresorhus" |                 "url": "https://github.com/sponsors/sindresorhus" | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|  |         "node_modules/@tanstack/match-sorter-utils": { | ||||||
|  |             "version": "8.19.4", | ||||||
|  |             "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.19.4.tgz", | ||||||
|  |             "integrity": "sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==", | ||||||
|  |             "license": "MIT", | ||||||
|  |             "dependencies": { | ||||||
|  |                 "remove-accents": "0.5.0" | ||||||
|  |             }, | ||||||
|  |             "engines": { | ||||||
|  |                 "node": ">=12" | ||||||
|  |             }, | ||||||
|  |             "funding": { | ||||||
|  |                 "type": "github", | ||||||
|  |                 "url": "https://github.com/sponsors/tannerlinsley" | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         "node_modules/@tanstack/query-core": { | ||||||
|  |             "version": "5.69.0", | ||||||
|  |             "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.69.0.tgz", | ||||||
|  |             "integrity": "sha512-Kn410jq6vs1P8Nm+ZsRj9H+U3C0kjuEkYLxbiCyn3MDEiYor1j2DGVULqAz62SLZtUZ/e9Xt6xMXiJ3NJ65WyQ==", | ||||||
|  |             "license": "MIT", | ||||||
|  |             "funding": { | ||||||
|  |                 "type": "github", | ||||||
|  |                 "url": "https://github.com/sponsors/tannerlinsley" | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         "node_modules/@tanstack/react-query": { | ||||||
|  |             "version": "5.69.0", | ||||||
|  |             "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.69.0.tgz", | ||||||
|  |             "integrity": "sha512-Ift3IUNQqTcaFa1AiIQ7WCb/PPy8aexZdq9pZWLXhfLcLxH0+PZqJ2xFImxCpdDZrFRZhLJrh76geevS5xjRhA==", | ||||||
|  |             "license": "MIT", | ||||||
|  |             "dependencies": { | ||||||
|  |                 "@tanstack/query-core": "5.69.0" | ||||||
|  |             }, | ||||||
|  |             "funding": { | ||||||
|  |                 "type": "github", | ||||||
|  |                 "url": "https://github.com/sponsors/tannerlinsley" | ||||||
|  |             }, | ||||||
|  |             "peerDependencies": { | ||||||
|  |                 "react": "^18 || ^19" | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         "node_modules/@tanstack/vue-query": { | ||||||
|  |             "version": "5.69.0", | ||||||
|  |             "resolved": "https://registry.npmjs.org/@tanstack/vue-query/-/vue-query-5.69.0.tgz", | ||||||
|  |             "integrity": "sha512-JZecDd0b+hZChqV5O1wXBfKoxlEj3aGvRj7upuqWei+oGrT+ERuOU4uQn7/DDVA5TouIt88G3oMFBjE2wKO/6A==", | ||||||
|  |             "license": "MIT", | ||||||
|  |             "dependencies": { | ||||||
|  |                 "@tanstack/match-sorter-utils": "^8.19.4", | ||||||
|  |                 "@tanstack/query-core": "5.69.0", | ||||||
|  |                 "@vue/devtools-api": "^6.6.3", | ||||||
|  |                 "vue-demi": "^0.14.10" | ||||||
|  |             }, | ||||||
|  |             "funding": { | ||||||
|  |                 "type": "github", | ||||||
|  |                 "url": "https://github.com/sponsors/tannerlinsley" | ||||||
|  |             }, | ||||||
|  |             "peerDependencies": { | ||||||
|  |                 "@vue/composition-api": "^1.1.2", | ||||||
|  |                 "vue": "^2.6.0 || ^3.3.0" | ||||||
|  |             }, | ||||||
|  |             "peerDependenciesMeta": { | ||||||
|  |                 "@vue/composition-api": { | ||||||
|  |                     "optional": true | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         "node_modules/@tanstack/vue-query/node_modules/vue-demi": { | ||||||
|  |             "version": "0.14.10", | ||||||
|  |             "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", | ||||||
|  |             "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", | ||||||
|  |             "hasInstallScript": true, | ||||||
|  |             "license": "MIT", | ||||||
|  |             "bin": { | ||||||
|  |                 "vue-demi-fix": "bin/vue-demi-fix.js", | ||||||
|  |                 "vue-demi-switch": "bin/vue-demi-switch.js" | ||||||
|  |             }, | ||||||
|  |             "engines": { | ||||||
|  |                 "node": ">=12" | ||||||
|  |             }, | ||||||
|  |             "funding": { | ||||||
|  |                 "url": "https://github.com/sponsors/antfu" | ||||||
|  |             }, | ||||||
|  |             "peerDependencies": { | ||||||
|  |                 "@vue/composition-api": "^1.0.0-rc.1", | ||||||
|  |                 "vue": "^3.0.0-0 || ^2.6.0" | ||||||
|  |             }, | ||||||
|  |             "peerDependenciesMeta": { | ||||||
|  |                 "@vue/composition-api": { | ||||||
|  |                     "optional": true | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|         "node_modules/@tootallnate/once": { |         "node_modules/@tootallnate/once": { | ||||||
|             "version": "1.1.2", |             "version": "1.1.2", | ||||||
|             "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", |             "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", | ||||||
|  | @ -9046,6 +9141,16 @@ | ||||||
|                 "node": ">=0.10.0" |                 "node": ">=0.10.0" | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|  |         "node_modules/react": { | ||||||
|  |             "version": "19.0.0", | ||||||
|  |             "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", | ||||||
|  |             "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", | ||||||
|  |             "license": "MIT", | ||||||
|  |             "peer": true, | ||||||
|  |             "engines": { | ||||||
|  |                 "node": ">=0.10.0" | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|         "node_modules/read-package-json-fast": { |         "node_modules/read-package-json-fast": { | ||||||
|             "version": "4.0.0", |             "version": "4.0.0", | ||||||
|             "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-4.0.0.tgz", |             "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-4.0.0.tgz", | ||||||
|  | @ -9092,6 +9197,12 @@ | ||||||
|             "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", |             "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", | ||||||
|             "license": "Apache-2.0" |             "license": "Apache-2.0" | ||||||
|         }, |         }, | ||||||
|  |         "node_modules/remove-accents": { | ||||||
|  |             "version": "0.5.0", | ||||||
|  |             "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", | ||||||
|  |             "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==", | ||||||
|  |             "license": "MIT" | ||||||
|  |         }, | ||||||
|         "node_modules/require-directory": { |         "node_modules/require-directory": { | ||||||
|             "version": "2.1.1", |             "version": "2.1.1", | ||||||
|             "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", |             "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Gabriellvl
						Gabriellvl