#2 edit mode for wishlist

Signed-off-by: Benny Samir Hierl <bennysamir@posteo.de>
This commit is contained in:
Benny Samir Hierl 2022-02-13 02:21:00 +01:00
parent d0165ff296
commit b7ec02e5e8
12 changed files with 293 additions and 19 deletions

View file

@ -30,6 +30,7 @@ export const wishlistRequestSchema = {
required: ['title', 'imageSrc', 'slugUrlText'], required: ['title', 'imageSrc', 'slugUrlText'],
properties: { properties: {
title: { type: 'string' }, title: { type: 'string' },
public: { type: 'boolean' },
imageSrc: { type: 'string' }, imageSrc: { type: 'string' },
description: { type: 'string' }, description: { type: 'string' },
slugUrlText: { type: 'string' }, slugUrlText: { type: 'string' },
@ -40,6 +41,7 @@ export const wishlistResponseSchema = {
properties: { properties: {
id: { type: 'string' }, id: { type: 'string' },
title: { type: 'string' }, title: { type: 'string' },
public: { type: 'boolean' },
imageSrc: { type: 'string' }, imageSrc: { type: 'string' },
description: { type: 'string' }, description: { type: 'string' },
slugUrlText: { type: 'string' }, slugUrlText: { type: 'string' },

View file

@ -37,7 +37,7 @@ import { useAuth, useEditMode } from '@/composables/'
const { t } = useI18n() const { t } = useI18n()
const { isAuthenticated, setToken } = useAuth() const { isAuthenticated, setToken } = useAuth()
const { editMode, toggle } = useEditMode() const { state: editMode, toggle } = useEditMode()
const toggleDark = useToggle(useDark()) const toggleDark = useToggle(useDark())
</script> </script>

View file

@ -0,0 +1,37 @@
<template>
<div class="relative mb-8">
<label class="mb-1 block w-full" :for="name">{{ label }}</label>
<input
class="border-2 border-solid border-stone-300 bg-transparent px-2 outline-none dark:border-stone-700"
:name="name"
:id="name"
type="checkbox"
:checked="checked"
@change="handleChange((checked = !checked))"
v-bind="$attrs"
/>
</div>
</template>
<script setup lang="ts">
import { useField } from 'vee-validate'
const props = defineProps({
value: {
type: Boolean,
},
name: {
type: String,
required: true,
},
label: {
type: String,
required: true,
},
})
const { checked, handleChange } = useField(props.name, undefined, {
type: 'checkbox',
checkedValue: props.value,
initialValue: props.value,
})
</script>

View file

@ -0,0 +1,60 @@
<template>
<div class="relative mb-8">
<label class="mb-1 block w-full" :for="name">{{ label }}</label>
<textarea
class="w-full rounded-md border-2 border-solid border-stone-300 bg-transparent px-2 outline-none dark:border-stone-700"
:class="[heightClass, !!errorMessage ? 'border-rose-500' : '']"
: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: '',
},
heightClass: {
type: String,
default: '',
},
})
const {
value: inputValue,
errorMessage,
handleBlur,
handleChange,
} = useField(props.name, undefined, {
initialValue: props.value,
})
</script>

View file

@ -0,0 +1,106 @@
<template>
<div
class="flex flex-col items-center space-x-0 space-y-2 md:flex-row md:space-x-6 md:space-y-0"
v-if="modelValue !== undefined"
>
<ImageTile :image-src="modelValue.imageSrc" class="shrink-0"></ImageTile>
<div v-if="!editModeIsActive">
<h1 class="mb-2 text-center text-2xl font-bold md:text-left">
{{ modelValue.title }}
</h1>
<p class="text-lg">
{{ modelValue.description }}
</p>
</div>
<Form
v-else
@submit="onSubmit"
:validation-schema="schema"
v-slot="{ meta }"
class="w-full flex-col"
>
<InputText
name="title"
type="text"
:value="modelValue.title"
:label="t('components.wishlist-header.main.form.title.label')"
/>
<InputCheckbox
name="public"
:value="modelValue.public"
:label="t('components.wishlist-header.main.form.public.label')"
/>
<InputTextArea
name="description"
type="text"
:value="modelValue.description"
height-class="h-20"
:label="t('components.wishlist-header.main.form.description.label')"
/>
<InputText
name="imageSrc"
type="text"
:value="modelValue.imageSrc"
:label="t('components.wishlist-header.main.form.image-src.label')"
/>
<InputText
name="slugUrlText"
type="text"
:value="modelValue.slugUrlText"
:label="t('components.wishlist-header.main.form.slug-text.label')"
/>
<BaseButton class="h-12 w-full" mode="primary" :disabled="!meta.valid">{{
t('components.wishlist-header.main.form.submit.text')
}}</BaseButton>
</Form>
</div>
</template>
<script setup lang="ts">
import { Form } from 'vee-validate'
import { object, string, boolean } from 'yup'
import { useI18n } from 'vue-i18n'
import ImageTile from '@/components/ImageTile.vue'
import BaseButton from '@/components/BaseButton.vue'
import InputText from '@/components/InputText.vue'
import InputCheckbox from '@/components/InputCheckbox.vue'
import InputTextArea from '@/components/InputTextArea.vue'
import { useEditMode, useWishlistStore } from '@/composables'
import { Wishlist } from '@/types'
import { PropType } from 'vue'
const { isActive: editModeIsActive } = useEditMode()
defineProps({
modelValue: {
type: Object as PropType<Wishlist>,
requried: true,
},
})
const { t } = useI18n()
const { update } = useWishlistStore()
const schema = object({
title: string().required(
t('components.wishlist-header.main.form.title.error-requried')
),
public: boolean(),
description: string().max(
300,
t('components.wishlist-header.main.form.description.error-max')
),
slugUrlText: string().required(
t('components.wishlist-header.main.form.slug-text.error-requried')
),
imageSrc: string()
.required(
t('components.wishlist-header.main.form.image-src.error-requried')
)
.url(t('components.wishlist-header.main.form.image-src.error-url')),
})
const onSubmit = async (values: any): Promise<void> => {
console.log(values)
await update(values)
}
</script>

View file

@ -1,5 +1,7 @@
import { ref, readonly } from 'vue' import { useAuth } from './useAuth'
import { ref, readonly, computed } from 'vue'
const { isAuthenticated } = useAuth()
const state = ref(false) const state = ref(false)
const activate = (): void => { const activate = (): void => {
@ -14,9 +16,12 @@ const toggle = (): void => {
state.value = !state.value state.value = !state.value
} }
const isActive = computed(() => state.value && isAuthenticated.value)
export const useEditMode = () => { export const useEditMode = () => {
return { return {
editMode: readonly(state), state: readonly(state),
isActive,
activate, activate,
deactivate, deactivate,
toggle, toggle,

View file

@ -16,6 +16,25 @@ const fetch = async (slugText: string): Promise<void> => {
} }
} }
const update = async (updatedData: Wishlist): Promise<void> => {
const id = state.value?.id
const payload = {
...state.value,
...updatedData,
}
try {
const { data } = await client.put(`/wishlist/${id}`, payload)
state.value = {
...state.value,
...data,
}
} catch (e: any) {
if (e.isAxiosError && !(<CustomAxiosError>e.ignore)) {
throw e
}
}
}
const itemBought = async (item: WishlistItem): Promise<void> => { const itemBought = async (item: WishlistItem): Promise<void> => {
await client.post(`/wishlist/${item.wishlistId}/item/${item.id}/bought`) await client.post(`/wishlist/${item.wishlistId}/item/${item.id}/bought`)
item.bought = true item.bought = true
@ -25,6 +44,7 @@ export const useWishlistStore = () => {
return { return {
state, state,
fetch, fetch,
update,
itemBought, itemBought,
} }
} }

View file

@ -70,6 +70,35 @@
"text": "Gekauft" "text": "Gekauft"
} }
}, },
"wishlist-header": {
"main": {
"form": {
"title": {
"label": "Titel",
"error-requried": "Titel wird benötigt"
},
"public": {
"label": "Auf der Startseite anzeigen?"
},
"description": {
"label": "Beschreibung",
"error-max": "Die maximale Länge beträgt 300 Zeichen"
},
"slug-text": {
"label": "URL Slug-Text",
"error-requried": "URL Slug-Text wird benötigt"
},
"image-src": {
"label": "Bild-URL",
"error-requried": "Bild-URL wird benötigt",
"error-url": "Bild-URL muss eine gültige URL sein"
},
"submit": {
"text": "Speichern"
}
}
}
},
"header": { "header": {
"edit-mode": { "edit-mode": {
"text": "Bearbeitungsmodus" "text": "Bearbeitungsmodus"

View file

@ -70,6 +70,33 @@
"text": "Bought" "text": "Bought"
} }
}, },
"wishlist-header": {
"main": {
"form": {
"title": {
"label": "Title"
},
"public": {
"label": "Show on startpage?"
},
"description": {
"label": "Description",
"error-max": "The max. length is 300 chars."
},
"slug-text": {
"label": "URL Slug-Text"
},
"image-src": {
"label": "Image-URL",
"error-requried": "Image-URL is required",
"error-url": "Image-URL has to be a valid url"
},
"submit": {
"text": "Save"
}
}
}
},
"header": { "header": {
"edit-mode": { "edit-mode": {
"text": "Edit-Mode" "text": "Edit-Mode"

View file

@ -4,8 +4,8 @@ import { WishlistItem as WishlistItemType } from '@/types'
import { computed } from 'vue' import { computed } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { useWishlistStore, useModal } from '@/composables' import { useWishlistStore, useModal } from '@/composables'
import ImageTile from '@/components/ImageTile.vue'
import WishlistItem from '@/components/WishlistItem.vue' import WishlistItem from '@/components/WishlistItem.vue'
import WishlistHeader from '@/components/WishlistHeader.vue'
import { IconNoGift } from '../components/icons' import { IconNoGift } from '../components/icons'
const route = useRoute() const route = useRoute()
@ -37,19 +37,7 @@ const bought = async (item: WishlistItemType): Promise<void> => {
<template> <template>
<div v-if="state !== null" class="h-full"> <div v-if="state !== null" class="h-full">
<div <WishlistHeader v-model="state" />
class="relative flex flex-col items-center space-x-0 space-y-2 md:flex-row md:space-x-6 md:space-y-0"
>
<ImageTile :image-src="state.imageSrc" class="shrink-0"></ImageTile>
<div>
<h1 class="mb-2 text-center text-2xl font-bold md:text-left">
{{ state.title }}
</h1>
<p class="text-lg">
{{ state.description }}
</p>
</div>
</div>
<div <div
v-if="notBoughtItems && notBoughtItems.length > 0" v-if="notBoughtItems && notBoughtItems.length > 0"
class="flex flex-col space-y-14 py-10 md:space-y-8" class="flex flex-col space-y-14 py-10 md:space-y-8"

View file

@ -5,7 +5,7 @@ import { Form } from 'vee-validate'
import { object, string } from 'yup' import { object, string } from 'yup'
import { useAuth } from '@/composables' import { useAuth } from '@/composables'
import BaseButton from '@/components/BaseButton.vue' import BaseButton from '@/components/BaseButton.vue'
import TextInput from '@/components/TextInput.vue' import InputText from '@/components/InputText.vue'
const router = useRouter() const router = useRouter()
const { setToken } = useAuth() const { setToken } = useAuth()
@ -38,7 +38,7 @@ const onSubmit = (values: any): void => {
v-slot="{ meta }" v-slot="{ meta }"
class="w-full flex-col space-y-3" class="w-full flex-col space-y-3"
> >
<TextInput <InputText
name="api-key" name="api-key"
type="text" type="text"
:label="t('pages.login-view.main.form.api-key.placeholder')" :label="t('pages.login-view.main.form.api-key.placeholder')"