#3 Login page added to set api key

Signed-off-by: Benny Samir Hierl <bennysamir@posteo.de>
This commit is contained in:
Benny Samir Hierl 2022-02-12 20:33:17 +01:00
parent a362b67ec6
commit c15b97d08a
12 changed files with 342 additions and 22 deletions

139
package-lock.json generated
View file

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

View file

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

View file

@ -1,11 +1,24 @@
<script lang="ts" setup>
defineProps<{
icon?: any
}>()
defineProps({
icon: {
type: Object,
default: null,
},
mode: {
type: String,
default: 'secondary',
},
})
</script>
<template>
<button
class="inline-flex w-fit flex-row items-center justify-center rounded-md border-2 border-stone-200 py-1 px-2 text-stone-500 hover:bg-stone-100 dark:border-stone-700 dark:text-white/70 dark:hover:bg-stone-700"
class="inline-flex w-fit flex-row items-center justify-center rounded-md border-2 py-1 px-2 disabled:cursor-not-allowed disabled:opacity-60"
:class="{
'border-0 bg-cyan-600 text-white disabled:bg-cyan-600 disabled:hover:bg-cyan-600 dark:bg-cyan-600 dark:hover:bg-cyan-600 dark:disabled:hover:bg-cyan-600':
mode === 'primary',
'border-stone-200 text-stone-500 hover:bg-stone-100 disabled:hover:bg-white dark:border-stone-700 dark:text-white/70 dark:hover:bg-stone-700 disabled:hover:dark:bg-stone-900':
mode === 'secondary',
}"
>
<component v-if="icon" :is="icon" class="mr-1 h-4 w-4" />
<span>

View file

@ -0,0 +1,56 @@
<template>
<div class="relative mb-8">
<label class="mb-1 block w-full" :for="name">{{ label }}</label>
<input
class="h-12 w-full rounded-md border-2 border-solid border-stone-300 bg-transparent px-2 outline-none dark:border-stone-700"
:class="{ 'border-rose-500': !!errorMessage }"
:name="name"
:id="name"
:type="type"
:value="inputValue"
:placeholder="placeholder"
@input="handleChange"
@blur="handleBlur"
v-bind="$attrs"
/>
<p class="absolute mt-2 text-sm text-rose-500" v-show="errorMessage">
{{ errorMessage }}
</p>
</div>
</template>
<script setup lang="ts">
import { useField } from 'vee-validate'
const props = defineProps({
type: {
type: String,
default: 'text',
},
value: {
type: String,
default: '',
},
name: {
type: String,
required: true,
},
label: {
type: String,
required: true,
},
placeholder: {
type: String,
default: '',
},
})
const {
value: inputValue,
errorMessage,
handleBlur,
handleChange,
} = useField(props.name, undefined, {
initialValue: props.value,
})
</script>

View file

@ -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),
}
}

View file

@ -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<any | null>(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<AxiosError> => {
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<AxiosError> => {
isLoading.value = false
if (err.response?.status === 404) {
router.push({ name: 'notFound' })

View file

@ -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": {

View file

@ -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": {

View file

@ -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'),
},
],
})

View file

@ -69,6 +69,7 @@ const bought = async (item: WishlistItemType): Promise<void> => {
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"
>
<IconNoGift class="h-10 w-10" />
<span>{{ t('pages.detail-view.main.empty-list.text') }}</span>
</div>
</div>

56
src/views/LoginView.vue Normal file
View file

@ -0,0 +1,56 @@
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { Form } from 'vee-validate'
import { object, string } from 'yup'
import useAuth from '@/composables/useAuth'
import BaseButton from '@/components/BaseButton.vue'
import TextInput from '@/components/TextInput.vue'
const router = useRouter()
const { setToken } = useAuth()
const { t } = useI18n()
const schema = object({
'api-key': string().required(
t('pages.login-view.main.form.api-key.error-requried')
),
})
const onSubmit = (values: any): void => {
setToken(values['api-key'])
router.push('/')
}
</script>
<template>
<div class="flex h-full">
<div
class="m-auto w-1/2 rounded-md border-2 border-stone-200 px-6 py-10 dark:border-stone-700"
>
<h1 class="text-semibold mb-8 text-center text-3xl">
{{ t('pages.login-view.main.title.text') }}
</h1>
<Form
@submit="onSubmit"
:validation-schema="schema"
v-slot="{ meta }"
class="w-full flex-col space-y-3"
>
<TextInput
name="api-key"
type="text"
:label="t('pages.login-view.main.form.api-key.placeholder')"
autocomplete="off"
/>
<BaseButton
class="h-12 w-full"
mode="primary"
:disabled="!meta.dirty || !meta.valid"
>Submit</BaseButton
>
</Form>
</div>
</div>
</template>

View file

@ -1,3 +1,9 @@
<script setup lang="ts">
import { IconCloudQuestion } from '@/components/icons'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
</script>
<template>
<div
class="mt-20 flex flex-col flex-wrap items-center justify-center text-center text-xl sm:flex-row sm:space-x-2 sm:text-left"
@ -6,8 +12,3 @@
<h1>{{ t('errors.not-found.text') }}</h1>
</div>
</template>
<script setup lang="ts">
import { IconCloudQuestion } from '@/components/icons'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
</script>