Dominik Milacher 0dc24c4db7
Some checks failed
Build and deploy updated apps / Build & deploy (push) Failing after 50s
Extend ux layer and overhaul panoramablick-saalbach.at
2025-11-21 21:17:52 +01:00

174 lines
3.9 KiB
Vue

<script setup lang="ts">
const { g, l, p } = useContentInjected()
const props = withDefaults(
defineProps<{
class?: string
ux?: any
buttonsProps?: any
sendProps?: any
privacyProps?: any
}>(),
{
buttonsProps: { align: 'left' },
sendProps: {
color: 'primary',
variant: 'solid',
trailing: true,
},
privacyProps: {
color: 'secondary',
variant: 'outline',
trailing: true,
},
}
)
import * as v from 'valibot'
const schema = computed(() =>
v.object({
name: v.pipe(v.string(), v.minLength(2, l.value.name.invalid)),
email: v.pipe(v.string(), v.email(l.value.email.invalid)),
subject: v.pipe(v.string(), v.minLength(3, l.value.subject.invalid)),
message: v.pipe(v.string(), v.minLength(10, l.value.message.invalid)),
})
)
const state = reactive({
name: '',
email: '',
subject: '',
message: '',
})
const toast = useToast()
const sending = ref(false)
async function onSubmit(event: any) {
sending.value = true
const start = Date.now()
let success = false
let attempt = 0
while (true) {
attempt += 1
try {
await $fetch('https://api.dominikmilacher.com/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: {
...event.data,
hotel: g.value.business.uid ?? '',
},
})
success = true
break
} catch (error: any) {
console.warn(`Send failed: ${JSON.stringify(error)}`)
if (attempt >= 3) {
break
}
}
}
const minimum = 2000
const elapsed = Date.now() - start
if (elapsed < minimum) {
await new Promise((resolve) => setTimeout(resolve, minimum - elapsed))
}
toast.add({
title: success ? l.value.sent.title : l.value.error.title,
description: success ? l.value.sent.description : l.value.error.description,
color: success ? 'success' : 'error',
})
if (success) {
Object.assign(state, { name: '', email: '', subject: '', message: '' })
}
sending.value = false
}
const classes = useStyling(
'contactForm',
{
slots: {
base: 'flex flex-col',
field: '[&>*]:m-0',
input: 'w-full',
send: 'cursor-pointer',
privacy: '',
},
},
props
)
</script>
<template>
<UForm :class="classes.base" :state="state" :schema="schema" @submit="onSubmit">
<UFormField name="name" :class="classes.field" :label="l.name.label" :ui="{ label: 'sr-only' }">
<UInput v-model="state.name" :class="classes.input" :placeholder="l.name.prompt" />
</UFormField>
<UFormField
name="email"
:class="classes.field"
:label="l.email.label"
:ui="{ label: 'sr-only' }"
>
<UInput v-model="state.email" :class="classes.input" :placeholder="l.email.prompt" />
</UFormField>
<UFormField
name="subject"
:class="classes.field"
:label="l.subject.label"
:ui="{ label: 'sr-only' }"
>
<UInput v-model="state.subject" :class="classes.input" :placeholder="l.subject.prompt" />
</UFormField>
<UFormField
name="message"
:class="classes.field"
:label="l.message.label"
:ui="{ label: 'sr-only' }"
>
<UTextarea
v-model="state.message"
:rows="6"
:class="classes.input"
:placeholder="l.message.prompt"
/>
</UFormField>
<XContainerButtons v-bind="props.buttonsProps" class="fmt-1">
<UButton
type="submit"
:class="classes.send"
:icon="l.send.icon"
:loading="sending"
:loading-icon="l.send.sending"
v-bind="props.sendProps"
>
{{ l.send.label }}
</UButton>
<UButton
:class="classes.privacy"
:icon="l.privacy.icon"
:to="p(l.privacy.link)"
v-bind="props.privacyProps"
>
{{ l.privacy.label }}
</UButton>
</XContainerButtons>
</UForm>
</template>