allow image file upload for wishlist

Signed-off-by: Benny Samir Hierl <bennysamir@posteo.de>
This commit is contained in:
Benny Samir Hierl 2022-02-15 23:45:04 +01:00
parent 37817098ae
commit 77d9916517
5 changed files with 156 additions and 22 deletions

View file

@ -0,0 +1,106 @@
<template>
<div class="relative mb-8">
<label class="mb-1 block w-full" :for="name">{{ label }}</label>
<div
class="flex h-24 w-full flex-row items-center space-x-10 rounded-md border-2 border-solid border-stone-300 bg-transparent px-2 outline-none dark:border-stone-700"
:class="{
'border-rose-500': !!errorMessage,
'border-dotted bg-stone-200/20': showDropzone,
}"
@drop.prevent="handleDrop"
@dragover.prevent="showDropzone = true"
@dragleave.prevent="showDropzone = false"
>
<div class="h-20 w-20">
<ImagePreview :src="value" class="h-full w-full" />
</div>
<div>
<i18n-t
keypath="components.wishlist-header.main.form.image-file.text-dropzone"
tag="p"
for="components.wishlist-header.main.form.image-file.text-dropzone-link"
>
<button
class="cursor-pointer text-cyan-600"
@click.prevent="fileInput.click()"
>
{{
t(
'components.wishlist-header.main.form.image-file.text-dropzone-link'
)
}}
</button>
</i18n-t>
<input
ref="fileInput"
class="hidden"
:name="name"
:id="name"
type="file"
@change="handleChange"
/>
</div>
</div>
<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'
import { ImagePreview } from './'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
type FileEventTarget = EventTarget & { files: FileList }
const fileInput = ref()
const showDropzone = ref(false)
const { t } = useI18n()
const props = defineProps({
accept: {
type: String,
default: 'image/*',
},
value: {
type: String,
default: '',
},
name: {
type: String,
required: true,
},
label: {
type: String,
required: true,
},
})
const { value, errorMessage } = useField(props.name, undefined, {})
const convertBase64 = (file: File): Promise<string> => {
return new Promise((resolve, reject) => {
const fileReader = new FileReader()
fileReader.readAsDataURL(file)
fileReader.onload = () => {
resolve(fileReader.result as string)
}
fileReader.onerror = (error) => {
reject(error)
}
})
}
const handleChange = async (event: any) => {
const file = (event.target as FileEventTarget).files[0]
value.value = await convertBase64(file)
}
const handleDrop = async (event: any) => {
let droppedFiles = event.dataTransfer.files
if (!droppedFiles) return
value.value = await convertBase64(droppedFiles[0])
}
</script>

View file

@ -43,6 +43,10 @@
:value="modelValue.imageSrc"
:label="t('components.wishlist-header.main.form.image-src.label')"
/>
<InputFile
name="imageFile"
:label="t('components.wishlist-header.main.form.image-file.label')"
/>
<InputText
name="slugUrlText"
type="text"
@ -72,6 +76,7 @@ import {
ButtonBase,
ImageTile,
InputText,
InputFile,
InputCheckbox,
InputTextArea,
} from '@/components'
@ -93,27 +98,39 @@ const { update } = useWishlistStore()
const { t } = useI18n()
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 schema = object().shape(
{
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().when('imageFile', {
is: (imageFile: string) => !imageFile || imageFile.length === 0,
then: string().required(
t('components.wishlist-header.main.form.image-src.error-requried')
),
}),
imageFile: string().when('imageSrc', {
is: (imageSrc: string) => !imageSrc || imageSrc.length === 0,
then: string().required(
t('components.wishlist-header.main.form.image-file.error-requried')
),
}),
},
//@ts-expect-error ...
['imageSrc', 'imageFile']
)
const onSubmit = async (values: any): Promise<void> => {
try {
values.imageSrc = values.imageFile || values.imageSrc
await update(values)
toast.success(t('common.wishlist.saved.text'))
router.push(`/${values.slugUrlText}`)

View file

@ -4,6 +4,7 @@ export { default as ImagePreview } from './ImagePreview.vue'
export { default as ImageTile } from './ImageTile.vue'
export { default as InputCheckbox } from './InputCheckbox.vue'
export { default as InputText } from './InputText.vue'
export { default as InputFile } from './InputFile.vue'
export { default as InputTextArea } from './InputTextArea.vue'
export { default as Modal } from './Modal.vue'
export { default as WishlistHeader } from './WishlistHeader.vue'

View file

@ -98,8 +98,13 @@
},
"image-src": {
"label": "Bild-URL",
"error-requried": "Bild-URL wird benötigt",
"error-url": "Bild-URL muss eine gültige URL sein"
"error-requried": "Bild-URL wird benötigt"
},
"image-file": {
"label": "Bild-Datei",
"text-dropzone-link": "hier",
"text-dropzone": "Ziehen Sie ein beliebiges Bild hierher oder klicken Sie {0}, um den Dialog zu öffnen.",
"error-requried": "Bild-Datei wird benötigt"
},
"submit": {
"text": "Speichern"

View file

@ -96,8 +96,13 @@
},
"image-src": {
"label": "Image-URL",
"error-requried": "Image-URL is required",
"error-url": "Image-URL has to be a valid url"
"error-requried": "Image-URL is required"
},
"image-file": {
"label": "Image-File",
"text-dropzone-link": "click here",
"text-dropzone": "drag and drop any image here or {0} to open dialog.",
"error-requried": "Image-File is required"
},
"submit": {
"text": "Save"