diff --git a/README.md b/README.md index b66c76f..38b4f1a 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # Nuxt 3 Simple Starter - A simple starter for Nuxt 3 -Nuxt 3 frontend template for third-party API integrations. - -> "Everything can work!" - BigDaddy +Nuxt 3 frontend template for third-party API integrations based on JSON-LD. ## Features @@ -13,7 +11,7 @@ Nuxt 3 frontend template for third-party API integrations. - [ ] 🍍 [State & Store Management (Pinia)](https://pinia.vuejs.org/) - [ ] 🍍 [Subscribing to the state](https://pinia.vuejs.org/core-concepts/state.html#Subscribing-to-the-state) - [x] 🍀 [Vue Composition Collection (Vueuse)](https://vueuse.org/) -- [ ] 🆎 [Internationalization (i18n)](https://v8.i18n.nuxtjs.org/) +- [x] 🆎 [Internationalization (i18n)](https://v8.i18n.nuxtjs.org/) ## Setup diff --git a/nuxt.config.ts b/nuxt.config.ts index 2733bbc..cc278ec 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -7,6 +7,7 @@ export default defineNuxtConfig({ "@nuxtjs/eslint-module", "@vueuse/nuxt", "@nuxtjs/i18n", + "@pinia/nuxt", ], i18n: { diff --git a/package.json b/package.json index 349b90d..eb707de 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "postinstall": "nuxt prepare" }, "dependencies": { + "@pinia/nuxt": "^0.5.1", "nuxt": "^3.11.1", "vue": "^3.4.21", "vue-router": "^4.3.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d1891fc..a1257cc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,9 @@ settings: excludeLinksFromLockfile: false dependencies: + '@pinia/nuxt': + specifier: ^0.5.1 + version: 0.5.1(rollup@4.13.2)(typescript@5.3.3)(vue@3.4.21) nuxt: specifier: ^3.11.1 version: 3.11.1(@unocss/reset@0.58.8)(eslint@8.57.0)(floating-vue@5.2.2)(rollup@4.13.2)(typescript@5.3.3)(unocss@0.58.8)(vite@5.2.7) @@ -1552,6 +1555,19 @@ packages: '@parcel/watcher-win32-ia32': 2.4.1 '@parcel/watcher-win32-x64': 2.4.1 + /@pinia/nuxt@0.5.1(rollup@4.13.2)(typescript@5.3.3)(vue@3.4.21): + resolution: {integrity: sha512-6wT6TqY81n+7/x3Yhf0yfaJVKkZU42AGqOR0T3+UvChcaOJhSma7OWPN64v+ptYlznat+fS1VTwNAcbi2lzHnw==} + dependencies: + '@nuxt/kit': 3.11.1(rollup@4.13.2) + pinia: 2.1.7(typescript@5.3.3)(vue@3.4.21) + transitivePeerDependencies: + - '@vue/composition-api' + - rollup + - supports-color + - typescript + - vue + dev: false + /@pkgjs/parseargs@0.11.0: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -3104,7 +3120,7 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.23.0 - caniuse-lite: 1.0.30001600 + caniuse-lite: 1.0.30001603 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.0.0 @@ -3169,7 +3185,7 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001600 + caniuse-lite: 1.0.30001603 electron-to-chromium: 1.4.722 node-releases: 2.0.14 update-browserslist-db: 1.0.13(browserslist@4.23.0) @@ -3275,12 +3291,12 @@ packages: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} dependencies: browserslist: 4.23.0 - caniuse-lite: 1.0.30001600 + caniuse-lite: 1.0.30001603 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 - /caniuse-lite@1.0.30001600: - resolution: {integrity: sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==} + /caniuse-lite@1.0.30001603: + resolution: {integrity: sha512-iL2iSS0eDILMb9n5yKQoTBim9jMZ0Yrk8g0N9K7UzYyWnfIKzXBZD5ngpM37ZcL/cv0Mli8XtVMRYMQAfFpi5Q==} /chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} @@ -6727,6 +6743,24 @@ packages: engines: {node: '>=0.10.0'} dev: true + /pinia@2.1.7(typescript@5.3.3)(vue@3.4.21): + resolution: {integrity: sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==} + peerDependencies: + '@vue/composition-api': ^1.4.0 + typescript: '>=4.4.4' + vue: ^2.6.14 || ^3.3.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + typescript: + optional: true + dependencies: + '@vue/devtools-api': 6.6.1 + typescript: 5.3.3 + vue: 3.4.21(typescript@5.3.3) + vue-demi: 0.14.7(vue@3.4.21) + dev: false + /pirates@4.0.6: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} diff --git a/stores/post.ts b/stores/post.ts new file mode 100644 index 0000000..a563cd9 --- /dev/null +++ b/stores/post.ts @@ -0,0 +1,16 @@ +import type { Post } from "~/types/post"; + +export const usePostStore = defineStore("post", () => { + const apiOperations = createApiOperation("/api/posts"); + + // Add your custom store properties here + // https://pinia.vuejs.org/core-concepts/#Setup-Stores + + return { + ...apiOperations, + }; +}); + +if (import.meta.hot) { + import.meta.hot.accept(acceptHMRUpdate(usePostStore, import.meta.hot)); +} diff --git a/types/blameable.ts b/types/blameable.ts new file mode 100644 index 0000000..b8b7784 --- /dev/null +++ b/types/blameable.ts @@ -0,0 +1,5 @@ +export interface Blameable { + createdBy?: string; + updatedBy?: string; + deletedBy?: string; +} diff --git a/types/collection.ts b/types/collection.ts new file mode 100644 index 0000000..a5c5b11 --- /dev/null +++ b/types/collection.ts @@ -0,0 +1,11 @@ +import type { View } from "~/types/view"; + +export interface PagedCollection { + "@context"?: string; + "@type"?: string; + "@id"?: string; + "hydra:member": T[]; + "hydra:search"?: object; + "hydra:totalItems"?: number; + "hydra:view": View; +} diff --git a/types/error.ts b/types/error.ts new file mode 100644 index 0000000..82f4ba6 --- /dev/null +++ b/types/error.ts @@ -0,0 +1,17 @@ +interface HydraError { + "@context"?: string; + "@type"?: string; + "hydra:title": string; + "hydra:description": string; + trace?: any; +} + +interface ValidatorError { + title?: string; + detail?: string; +} + +export interface ErrorResponse extends HydraError, ValidatorError { + code?: string; + message?: string; +} diff --git a/types/item.ts b/types/item.ts new file mode 100644 index 0000000..81894a6 --- /dev/null +++ b/types/item.ts @@ -0,0 +1,5 @@ +export interface Item { + "@context"?: string; + "@type"?: string; + "@id"?: string; +} diff --git a/types/post.ts b/types/post.ts new file mode 100644 index 0000000..6ee5b9c --- /dev/null +++ b/types/post.ts @@ -0,0 +1,9 @@ +import type { Item } from "~/types/item"; +import type { Timestampable } from "~/types/timestampable"; +import type { Blameable } from "~/types/blameable"; + +export interface Post extends Item, Timestampable, Blameable { + id?: number; + title?: string; + content?: string; +} diff --git a/types/timestampable.ts b/types/timestampable.ts new file mode 100644 index 0000000..dd89c5e --- /dev/null +++ b/types/timestampable.ts @@ -0,0 +1,4 @@ +export interface Timestampable { + createdAt?: string; + updatedAt?: string; +} diff --git a/types/view.ts b/types/view.ts new file mode 100644 index 0000000..ced49a2 --- /dev/null +++ b/types/view.ts @@ -0,0 +1,7 @@ +export interface View { + "@id": string; + "hydra:first": string; + "hydra:last": string; + "hydra:next": string; + "hydra:previous": string; +} diff --git a/utils/create-api-operation.ts b/utils/create-api-operation.ts new file mode 100644 index 0000000..247c453 --- /dev/null +++ b/utils/create-api-operation.ts @@ -0,0 +1,53 @@ +import type { PagedCollection } from "~/types/collection"; + +export default function createApiOperation(endpoint: string) { + function serialize(body: T | object): Record { + return JSON.parse(JSON.stringify(body)); + } + + async function collection(params?: object) { + return await $fetch>(`${endpoint}`, { + params, + }); + } + + async function item(id: number) { + return await $fetch(`${endpoint}/${id}`); + } + + async function post(body: T | FormData) { + return await $fetch(`${endpoint}`, { + method: "POST", + body: serialize(body), + }); + } + + async function patch(body: T extends { id: number } ? T : any) { + return await $fetch(`${endpoint}/${body.id}`, { + method: "PATCH", + body: serialize(body), + }); + } + + async function put(body: T extends { id: number } ? T : any) { + return await $fetch(`${endpoint}/${body.id}`, { + method: "PUT", + body: serialize(body), + }); + } + + async function remove(id: number) { + return await $fetch(`${endpoint}/${id}`, { + method: "DELETE", + }); + } + + return { + collection, + item, + post, + patch, + put, + remove, + }; +}