v.0.1.0 pinia, albums-store

This commit is contained in:
tiijay
2026-09-19 11:24:27 +02:00
parent 0d1d1fb24d
commit 9d3da3361d
9 changed files with 162 additions and 58 deletions
+44 -7
View File
@@ -1,16 +1,17 @@
{ {
"name": "tmp-app-creation", "name": "tmp-app-creation",
"version": "0.0.0", "version": "0.0.6",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "tmp-app-creation", "name": "tmp-app-creation",
"version": "0.0.0", "version": "0.0.6",
"dependencies": { "dependencies": {
"@tailwindcss/vite": "^4.3.3", "@tailwindcss/vite": "^4.3.3",
"@types/axios": "^0.9.36", "@types/axios": "^0.9.36",
"axios": "^1.20.0", "axios": "^1.20.0",
"pinia": "^4.0.3",
"tailwindcss": "^4.3.3", "tailwindcss": "^4.3.3",
"vue": "^3.5.42" "vue": "^3.5.42"
}, },
@@ -1469,6 +1470,16 @@
"@vue/shared": "3.5.42" "@vue/shared": "3.5.42"
} }
}, },
"node_modules/@vue/devtools-api": {
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.2.1.tgz",
"integrity": "sha512-6u4vXBlIBAC1wMplIZgpyPn7uh/s4Bf6F5bMzvLv+EdJ0aHs/+4B7Ygv864EStQSjRbsRzTko/kUG1A1IejQ3A==",
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/devtools-kit": "^8.2.1"
}
},
"node_modules/@vue/devtools-core": { "node_modules/@vue/devtools-core": {
"version": "8.2.1", "version": "8.2.1",
"resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-8.2.1.tgz", "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-8.2.1.tgz",
@@ -1487,7 +1498,6 @@
"version": "8.2.1", "version": "8.2.1",
"resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.2.1.tgz", "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.2.1.tgz",
"integrity": "sha512-FIGIuq3AWReEpbAHY/cRGeHDfI0qOb8OCQ3YjbEAX04uaxIDbGc9rhkbVcG7rnfHPXE3RsU5KrWOu9V/okd8AQ==", "integrity": "sha512-FIGIuq3AWReEpbAHY/cRGeHDfI0qOb8OCQ3YjbEAX04uaxIDbGc9rhkbVcG7rnfHPXE3RsU5KrWOu9V/okd8AQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/devtools-shared": "^8.2.1", "@vue/devtools-shared": "^8.2.1",
@@ -1500,7 +1510,6 @@
"version": "8.2.1", "version": "8.2.1",
"resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.2.1.tgz", "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.2.1.tgz",
"integrity": "sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g==", "integrity": "sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@vue/language-core": { "node_modules/@vue/language-core": {
@@ -1663,7 +1672,6 @@
"version": "2.9.0", "version": "2.9.0",
"resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz",
"integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==",
"dev": true,
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"url": "https://github.com/sponsors/antfu" "url": "https://github.com/sponsors/antfu"
@@ -2195,7 +2203,6 @@
"version": "5.5.3", "version": "5.5.3",
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/https-proxy-agent": { "node_modules/https-proxy-agent": {
@@ -2702,6 +2709,12 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/nostics": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/nostics/-/nostics-1.2.0.tgz",
"integrity": "sha512-FGqEfhQjrvo1lL8KFifdTQiNwwQHJxC1jtYE1Rc54qF/jxONUNL+kC9gS1krX8Q65PgrQ5fCqH/I4NhWBvdSqg==",
"license": "MIT"
},
"node_modules/npm-normalize-package-bin": { "node_modules/npm-normalize-package-bin": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-6.0.0.tgz", "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-6.0.0.tgz",
@@ -2809,7 +2822,6 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz",
"integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/picocolors": { "node_modules/picocolors": {
@@ -2843,6 +2855,31 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/pinia": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-4.0.3.tgz",
"integrity": "sha512-XMQqpvjgG7LMqVhhFUzKLT4KEbsYbfZZ0CZU9PgdFm1O2VKBmIkykL8+SfNhOaT7sR0wT6BEgKT0YNDYWgmVRA==",
"license": "MIT",
"dependencies": {
"nostics": "^1.1.4"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"@vue/devtools-api": "^8.1.5",
"typescript": ">=5.6.0",
"vue": "^3.5.11"
},
"peerDependenciesMeta": {
"@vue/devtools-api": {
"optional": false
},
"typescript": {
"optional": true
}
}
},
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.5.28", "version": "8.5.28",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz",
+1
View File
@@ -14,6 +14,7 @@
"@tailwindcss/vite": "^4.3.3", "@tailwindcss/vite": "^4.3.3",
"@types/axios": "^0.9.36", "@types/axios": "^0.9.36",
"axios": "^1.20.0", "axios": "^1.20.0",
"pinia": "^4.0.3",
"tailwindcss": "^4.3.3", "tailwindcss": "^4.3.3",
"vue": "^3.5.42" "vue": "^3.5.42"
}, },
+4 -2
View File
@@ -1,12 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import FirstVueComponent from "./components/FirstVueComponent.vue"; import FirstVueComponent from "./components/FirstVueComponent.vue";
import SecondVueComponent from "./components/SecondVueComponent.vue"; import SecondVueComponent from "./components/SecondVueComponent.vue";
import FetchFromApi from "./components/FetchFromApi.vue";
import pkg from "../package.json"; // Works out-of-the-box in Vite import pkg from "../package.json"; // Works out-of-the-box in Vite
import pool_bg from "@/assets/images/swimming-pool.jpg"; import pool_bg from "@/assets/images/swimming-pool.jpg";
import type { Album } from "./types/album.ts"; import type { Album } from "./types/album.ts";
import AlbumsList from "./components/AlbumsList.vue";
import AlbumTitles from "./components/AlbumTitles.vue";
document.title = `${pkg.name} ${pkg.version}©2026`; document.title = `${pkg.name} ${pkg.version}©2026`;
@@ -25,7 +26,8 @@ const sample_album: Album = {
<div class="container"> <div class="container">
<FirstVueComponent class="component-box-fade" /> <FirstVueComponent class="component-box-fade" />
<SecondVueComponent :album="sample_album" /> <SecondVueComponent :album="sample_album" />
<FetchFromApi class="component-box-fade" /> <AlbumsList />
<AlbumTitles />
</div> </div>
</div> </div>
</template> </template>
+16
View File
@@ -0,0 +1,16 @@
<!-- src/components/AlbumTitles.vue -->
<script setup lang="ts">
import { storeToRefs } from "pinia";
import { useAlbumsStore } from "@/stores/albums";
const { titles, count } = storeToRefs(useAlbumsStore());
</script>
<template>
<div>
<p>{{ count }} albums loaded</p>
<ul>
<li v-for="t in titles" :key="t">{{ t }}</li>
</ul>
</div>
</template>
+28
View File
@@ -0,0 +1,28 @@
<!-- src/components/AlbumsList.vue -->
<script setup lang="ts">
import { onMounted } from "vue";
import { storeToRefs } from "pinia";
import { useAlbumsStore } from "@/stores/albums";
import AlbumCard from "@/components/AlbumCard.vue";
const store = useAlbumsStore();
// ⚠️ storeToRefs keeps reactivity for state + getters
const { albums, loading, error } = storeToRefs(store);
// actions can be destructured directly — they're bound
const { fetchAlbums } = store;
onMounted(() => fetchAlbums());
</script>
<template>
<div
class="m-2.5 w-1/3 rounded-2xl bg-linear-to-br from-emerald-200 to-emerald-500 border-2"
>
<div v-if="loading" class="p-3">Loading…</div>
<div v-else-if="error" class="p-3 text-red-700">Failed to load albums.</div>
<div v-else class="p-1.5 flex flex-wrap gap-3">
<AlbumCard v-for="a in albums" :key="a.id" :album="a" />
</div>
</div>
</template>
-47
View File
@@ -1,47 +0,0 @@
<script setup lang="ts">
import { onMounted, ref } from "vue";
import axios from "axios";
import type { Album } from "@/types/album";
const album_titles = ref<string[]>([]);
onMounted(async (): Promise<void> => {
try {
// Axios automatically parses the JSON response into 'response.data'
const albums_response = await axios.get<Album[]>(
"https://jsonplaceholder.typicode.com/albums",
);
console.log("albums_response:", albums_response.data);
album_titles.value = albums_response.data
.slice(0, 12)
.map((album) => album.title);
console.log("album_titles:", [...album_titles.value]);
} catch (error) {
console.error("Error fetching albums!", error);
}
});
</script>
<template>
<div class="card">
<h3>Albums List</h3>
<ul>
<li v-for="(album_title, index) in album_titles" :key="index">
{{ album_title }}
</li>
</ul>
</div>
</template>
<style scoped>
/* Optional styling for your card */
ul {
padding-left: 20px;
}
li {
margin-bottom: 4px;
}
</style>
+8 -1
View File
@@ -4,13 +4,20 @@ import { ref } from "vue";
const msg = ref<string>("Tom is building a vue template, oh yeah"); const msg = ref<string>("Tom is building a vue template, oh yeah");
const status = ref<boolean>(true); const status = ref<boolean>(true);
defineProps({
hide: {
type: Boolean,
default: false,
},
});
// Define function directly // Define function directly
const toggleStatus = (): void => { const toggleStatus = (): void => {
status.value = !status.value; status.value = !status.value;
}; };
</script> </script>
<template> <template v-if="hide">
<div class="card"> <div class="card">
<div class="tj">{{ msg }} {{ status }}</div> <div class="tj">{{ msg }} {{ status }}</div>
<p v-if="status">{{ msg }} {{ status }}</p> <p v-if="status">{{ msg }} {{ status }}</p>
+4 -1
View File
@@ -1,5 +1,8 @@
import { createPinia } from "pinia";
import { createApp } from "vue"; import { createApp } from "vue";
import App from "@/App.vue"; import App from "@/App.vue";
import "@/assets/styles/global.css"; import "@/assets/styles/global.css";
createApp(App).mount("#app"); const app = createApp(App);
app.use(createPinia());
app.mount("#app");
+57
View File
@@ -0,0 +1,57 @@
// src/stores/albums.ts
import { ref, computed } from "vue";
import { defineStore } from "pinia";
import axios from "axios";
import type { Album } from "@/types/album";
const API_URL = "https://jsonplaceholder.typicode.com/albums";
export const useAlbumsStore = defineStore("albums", () => {
// ---- state ----
const albums = ref<Album[]>([]);
const loading = ref(false);
const error = ref<unknown>(null);
let loaded = false; // module-internal guard (not reactive)
// ---- getters ----
const titles = computed(() => albums.value.map((a) => a.title));
const count = computed(() => albums.value.length);
const isLoading = computed(() => loading.value);
// ---- actions ----
async function fetchAlbums(force = false) {
if (loaded && !force) return;
if (loading.value) return;
loading.value = true;
error.value = null;
try {
const { data } = await axios.get<Album[]>(API_URL);
albums.value = data.slice(0, 12);
loaded = true;
console.log("albums loaded:", albums.value.length);
} catch (e) {
error.value = e;
console.error("Error fetching albums!", e);
} finally {
loading.value = false;
}
}
function reset() {
albums.value = [];
error.value = null;
loaded = false;
}
return {
albums,
loading,
error,
titles,
count,
isLoading,
fetchAlbums,
reset,
};
});