This commit is contained in:
Oscar
2026-06-22 09:31:44 +03:00
commit 3902d0dec4
48 changed files with 19218 additions and 0 deletions

View File

@@ -0,0 +1,200 @@
<template>
<section id="contact" class="py-24 px-4 bg-gray-950">
<div class="max-w-2xl mx-auto">
<div class="section-hidden" ref="sectionRef">
<h2 class="text-3xl md:text-4xl font-bold text-white mb-4">
Связаться
</h2>
<div class="w-16 h-1 bg-indigo-500 mb-10 rounded-full" />
<UCard class="bg-gray-900 border-gray-800">
<div v-if="success" class="space-y-4">
<UAlert
color="success"
variant="soft"
icon="i-heroicons-check-circle"
title="Сообщение отправлено!"
description="Мы получили ваше обращение и свяжемся с вами в ближайшее время."
/>
<UAlert
v-if="aiReply"
color="primary"
variant="soft"
icon="i-heroicons-sparkles"
title="Автоответ от AI"
:description="aiReply"
/>
<UButton
color="neutral"
variant="ghost"
icon="i-heroicons-arrow-left"
@click="handleReset"
>
Отправить ещё
</UButton>
</div>
<form v-else @submit.prevent="handleSubmit" class="space-y-5">
<UAlert
v-if="error"
color="error"
variant="soft"
icon="i-heroicons-exclamation-triangle"
:title="error"
/>
<UFormField
label="Имя"
required
:error="validationErrors.name"
>
<UInput
v-model="form.name"
placeholder="Иван Иванов"
:disabled="loading"
size="lg"
class="w-full"
/>
</UFormField>
<UFormField
label="Телефон"
required
:error="validationErrors.phone"
>
<UInput
v-model="form.phone"
placeholder="+7 999 123-45-67"
type="tel"
:disabled="loading"
size="lg"
class="w-full"
/>
</UFormField>
<UFormField
label="Email"
required
:error="validationErrors.email"
>
<UInput
v-model="form.email"
placeholder="ivan@example.com"
type="email"
:disabled="loading"
size="lg"
class="w-full"
/>
</UFormField>
<UFormField label="Комментарий">
<UTextarea
v-model="form.comment"
placeholder="Расскажите о вашем проекте..."
:disabled="loading"
:rows="4"
class="w-full"
/>
</UFormField>
<UButton
type="submit"
color="primary"
size="lg"
:loading="loading"
:disabled="loading"
icon="i-heroicons-paper-airplane"
class="w-full justify-center"
>
{{ loading ? 'Отправляем...' : 'Отправить' }}
</UButton>
</form>
</UCard>
</div>
</div>
</section>
</template>
<script setup lang="ts">
const sectionRef = ref<HTMLElement | null>(null);
const { loading, error, success, aiReply, submit, reset } = useContact();
const form = reactive({
name: '',
phone: '',
email: '',
comment: '',
});
const validationErrors = reactive({
name: '',
phone: '',
email: '',
});
function validate(): boolean {
validationErrors.name = '';
validationErrors.phone = '';
validationErrors.email = '';
let valid = true;
if (!form.name.trim()) {
validationErrors.name = 'Введите имя';
valid = false;
}
if (!form.phone.trim()) {
validationErrors.phone = 'Введите телефон';
valid = false;
}
else if (!/^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/.test(form.phone.replace(/\s/g, ''))) {
validationErrors.phone = 'Некорректный формат телефона';
valid = false;
}
if (!form.email.trim()) {
validationErrors.email = 'Введите email';
valid = false;
}
else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)) {
validationErrors.email = 'Некорректный email';
valid = false;
}
return valid;
}
async function handleSubmit() {
if (!validate()) return;
await submit({
name: form.name.trim(),
phone: form.phone.trim(),
email: form.email.trim(),
comment: form.comment.trim() || undefined,
});
}
function handleReset() {
reset();
form.name = '';
form.phone = '';
form.email = '';
form.comment = '';
}
onMounted(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.remove('section-hidden');
entry.target.classList.add('section-visible');
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.1 },
);
if (sectionRef.value) observer.observe(sectionRef.value);
});
</script>