feat: tan stack theme query

This commit is contained in:
Gabriellvl 2025-03-22 00:59:37 +01:00
parent 47a522e443
commit 2b509774b8
6 changed files with 197 additions and 64 deletions

View file

@ -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) {

View file

@ -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",

View file

@ -1,76 +1,52 @@
<script setup lang="ts">
import ThemeCard from "@/components/ThemeCard.vue";
import { ref, onMounted, watch } 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 ThemeCard from "@/components/ThemeCard.vue";
import { ref, watchEffect, computed } from "vue";
import { useI18n } from "vue-i18n";
import { AGE_TO_THEMES, THEMESITEMS } from "@/utils/constants.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
}
});
const props = defineProps({
selectedTheme: { 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 cards = ref<Array<{ key: string; title: string; description: string; image: string }>>([]);
const allCards = ref([]);
const cards = ref([]);
// Fetch all themes based on the current language
async function fetchThemes() {
try {
// Get the current selected language
const language = locale.value;
watchEffect(() => {
const themes = allThemes.value ?? [];
allCards.value = themes;
// 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);
}
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 = 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>
<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"

View file

@ -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");

View 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
View file

@ -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",