mirror of
https://github.com/ThisIsBenny/wishlist-app.git
synced 2025-04-19 23:37:41 +00:00
some refactoring
Signed-off-by: Benny Samir Hierl <bennysamir@posteo.de>
This commit is contained in:
parent
a9badaf6f1
commit
adf6014959
5 changed files with 161 additions and 165 deletions
124
src/components/FormWishlist.vue
Normal file
124
src/components/FormWishlist.vue
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
<template>
|
||||||
|
<Form
|
||||||
|
@submit="onSubmit"
|
||||||
|
:validation-schema="schema"
|
||||||
|
v-slot="{ meta }"
|
||||||
|
class="w-full flex-col"
|
||||||
|
>
|
||||||
|
<InputText
|
||||||
|
name="title"
|
||||||
|
type="text"
|
||||||
|
:value="wishlist.title"
|
||||||
|
:label="t('components.wishlist-header.main.form.title.label')"
|
||||||
|
/>
|
||||||
|
<InputCheckbox
|
||||||
|
name="public"
|
||||||
|
:value="wishlist.public"
|
||||||
|
:label="t('components.wishlist-header.main.form.public.label')"
|
||||||
|
/>
|
||||||
|
<InputTextArea
|
||||||
|
name="description"
|
||||||
|
type="text"
|
||||||
|
:value="wishlist.description"
|
||||||
|
height-class="h-20"
|
||||||
|
:label="t('components.wishlist-header.main.form.description.label')"
|
||||||
|
/>
|
||||||
|
<InputText
|
||||||
|
name="imageSrc"
|
||||||
|
type="text"
|
||||||
|
:value="wishlist.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"
|
||||||
|
:value="wishlist.slugUrlText"
|
||||||
|
:label="t('components.wishlist-header.main.form.slug-text.label')"
|
||||||
|
/>
|
||||||
|
<ButtonBase
|
||||||
|
class="h-12 w-full"
|
||||||
|
mode="primary"
|
||||||
|
:disabled="!meta.valid"
|
||||||
|
:icon="IconSave"
|
||||||
|
>{{ t('components.wishlist-header.main.form.submit.text') }}</ButtonBase
|
||||||
|
>
|
||||||
|
</Form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Wishlist } from '@/types'
|
||||||
|
import { PropType } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { Form } from 'vee-validate'
|
||||||
|
import { object, string, boolean } from 'yup'
|
||||||
|
import { useToast } from 'vue-toastification'
|
||||||
|
import {
|
||||||
|
ButtonBase,
|
||||||
|
InputText,
|
||||||
|
InputFile,
|
||||||
|
InputCheckbox,
|
||||||
|
InputTextArea,
|
||||||
|
} from '@/components'
|
||||||
|
import { IconSave } from '@/components/icons'
|
||||||
|
import { useWishlistStore } from '@/composables'
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
wishlist: {
|
||||||
|
type: Object as PropType<Wishlist>,
|
||||||
|
requried: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const { update } = useWishlistStore()
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
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}`)
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(t('common.wishlist.saving-failed.text'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,141 +0,0 @@
|
||||||
<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')"
|
|
||||||
/>
|
|
||||||
<InputFile
|
|
||||||
name="imageFile"
|
|
||||||
:label="t('components.wishlist-header.main.form.image-file.label')"
|
|
||||||
/>
|
|
||||||
<InputText
|
|
||||||
name="slugUrlText"
|
|
||||||
type="text"
|
|
||||||
:value="modelValue.slugUrlText"
|
|
||||||
:label="t('components.wishlist-header.main.form.slug-text.label')"
|
|
||||||
/>
|
|
||||||
<ButtonBase
|
|
||||||
class="h-12 w-full"
|
|
||||||
mode="primary"
|
|
||||||
:disabled="!meta.valid"
|
|
||||||
:icon="IconSave"
|
|
||||||
>{{ t('components.wishlist-header.main.form.submit.text') }}</ButtonBase
|
|
||||||
>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { Wishlist } from '@/types'
|
|
||||||
import { PropType } from 'vue'
|
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { useI18n } from 'vue-i18n'
|
|
||||||
import { Form } from 'vee-validate'
|
|
||||||
import { object, string, boolean } from 'yup'
|
|
||||||
import { useToast } from 'vue-toastification'
|
|
||||||
import {
|
|
||||||
ButtonBase,
|
|
||||||
ImageTile,
|
|
||||||
InputText,
|
|
||||||
InputFile,
|
|
||||||
InputCheckbox,
|
|
||||||
InputTextArea,
|
|
||||||
} from '@/components'
|
|
||||||
import { IconSave } from '@/components/icons'
|
|
||||||
import { useEditMode, useWishlistStore } from '@/composables'
|
|
||||||
|
|
||||||
defineProps({
|
|
||||||
modelValue: {
|
|
||||||
type: Object as PropType<Wishlist>,
|
|
||||||
requried: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
const toast = useToast()
|
|
||||||
|
|
||||||
const { isActive: editModeIsActive } = useEditMode()
|
|
||||||
const { update } = useWishlistStore()
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
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}`)
|
|
||||||
} catch (error) {
|
|
||||||
toast.error(t('common.wishlist.saving-failed.text'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -4,11 +4,9 @@ import IconLink from './icons/IconLink.vue'
|
||||||
import ImagePreview from './ImagePreview.vue'
|
import ImagePreview from './ImagePreview.vue'
|
||||||
import { ButtonBase } from './'
|
import { ButtonBase } from './'
|
||||||
import IconCart from './icons/IconCart.vue'
|
import IconCart from './icons/IconCart.vue'
|
||||||
|
import { WishlistItem } from '@/types'
|
||||||
defineProps<{
|
defineProps<{
|
||||||
title: string
|
item: WishlistItem
|
||||||
image: string
|
|
||||||
url?: string
|
|
||||||
description: string
|
|
||||||
}>()
|
}>()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
</script>
|
</script>
|
||||||
|
@ -19,15 +17,15 @@ const { t } = useI18n()
|
||||||
>
|
>
|
||||||
<ImagePreview
|
<ImagePreview
|
||||||
class="max-h-44 flex-shrink-0 flex-grow-0 object-cover sm:aspect-[3/2]"
|
class="max-h-44 flex-shrink-0 flex-grow-0 object-cover sm:aspect-[3/2]"
|
||||||
:src="image"
|
:src="item.imageSrc"
|
||||||
:alt="title"
|
:alt="item.title"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="flex flex-col justify-between p-2">
|
<div class="flex flex-col justify-between p-2">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="mb-1 text-lg font-bold">{{ title }}</h1>
|
<h1 class="mb-1 text-lg font-bold">{{ item.title }}</h1>
|
||||||
<p class="text-sm sm:line-clamp-3">
|
<p class="text-sm sm:line-clamp-3">
|
||||||
{{ description }}
|
{{ item.description }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row items-baseline space-x-2">
|
<div class="flex flex-row items-baseline space-x-2">
|
||||||
|
@ -38,8 +36,8 @@ const { t } = useI18n()
|
||||||
>{{ t('components.wishlist-item.bought-button.text') }}</ButtonBase
|
>{{ t('components.wishlist-item.bought-button.text') }}</ButtonBase
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
v-if="url"
|
v-if="item.url"
|
||||||
:href="url"
|
:href="item.url"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
class="mt-1 flex w-fit flex-row items-center text-sm text-stone-500 dark:text-white/60"
|
class="mt-1 flex w-fit flex-row items-center text-sm text-stone-500 dark:text-white/60"
|
||||||
|
|
|
@ -7,5 +7,5 @@ export { default as InputText } from './InputText.vue'
|
||||||
export { default as InputFile } from './InputFile.vue'
|
export { default as InputFile } from './InputFile.vue'
|
||||||
export { default as InputTextArea } from './InputTextArea.vue'
|
export { default as InputTextArea } from './InputTextArea.vue'
|
||||||
export { default as Modal } from './Modal.vue'
|
export { default as Modal } from './Modal.vue'
|
||||||
export { default as WishlistHeader } from './WishlistHeader.vue'
|
export { default as FormWishlist } from './FormWishlist.vue'
|
||||||
export { default as WishlistItem } from './WishlistItem.vue'
|
export { default as WishlistItem } from './WishlistItem.vue'
|
||||||
|
|
|
@ -2,14 +2,14 @@
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { WishlistItem as WishlistItemType } from '@/types'
|
import { WishlistItem as WishlistItemType } from '@/types'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { useWishlistStore, useModal } from '@/composables'
|
import { useWishlistStore, useModal, useEditMode } from '@/composables'
|
||||||
import WishlistItem from '@/components/WishlistItem.vue'
|
import { FormWishlist, ImageTile, WishlistItem } from '@/components'
|
||||||
import WishlistHeader from '@/components/WishlistHeader.vue'
|
|
||||||
import { IconNoGift } from '../components/icons'
|
import { IconNoGift } from '../components/icons'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const modal = useModal()
|
const modal = useModal()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
const { isActive: editModeIsActive } = useEditMode()
|
||||||
|
|
||||||
const { state, fetch, isReady, itemBought, filteredItems } = useWishlistStore()
|
const { state, fetch, isReady, itemBought, filteredItems } = useWishlistStore()
|
||||||
await fetch(route.params.slug as string)
|
await fetch(route.params.slug as string)
|
||||||
|
@ -29,14 +29,28 @@ const bought = async (item: WishlistItemType): Promise<void> => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="isReady" class="h-full">
|
<div v-if="isReady" class="h-full">
|
||||||
<WishlistHeader v-model="state" />
|
<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="state !== undefined"
|
||||||
|
>
|
||||||
|
<ImageTile :image-src="state.imageSrc" class="shrink-0"></ImageTile>
|
||||||
|
<div v-if="!editModeIsActive">
|
||||||
|
<h1 class="mb-2 text-center text-2xl font-bold md:text-left">
|
||||||
|
{{ state.title }}
|
||||||
|
</h1>
|
||||||
|
<p class="text-lg">
|
||||||
|
{{ state.description }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<FormWishlist v-else :wishlist="state" />
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="filteredItems.length > 0"
|
v-if="filteredItems.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"
|
||||||
>
|
>
|
||||||
|
<div v-for="(item, index) in filteredItems" :key="index">
|
||||||
<WishlistItem
|
<WishlistItem
|
||||||
v-for="(item, index) in filteredItems"
|
:item="item"
|
||||||
:key="index"
|
|
||||||
:title="item.title"
|
:title="item.title"
|
||||||
:url="item.url"
|
:url="item.url"
|
||||||
:image="item.imageSrc"
|
:image="item.imageSrc"
|
||||||
|
@ -44,6 +58,7 @@ const bought = async (item: WishlistItemType): Promise<void> => {
|
||||||
@bought="bought(item)"
|
@bought="bought(item)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div v-else class="flex h-1/2 w-full justify-center">
|
<div v-else class="flex h-1/2 w-full justify-center">
|
||||||
<div
|
<div
|
||||||
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"
|
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"
|
||||||
|
|
Loading…
Add table
Reference in a new issue