diff --git a/package-lock.json b/package-lock.json index 836a258..4e7532a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,9 +17,11 @@ "fastify-helmet": "^7.0.1", "fastify-static": "^4.5.0", "open-graph-scraper": "^4.11.0", + "vee-validate": "^4.5.8", "vue": "^3.2.27", "vue-i18n": "^9.2.0-beta.30", - "vue-router": "^4.0.12" + "vue-router": "^4.0.12", + "yup": "^0.32.11" }, "devDependencies": { "@rushstack/eslint-patch": "^1.1.0", @@ -158,6 +160,17 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/runtime": { + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", + "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", + "dependencies": { + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/types": { "version": "7.16.8", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", @@ -534,6 +547,11 @@ "@types/node": "*" } }, + "node_modules/@types/lodash": { + "version": "4.14.178", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", + "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==" + }, "node_modules/@types/node": { "version": "16.11.21", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.21.tgz", @@ -4473,8 +4491,12 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -4640,6 +4662,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/nanoclone": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz", + "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==" + }, "node_modules/nanoid": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", @@ -5459,6 +5486,11 @@ "asap": "~2.0.3" } }, + "node_modules/property-expr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz", + "integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -5743,6 +5775,11 @@ "node": ">=8.10.0" } }, + "node_modules/regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + }, "node_modules/regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", @@ -6436,6 +6473,11 @@ "integrity": "sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ=", "dev": true }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=" + }, "node_modules/touch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", @@ -6754,6 +6796,17 @@ "node": ">= 0.8" } }, + "node_modules/vee-validate": { + "version": "4.5.8", + "resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-4.5.8.tgz", + "integrity": "sha512-XZx2J93rlET49CdIIoxG9R6wQNoT3RxUUw9ar3QbIhczpzbtlm4BQ+TpCA9DoYHKFlApcXnE28WU7m4quoPsCA==", + "dependencies": { + "@vue/devtools-api": "^6.0.0-beta.15" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, "node_modules/vite": { "version": "2.7.13", "resolved": "https://registry.npmjs.org/vite/-/vite-2.7.13.tgz", @@ -7374,6 +7427,23 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/yup": { + "version": "0.32.11", + "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz", + "integrity": "sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==", + "dependencies": { + "@babel/runtime": "^7.15.4", + "@types/lodash": "^4.14.175", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "nanoclone": "^0.2.1", + "property-expr": "^2.0.4", + "toposort": "^2.0.2" + }, + "engines": { + "node": ">=10" + } } }, "dependencies": { @@ -7456,6 +7526,14 @@ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.10.tgz", "integrity": "sha512-Sm/S9Or6nN8uiFsQU1yodyDW3MWXQhFeqzMPM+t8MJjM+pLsnFVxFZzkpXKvUXh+Gz9cbMoYYs484+Jw/NTEFQ==" }, + "@babel/runtime": { + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", + "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, "@babel/types": { "version": "7.16.8", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", @@ -7765,6 +7843,11 @@ "@types/node": "*" } }, + "@types/lodash": { + "version": "4.14.178", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", + "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==" + }, "@types/node": { "version": "16.11.21", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.21.tgz", @@ -10675,8 +10758,12 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, "lodash.merge": { "version": "4.6.2", @@ -10799,6 +10886,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "nanoclone": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz", + "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==" + }, "nanoid": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", @@ -11400,6 +11492,11 @@ "asap": "~2.0.3" } }, + "property-expr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz", + "integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==" + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -11642,6 +11739,11 @@ "picomatch": "^2.2.1" } }, + "regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + }, "regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", @@ -12160,6 +12262,11 @@ "integrity": "sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ=", "dev": true }, + "toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=" + }, "touch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", @@ -12390,6 +12497,14 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, + "vee-validate": { + "version": "4.5.8", + "resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-4.5.8.tgz", + "integrity": "sha512-XZx2J93rlET49CdIIoxG9R6wQNoT3RxUUw9ar3QbIhczpzbtlm4BQ+TpCA9DoYHKFlApcXnE28WU7m4quoPsCA==", + "requires": { + "@vue/devtools-api": "^6.0.0-beta.15" + } + }, "vite": { "version": "2.7.13", "resolved": "https://registry.npmjs.org/vite/-/vite-2.7.13.tgz", @@ -12844,6 +12959,20 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + }, + "yup": { + "version": "0.32.11", + "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz", + "integrity": "sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==", + "requires": { + "@babel/runtime": "^7.15.4", + "@types/lodash": "^4.14.175", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "nanoclone": "^0.2.1", + "property-expr": "^2.0.4", + "toposort": "^2.0.2" + } } } } diff --git a/package.json b/package.json index e2d056d..570e28b 100644 --- a/package.json +++ b/package.json @@ -28,9 +28,11 @@ "fastify-helmet": "^7.0.1", "fastify-static": "^4.5.0", "open-graph-scraper": "^4.11.0", + "vee-validate": "^4.5.8", "vue": "^3.2.27", "vue-i18n": "^9.2.0-beta.30", - "vue-router": "^4.0.12" + "vue-router": "^4.0.12", + "yup": "^0.32.11" }, "devDependencies": { "@rushstack/eslint-patch": "^1.1.0", diff --git a/src/components/BaseButton.vue b/src/components/BaseButton.vue index 7dfc634..e7dbe49 100644 --- a/src/components/BaseButton.vue +++ b/src/components/BaseButton.vue @@ -1,11 +1,24 @@ diff --git a/src/components/TextInput.vue b/src/components/TextInput.vue new file mode 100644 index 0000000..a9a9808 --- /dev/null +++ b/src/components/TextInput.vue @@ -0,0 +1,56 @@ + + + {{ label }} + + + + {{ errorMessage }} + + + + + diff --git a/src/composables/useAuth.ts b/src/composables/useAuth.ts new file mode 100644 index 0000000..c0cee60 --- /dev/null +++ b/src/composables/useAuth.ts @@ -0,0 +1,15 @@ +import { readonly } from 'vue' +import { useStorage } from '@vueuse/core' + +const state = useStorage('auth-token', '') + +const setToken = (token: string): void => { + state.value = token +} + +export default () => { + return { + setToken, + token: readonly(state), + } +} diff --git a/src/composables/useAxios.ts b/src/composables/useAxios.ts index 1c872c2..b052340 100644 --- a/src/composables/useAxios.ts +++ b/src/composables/useAxios.ts @@ -1,10 +1,17 @@ -import axios, { AxiosInstance, AxiosRequestConfig } from 'axios' +import axios, { + AxiosError, + AxiosInstance, + AxiosRequestConfig, + AxiosResponse, +} from 'axios' import { apiConfig } from '@/config' import { ref } from 'vue' import router from '../router' +import useAuth from './useAuth' +const { token } = useAuth() const isLoading = ref(false) -const error = ref(null) +const error = ref(null) const config: AxiosRequestConfig = { baseURL: apiConfig.baseURL, @@ -13,12 +20,20 @@ const config: AxiosRequestConfig = { const client: AxiosInstance = axios.create(config) export const requestInterceptor = client.interceptors.request.use( - function (config) { + (config: AxiosRequestConfig): AxiosRequestConfig => { + if (!config) { + config = {} + } + if (!config.headers) { + config.headers = {} + } isLoading.value = true error.value = null + config.headers.Authorization = token.value ? `Bearer ${token.value}` : '' + return config }, - function (err) { + (err: AxiosError): Promise => { isLoading.value = false error.value = err return Promise.reject(err) @@ -26,11 +41,11 @@ export const requestInterceptor = client.interceptors.request.use( ) export const responseInterceptor = client.interceptors.response.use( - function (response) { + (response: AxiosResponse): AxiosResponse => { isLoading.value = false return response }, - function (err) { + (err: AxiosError): Promise => { isLoading.value = false if (err.response?.status === 404) { router.push({ name: 'notFound' }) diff --git a/src/config/locales/de-DE.json b/src/config/locales/de-DE.json index c827653..01a47c0 100644 --- a/src/config/locales/de-DE.json +++ b/src/config/locales/de-DE.json @@ -16,6 +16,19 @@ } }, "pages": { + "login-view": { + "main": { + "title": { + "text": "Anmelden" + }, + "form": { + "api-key": { + "placeholder": "API-Key", + "error-requried": "API-Key wird benötigt" + } + } + } + }, "detail-view": { "main": { "empty-list": { diff --git a/src/config/locales/en-US.json b/src/config/locales/en-US.json index 5a332f8..3445da4 100644 --- a/src/config/locales/en-US.json +++ b/src/config/locales/en-US.json @@ -16,6 +16,19 @@ } }, "pages": { + "login-view": { + "main": { + "title": { + "text": "Login" + }, + "form": { + "api-key": { + "placeholder": "API-Key", + "error-requried": "API-Key is required" + } + } + } + }, "detail-view": { "main": { "empty-list": { diff --git a/src/router/index.ts b/src/router/index.ts index ca65550..867b4c1 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,5 +1,6 @@ import { createRouter, createWebHistory } from 'vue-router' import HomeView from '@/views/HomeView.vue' +import LoginView from '@/views/LoginView.vue' import DetailView from '@/views/DetailView.vue' const router = createRouter({ @@ -10,6 +11,11 @@ const router = createRouter({ name: 'home', component: HomeView, }, + { + path: '/login', + name: 'login', + component: LoginView, + }, { path: '/:slug', name: 'detail', @@ -18,7 +24,7 @@ const router = createRouter({ { name: 'notFound', path: '/:pathMatch(.*)*', - component: () => import('@/views/NotFound.vue'), + component: () => import('@/views/NotFoundView.vue'), }, ], }) diff --git a/src/views/DetailView.vue b/src/views/DetailView.vue index 53bfc9a..78e270c 100644 --- a/src/views/DetailView.vue +++ b/src/views/DetailView.vue @@ -69,6 +69,7 @@ const bought = async (item: WishlistItemType): Promise => { class="flex flex-col flex-wrap items-center justify-center text-center text-xl text-gray-600/75 dark:text-white/70 sm:flex-row sm:space-x-2 sm:text-left" > + {{ t('pages.detail-view.main.empty-list.text') }} diff --git a/src/views/LoginView.vue b/src/views/LoginView.vue new file mode 100644 index 0000000..749c8fc --- /dev/null +++ b/src/views/LoginView.vue @@ -0,0 +1,56 @@ + + + + + + + {{ t('pages.login-view.main.title.text') }} + + + + Submit + + + + diff --git a/src/views/NotFound.vue b/src/views/NotFoundView.vue similarity index 99% rename from src/views/NotFound.vue rename to src/views/NotFoundView.vue index 82ef1fe..25f034b 100644 --- a/src/views/NotFound.vue +++ b/src/views/NotFoundView.vue @@ -1,3 +1,9 @@ + + {{ t('errors.not-found.text') }} -
+ {{ errorMessage }} +