292 lines
10 KiB
Vue
292 lines
10 KiB
Vue
<template>
|
|
<div class="min-h-screen pt-24 pb-16">
|
|
<!-- Background with gradient -->
|
|
<div class="fixed inset-0 -z-10">
|
|
<div
|
|
class="absolute inset-0 bg-linear-to-br from-primary-50 via-cyan-50 to-accent-50 dark:from-gray-950 dark:via-primary-950/30 dark:to-accent-950/30"
|
|
/>
|
|
<!-- Gradient orbs -->
|
|
<div class="absolute top-20 left-1/4 w-96 h-96 bg-primary-400/20 rounded-full blur-3xl animate-orb-float" />
|
|
<div
|
|
class="absolute bottom-20 right-1/4 w-80 h-80 bg-accent-400/20 rounded-full blur-3xl animate-orb-float"
|
|
style="animation-delay: 3s"
|
|
/>
|
|
</div>
|
|
|
|
<div class="relative max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<!-- Header section -->
|
|
<div class="text-center mb-12">
|
|
<!-- Badge -->
|
|
<span
|
|
class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300 text-sm font-medium mb-6"
|
|
>
|
|
<UIcon name="i-lucide-message-circle" class="w-4 h-4" />
|
|
{{ $t('contact.badge') }}
|
|
</span>
|
|
|
|
<!-- Title -->
|
|
<h1
|
|
class="font-heading text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900 dark:text-white mb-6 animate-fade-in-up"
|
|
>
|
|
{{ $t('contact.title', { highlight: '' })
|
|
}}<span class="gradient-text">{{ $t('contact.titleHighlight') }}</span>
|
|
</h1>
|
|
|
|
<!-- Description -->
|
|
<p
|
|
class="text-lg text-gray-600 dark:text-gray-300 max-w-2xl mx-auto animate-fade-in-up"
|
|
style="animation-delay: 100ms"
|
|
>
|
|
{{ $t('contact.description') }}
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Form section -->
|
|
<div class="animate-fade-in-up" style="animation-delay: 150ms">
|
|
<UCard variant="glass" :ui="{ root: 'rounded-3xl shadow-2xl', body: 'p-8 sm:p-10' }">
|
|
<!-- Success state -->
|
|
<Transition
|
|
enter-active-class="transition-all duration-500 ease-out"
|
|
enter-from-class="opacity-0 scale-95"
|
|
enter-to-class="opacity-100 scale-100"
|
|
leave-active-class="transition-all duration-300 ease-in"
|
|
leave-from-class="opacity-100 scale-100"
|
|
leave-to-class="opacity-0 scale-95"
|
|
>
|
|
<div v-if="isSuccess" class="text-center py-8">
|
|
<div
|
|
class="w-20 h-20 mx-auto mb-6 rounded-full bg-success-100 dark:bg-success-900/30 flex items-center justify-center"
|
|
>
|
|
<UIcon name="i-lucide-check-circle" class="w-10 h-10 text-success-600 dark:text-success-400" />
|
|
</div>
|
|
<h2 class="font-heading text-2xl font-bold text-gray-900 dark:text-white mb-3">
|
|
{{ $t('contact.success.title') }}
|
|
</h2>
|
|
<p class="text-gray-600 dark:text-gray-300 mb-8">
|
|
{{ $t('contact.success.message') }}
|
|
</p>
|
|
<div class="flex flex-col sm:flex-row gap-3 justify-center">
|
|
<UButton to="/" variant="gradientOutline" leading-icon="i-lucide-home">
|
|
{{ $t('contact.success.backToHome') }}
|
|
</UButton>
|
|
<UButton variant="gradient" leading-icon="i-lucide-mail-plus" @click="resetForm">
|
|
{{ $t('contact.success.sendAnother') }}
|
|
</UButton>
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
|
|
<!-- Form -->
|
|
<UForm v-if="!isSuccess" :state="formState" :schema="schema" class="space-y-6" @submit="onSubmit">
|
|
<!-- Name field -->
|
|
<UFormField :label="$t('contact.form.name')" name="name" required class="w-full">
|
|
<UInput
|
|
v-model="formState.name"
|
|
size="xl"
|
|
:disabled="isLoading"
|
|
autocomplete="name"
|
|
class="w-full"
|
|
:ui="{
|
|
base: 'w-full rounded-xl bg-white dark:bg-gray-900 border-gray-200 dark:border-gray-700 focus:ring-2 focus:ring-primary-500 focus:border-transparent'
|
|
}"
|
|
>
|
|
<template #leading>
|
|
<UIcon name="i-lucide-user" class="w-5 h-5 text-gray-400" />
|
|
</template>
|
|
</UInput>
|
|
</UFormField>
|
|
|
|
<!-- Email field -->
|
|
<UFormField :label="$t('contact.form.email')" name="email" required class="w-full">
|
|
<UInput
|
|
v-model="formState.email"
|
|
type="email"
|
|
size="xl"
|
|
:disabled="isLoading"
|
|
autocomplete="email"
|
|
class="w-full"
|
|
:ui="{
|
|
base: 'w-full rounded-xl bg-white dark:bg-gray-900 border-gray-200 dark:border-gray-700 focus:ring-2 focus:ring-primary-500 focus:border-transparent'
|
|
}"
|
|
>
|
|
<template #leading>
|
|
<UIcon name="i-lucide-mail" class="w-5 h-5 text-gray-400" />
|
|
</template>
|
|
</UInput>
|
|
</UFormField>
|
|
|
|
<!-- Message field -->
|
|
<UFormField :label="$t('contact.form.message')" name="message" required class="w-full">
|
|
<UTextarea
|
|
v-model="formState.message"
|
|
:rows="6"
|
|
:disabled="isLoading"
|
|
class="w-full"
|
|
size="xl"
|
|
:ui="{
|
|
base: 'w-full rounded-xl bg-white dark:bg-gray-900 border-gray-200 dark:border-gray-700 focus:ring-2 focus:ring-primary-500 focus:border-transparent resize-none'
|
|
}"
|
|
/>
|
|
<template #hint>
|
|
<span class="text-xs text-gray-400"> {{ formState.message.length }} / 5000 </span>
|
|
</template>
|
|
</UFormField>
|
|
|
|
<!-- Error message -->
|
|
<Transition
|
|
enter-active-class="transition-all duration-300 ease-out"
|
|
enter-from-class="opacity-0 -translate-y-2"
|
|
enter-to-class="opacity-100 translate-y-0"
|
|
>
|
|
<div
|
|
v-if="error"
|
|
class="p-4 rounded-xl bg-error-100 dark:bg-error-900/30 border border-error-200 dark:border-error-800"
|
|
role="alert"
|
|
>
|
|
<div class="flex items-center gap-3 text-error-700 dark:text-error-300">
|
|
<UIcon name="i-lucide-alert-circle" class="w-5 h-5 shrink-0" />
|
|
<span>{{ error }}</span>
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
|
|
<!-- Submit button -->
|
|
<UButton type="submit" size="xl" block variant="gradient" :loading="isLoading" class="rounded-xl">
|
|
<template v-if="isLoading">
|
|
{{ $t('contact.form.sending') }}
|
|
</template>
|
|
<template v-else>
|
|
<UIcon name="i-lucide-send" class="w-5 h-5 mr-2" />
|
|
{{ $t('contact.form.submit') }}
|
|
</template>
|
|
</UButton>
|
|
</UForm>
|
|
</UCard>
|
|
|
|
<!-- Trust indicators -->
|
|
<div class="mt-8 flex flex-wrap justify-center gap-6 animate-fade-in-up" style="animation-delay: 300ms">
|
|
<div class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-300">
|
|
<div class="w-6 h-6 rounded-full bg-success-100 dark:bg-success-900/30 flex items-center justify-center">
|
|
<UIcon name="i-lucide-shield-check" class="w-3.5 h-3.5 text-success-600 dark:text-success-400" />
|
|
</div>
|
|
<span>{{ $t('contact.trust.gdpr') }}</span>
|
|
</div>
|
|
<div class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-300">
|
|
<div class="w-6 h-6 rounded-full bg-success-100 dark:bg-success-900/30 flex items-center justify-center">
|
|
<UIcon name="i-lucide-lock" class="w-3.5 h-3.5 text-success-600 dark:text-success-400" />
|
|
</div>
|
|
<span>{{ $t('contact.trust.encrypted') }}</span>
|
|
</div>
|
|
<div class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-300">
|
|
<div class="w-6 h-6 rounded-full bg-success-100 dark:bg-success-900/30 flex items-center justify-center">
|
|
<UIcon name="i-lucide-clock" class="w-3.5 h-3.5 text-success-600 dark:text-success-400" />
|
|
</div>
|
|
<span>{{ $t('contact.trust.responseTime') }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { z } from 'zod'
|
|
import type { FormSubmitEvent } from '@nuxt/ui'
|
|
import type { ContactFormData } from '~/composables/useContactForm'
|
|
|
|
const { t, locale } = useI18n({ useScope: 'global' })
|
|
const { isLoading, isSuccess, error, submitForm, reset } = useContactForm()
|
|
|
|
// SEO Meta
|
|
useSeoMeta({
|
|
title: () => t('contact.meta.title'),
|
|
description: () => t('contact.meta.description'),
|
|
ogTitle: () => t('contact.meta.title'),
|
|
ogDescription: () => t('contact.meta.description'),
|
|
ogImage: '/og-image.png',
|
|
ogType: 'website',
|
|
twitterCard: 'summary_large_image'
|
|
})
|
|
|
|
// Structured data
|
|
useHead({
|
|
htmlAttrs: {
|
|
lang: () => locale.value
|
|
},
|
|
script: [
|
|
{
|
|
type: 'application/ld+json',
|
|
innerHTML: JSON.stringify({
|
|
'@context': 'https://schema.org',
|
|
'@type': 'ContactPage',
|
|
name: t('contact.meta.title'),
|
|
description: t('contact.meta.description')
|
|
})
|
|
}
|
|
]
|
|
})
|
|
|
|
// Form validation schema
|
|
const schema = computed(() =>
|
|
z.object({
|
|
name: z.string().min(1, t('contact.validation.nameRequired')).max(100, t('contact.validation.nameMax')),
|
|
email: z.string().min(1, t('contact.validation.emailRequired')).email(t('contact.validation.emailInvalid')),
|
|
message: z.string().min(10, t('contact.validation.messageMin')).max(5000, t('contact.validation.messageMax'))
|
|
})
|
|
)
|
|
|
|
// Form state
|
|
const formState = reactive<ContactFormData>({
|
|
name: '',
|
|
email: '',
|
|
message: ''
|
|
})
|
|
|
|
// Submit handler
|
|
async function onSubmit(event: FormSubmitEvent<ContactFormData>) {
|
|
await submitForm(event.data)
|
|
}
|
|
|
|
// Reset form
|
|
function resetForm() {
|
|
formState.name = ''
|
|
formState.email = ''
|
|
formState.message = ''
|
|
reset()
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* Orb float animation */
|
|
@keyframes orb-float {
|
|
0%,
|
|
100% {
|
|
transform: translateY(0) scale(1);
|
|
}
|
|
50% {
|
|
transform: translateY(-20px) scale(1.05);
|
|
}
|
|
}
|
|
|
|
.animate-orb-float {
|
|
animation: orb-float 8s ease-in-out infinite;
|
|
}
|
|
|
|
/* Fade in up animation */
|
|
@keyframes fade-in-up {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(20px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.animate-fade-in-up {
|
|
animation: fade-in-up 0.6s ease-out forwards;
|
|
opacity: 0;
|
|
}
|
|
</style>
|