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) { | ||||
|     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); | ||||
| 
 | ||||
|     if (theme) { | ||||
|  |  | |||
|  | @ -16,12 +16,14 @@ | |||
|         "test:e2e": "playwright test" | ||||
|     }, | ||||
|     "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-i18n": "^11.1.2", | ||||
|         "vue-router": "^4.5.0", | ||||
|         "vuetify": "^3.7.12", | ||||
|         "oidc-client-ts": "^3.1.0", | ||||
|         "axios": "^1.8.2" | ||||
|         "vuetify": "^3.7.12" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "@playwright/test": "^1.50.1", | ||||
|  |  | |||
|  | @ -1,76 +1,52 @@ | |||
| <script setup lang="ts"> | ||||
| import ThemeCard from "@/components/ThemeCard.vue"; | ||||
|     import { ref, onMounted, watch } from "vue"; | ||||
| import { ref, watchEffect, computed } from "vue"; | ||||
| import { useI18n } from "vue-i18n"; | ||||
| import { AGE_TO_THEMES, THEMESITEMS } from "@/utils/constants.ts"; | ||||
|     import {getAllThemes} from "@/controllers/themes.ts"; | ||||
|     import {getThemeController} from "@/controllers/controllers.ts"; | ||||
| import { useThemeQuery } from "@/queries/themes.ts"; | ||||
| 
 | ||||
|     // Receive the selectedTheme and selectedAge from the parent component | ||||
| const props = defineProps({ | ||||
|         selectedTheme: { | ||||
|             type: String, | ||||
|             required: true | ||||
|         }, | ||||
|         selectedAge: { | ||||
|             type: String, | ||||
|             required: true | ||||
|         } | ||||
|     selectedTheme: { type: String, required: true }, | ||||
|     selectedAge: { type: String, required: true } | ||||
| }); | ||||
| 
 | ||||
|     const themeController = getThemeController(); | ||||
| 
 | ||||
| const { locale } = useI18n(); | ||||
| const language = computed(() => locale.value); | ||||
| 
 | ||||
|     const allCards = ref<Array<{ key: string; title: string; description: string; image: string }>>([]); | ||||
|     const cards = ref<Array<{ key: string; title: string; description: string; image: string }>>([]); | ||||
| const { data: allThemes, isLoading, error } = useThemeQuery(language); | ||||
| 
 | ||||
|     // Fetch all themes based on the current language | ||||
|     async function fetchThemes() { | ||||
|         try { | ||||
|             // Get the current selected language | ||||
|             const language = locale.value; | ||||
| const allCards = ref([]); | ||||
| const cards = ref([]); | ||||
| 
 | ||||
|             // Update the cards value with the fetched themes | ||||
|             allCards.value = await themeController.getAll(language); | ||||
|             cards.value = allCards.value; | ||||
|         } catch (error) { | ||||
|             console.error("Error fetching themes:", error); | ||||
|         } | ||||
|     } | ||||
| watchEffect(() => { | ||||
|     const themes = allThemes.value ?? []; | ||||
|     allCards.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)); | ||||
|     if (props.selectedTheme) { | ||||
|         cards.value = themes.filter((theme) => | ||||
|             THEMESITEMS[props.selectedTheme]?.includes(theme.key) && | ||||
|             AGE_TO_THEMES[props.selectedAge]?.includes(theme.key) | ||||
|         ); | ||||
|     } else { | ||||
|             cards.value = allCards.value; | ||||
|         cards.value = themes; | ||||
|     } | ||||
| }); | ||||
| 
 | ||||
|     // 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> | ||||
| 
 | ||||
| 
 | ||||
| <template> | ||||
|     <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-for="card in cards" | ||||
|                 :key="card.key" | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ import i18n from "./i18n/i18n.ts"; | |||
| // Components
 | ||||
| import App from "./App.vue"; | ||||
| import router from "./router"; | ||||
| import { VueQueryPlugin, QueryClient } from '@tanstack/vue-query'; | ||||
| 
 | ||||
| const app = createApp(App); | ||||
| 
 | ||||
|  | @ -24,6 +25,18 @@ const vuetify = createVuetify({ | |||
|     components, | ||||
|     directives, | ||||
| }); | ||||
| 
 | ||||
| const queryClient = new QueryClient({ | ||||
|     defaultOptions: { | ||||
|         queries: { | ||||
|             retry: 1, | ||||
|             refetchOnWindowFocus: false, | ||||
|         }, | ||||
|     }, | ||||
| }); | ||||
| 
 | ||||
| app.use(vuetify); | ||||
| app.use(i18n); | ||||
| app.use(VueQueryPlugin, { queryClient }); | ||||
| 
 | ||||
| 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", | ||||
|             "version": "0.1.1", | ||||
|             "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", | ||||
|  | @ -2532,6 +2534,99 @@ | |||
|                 "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": { | ||||
|             "version": "1.1.2", | ||||
|             "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", | ||||
|  | @ -9046,6 +9141,16 @@ | |||
|                 "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": { | ||||
|             "version": "4.0.0", | ||||
|             "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==", | ||||
|             "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": { | ||||
|             "version": "2.1.1", | ||||
|             "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", | ||||
|  |  | |||
		Reference in a new issue
	
	 Gabriellvl
						Gabriellvl