Новые изменения в компоенты

This commit is contained in:
Web-serfer 2026-05-03 15:57:35 +05:00
parent 762727290f
commit 1ee9cbe9fe
27 changed files with 845 additions and 229 deletions

View file

@ -1,5 +1,6 @@
---
import Button from "../base/Button.astro";
import ConsentCheckbox from "../base/ConsentCheckbox.astro";
// Иконки
const emailIcon = `<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>`;
@ -85,6 +86,7 @@ const subtitle = isResetMode
id="submit-btn"
disabled
/>
<ConsentCheckbox formId="forgot-form" />
</form>
)}

View file

@ -1,6 +1,7 @@
---
import Button from "../../base/Button.astro";
import Toast from "../../base/Toast.astro";
import ConsentCheckbox from "../../base/ConsentCheckbox.astro";
// Иконки по умолчанию (SVG строки)
const emailIcon = `<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>`;
@ -164,6 +165,8 @@ const MAX_PASSWORD_LENGTH = 12;
id="submit-btn"
/>
<ConsentCheckbox formId="login-form" />
<!-- Register link -->
<p class="text-center text-sm text-gray-500 m-0">
Нет аккаунта? <a
@ -370,6 +373,16 @@ const MAX_PASSWORD_LENGTH = 12;
if (passwordInput.value.length > 0) validatePassword(true);
});
// Чекбокс согласия
const consentCheckbox = form.querySelector<HTMLInputElement>('#login-form-consent');
consentCheckbox?.addEventListener('change', () => {
const wrapper = consentCheckbox?.parentElement?.parentElement as HTMLElement;
const consentError = wrapper?.querySelector('.consent-error') as HTMLElement;
if (consentCheckbox?.checked && consentError) {
consentError.classList.add('hidden');
}
});
// Отправка формы
form?.addEventListener("submit", async (e) => {
e.preventDefault();
@ -380,6 +393,21 @@ const MAX_PASSWORD_LENGTH = 12;
return;
}
// Проверка согласия на обработку ПДн
const consentCheckbox = form.querySelector<HTMLInputElement>('#login-form-consent');
if (!consentCheckbox?.checked) {
const wrapper = consentCheckbox?.parentElement?.parentElement as HTMLElement;
const consentError = wrapper?.querySelector('.consent-error') as HTMLElement;
if (consentError) consentError.classList.remove('hidden');
if (window.showToast) {
window.showToast(
"Необходимо согласие на обработку персональных данных",
"error",
);
}
return;
}
// Если локальная валидация не пройдена — показываем тост с ошибкой
if (!showAllErrors()) {
console.log("[LOGIN FORM] Валидация не пройдена");

View file

@ -1,6 +1,7 @@
---
import Button from "../../base/Button.astro";
import Toast from "../../base/Toast.astro";
import ConsentCheckbox from "../../base/ConsentCheckbox.astro";
// Иконки по умолчанию (SVG строки)
const userIcon = `<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>`;
@ -223,6 +224,8 @@ const MAX_PASSWORD_LENGTH = 12;
disabled
/>
<ConsentCheckbox formId="register-form" />
<!-- Login link -->
<p class="text-center text-sm text-gray-500 m-0">
Уже есть аккаунт? <a
@ -437,7 +440,9 @@ const MAX_PASSWORD_LENGTH = 12;
// Обновление состояния кнопки отправки
function updateSubmitButton() {
const isFormValid = validateName() && validateEmail() && validatePassword() && validateConfirmPassword() && passwordsMatch();
submitBtn.disabled = !isFormValid;
const consentCheckbox = form.querySelector<HTMLInputElement>('#register-form-consent');
const isConsentValid = consentCheckbox ? consentCheckbox.checked : false;
submitBtn.disabled = !(isFormValid && isConsentValid);
}
// Ограничение ввода для email (только разрешенные символы)
@ -526,6 +531,17 @@ const MAX_PASSWORD_LENGTH = 12;
updateSubmitButton();
});
// Чекбокс согласия
const consentCheckbox = form.querySelector<HTMLInputElement>('#register-form-consent');
consentCheckbox?.addEventListener('change', () => {
const wrapper = consentCheckbox?.parentElement?.parentElement as HTMLElement;
const consentError = wrapper?.querySelector('.consent-error') as HTMLElement;
if (consentCheckbox?.checked && consentError) {
consentError.classList.add('hidden');
}
updateSubmitButton();
});
// Отправка формы
form?.addEventListener("submit", async (e) => {
e.preventDefault();
@ -536,6 +552,21 @@ const MAX_PASSWORD_LENGTH = 12;
return;
}
// Проверка согласия на обработку ПДн
const consentCheckbox = form.querySelector<HTMLInputElement>('#register-form-consent');
if (!consentCheckbox?.checked) {
const wrapper = consentCheckbox?.parentElement?.parentElement as HTMLElement;
const consentError = wrapper?.querySelector('.consent-error') as HTMLElement;
if (consentError) consentError.classList.remove('hidden');
if (window.showToast) {
window.showToast(
"Необходимо согласие на обработку персональных данных",
"error",
);
}
return;
}
// Если локальная валидация не пройдена — показываем тост с ошибкой
if (!showAllErrors()) {
console.log("[REGISTER FORM] Валидация не пройдена");

View file

@ -1,6 +1,7 @@
---
import SectionHeader from "@components/base/SectionHeader.astro";
import { CONTACT_CONSTANTS } from "@constants/constants.ts";
import ConsentCheckbox from "@components/base/ConsentCheckbox.astro";
const contactIcons = {
phone:
@ -352,15 +353,7 @@ const contactInfo: ContactInfo[] = [
<span class="btn-loading hidden">Отправка...</span>
</button>
<p class="text-center text-xs text-gray-400">
Нажимая кнопку, вы соглашаетесь с{" "}
<a
href="/policy"
class="text-[var(--color-gold)] hover:underline"
>
политикой конфиденциальности
</a>
</p>
<ConsentCheckbox formId="consultation-form" />
</form>
</div>
</div>
@ -540,6 +533,15 @@ const contactInfo: ContactInfo[] = [
if (!validateField(field, false)) isValid = false;
});
const consentCheckbox = form.querySelector<HTMLInputElement>('#consultation-form-consent');
if (consentCheckbox) {
const consentError = consentCheckbox.parentElement?.parentElement?.querySelector('.consent-error') as HTMLElement;
if (!consentCheckbox.checked) {
isValid = false;
if (consentError) consentError.classList.add('hidden');
}
}
submitBtn.disabled = !isValid;
return isValid;
}
@ -556,6 +558,18 @@ const contactInfo: ContactInfo[] = [
if (!validateField(field, true)) isValid = false;
});
const consentCheckbox = form.querySelector<HTMLInputElement>('#consultation-form-consent');
if (consentCheckbox) {
const wrapper = consentCheckbox.parentElement?.parentElement as HTMLElement;
const consentError = wrapper?.querySelector('.consent-error') as HTMLElement;
if (!consentCheckbox.checked) {
isValid = false;
if (consentError) consentError.classList.remove('hidden');
} else {
if (consentError) consentError.classList.add('hidden');
}
}
return isValid;
}
@ -637,6 +651,12 @@ const contactInfo: ContactInfo[] = [
});
});
// Чекбокс согласия
const consentCheckbox = form.querySelector<HTMLInputElement>('#consultation-form-consent');
consentCheckbox?.addEventListener('change', () => {
checkFormValidity();
});
// Валидация при потере фокуса (показываем ошибки только если поле было заполнено неверно)
form.querySelectorAll("input, textarea").forEach((field) => {
field.addEventListener("blur", () => {

View file

@ -0,0 +1,29 @@
---
interface Props {
formId: string;
name?: string;
}
const { formId, name = "consent" } = Astro.props;
---
<div class="consent-checkbox-wrapper">
<label class="flex items-start gap-3 cursor-pointer group">
<input
type="checkbox"
name={name}
id={`${formId}-consent`}
class="w-5 h-5 mt-0.5 flex-shrink-0 accent-[#bf9b58] cursor-pointer"
required
/>
<span class="text-sm text-gray-600 leading-relaxed">
Нажимая кнопку «Отправить», я соглашаюсь на обработку моих персональных данных в соответствии с
<a href="/privacy-policy" target="_blank" class="text-[var(--color-gold)] hover:underline font-medium">Политикой конфиденциальности</a>
и даю согласие на
<a href="/cookie-policy" target="_blank" class="text-[var(--color-gold)] hover:underline font-medium">использование файлов cookie</a>
</span>
</label>
<p class="consent-error hidden text-red-500 text-xs mt-1 ml-8">
Необходимо согласие на обработку персональных данных
</p>
</div>

View file

@ -1,6 +1,7 @@
---
import SectionHeader from "@components/base/SectionHeader.astro";
import { CONTACT_CONSTANTS } from "@constants/constants.ts";
import ConsentCheckbox from "@components/base/ConsentCheckbox.astro";
const contactIcons = {
phone:
@ -263,15 +264,7 @@ const contactInfo: ContactInfo[] = [
<span class="btn-loading hidden">Отправка...</span>
</button>
<p class="text-center text-xs text-gray-400">
Нажимая кнопку, вы соглашаетесь с{" "}
<a
href="/policy"
class="text-[var(--color-gold)] hover:underline"
>
политикой конфиденциальности
</a>
</p>
<ConsentCheckbox formId="consultation-form" />
</form>
</div>
</div>
@ -402,6 +395,15 @@ const contactInfo: ContactInfo[] = [
if (!validateField(field, false)) isValid = false;
});
const consentCheckbox = form.querySelector<HTMLInputElement>('#consultation-form-consent');
if (consentCheckbox) {
const consentError = consentCheckbox.parentElement?.parentElement?.querySelector('.consent-error') as HTMLElement;
if (!consentCheckbox.checked) {
isValid = false;
if (consentError) consentError.classList.add('hidden');
}
}
submitBtn.disabled = !isValid;
return isValid;
}
@ -418,6 +420,18 @@ const contactInfo: ContactInfo[] = [
if (!validateField(field, true)) isValid = false;
});
const consentCheckbox = form.querySelector<HTMLInputElement>('#consultation-form-consent');
if (consentCheckbox) {
const wrapper = consentCheckbox.parentElement?.parentElement as HTMLElement;
const consentError = wrapper?.querySelector('.consent-error') as HTMLElement;
if (!consentCheckbox.checked) {
isValid = false;
if (consentError) consentError.classList.remove('hidden');
} else {
if (consentError) consentError.classList.add('hidden');
}
}
return isValid;
}
@ -499,6 +513,12 @@ const contactInfo: ContactInfo[] = [
});
});
// Чекбокс согласия
const consentCheckbox = form.querySelector<HTMLInputElement>('#consultation-form-consent');
consentCheckbox?.addEventListener('change', () => {
checkFormValidity();
});
// Валидация при потере фокуса (показываем ошибки только если поле было заполнено неверно)
form.querySelectorAll("input, textarea").forEach((field) => {
field.addEventListener("blur", () => {

View file

@ -0,0 +1,78 @@
---
---
<div id="cookie-banner" class="fixed bottom-0 left-0 right-0 z-50 hidden">
<div class="bg-gradient-to-r from-[#1a1f3d] to-[#2a2f4d] text-white p-4 md:p-6">
<div class="container mx-auto px-4 md:px-8">
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
<div class="flex-1 pr-0 lg:pr-4">
<h3 class="text-lg font-bold mb-2">🍪 Использование файлов cookie</h3>
<p class="text-sm text-gray-300 leading-relaxed">
Мы используем файлы cookie для улучшения работы сайта и анализа посещаемости.
<a href="/cookie-policy" class="text-[#bf9b58] hover:underline ml-1">Подробнее</a>
</p>
</div>
<div class="flex flex-col sm:flex-row gap-3 flex-shrink-0">
<button
id="cookie-decline"
class="px-6 py-2.5 border-2 border-white/30 text-white rounded-xl font-semibold hover:bg-white/10 hover:cursor-pointer transition-all text-sm"
>
Отклонить
</button>
<button
id="cookie-accept"
class="px-6 py-2.5 bg-[#bf9b58] text-white rounded-xl font-semibold hover:bg-[#c4aa68] hover:cursor-pointer transition-all text-sm"
>
Принять
</button>
</div>
</div>
</div>
</div>
</div>
<script>
function initCookieBanner() {
const banner = document.getElementById('cookie-banner');
const acceptBtn = document.getElementById('cookie-accept');
const declineBtn = document.getElementById('cookie-decline');
if (!banner || !acceptBtn || !declineBtn) return;
const cookieConsent = localStorage.getItem('cookieConsent');
if (!cookieConsent) {
banner.classList.remove('hidden');
document.body.classList.add('pb-cookie-banner-active');
}
acceptBtn.addEventListener('click', () => {
localStorage.setItem('cookieConsent', 'accepted');
localStorage.setItem('cookieConsentDate', new Date().toISOString());
banner.classList.add('hidden');
document.body.classList.remove('pb-cookie-banner-active');
});
declineBtn.addEventListener('click', () => {
localStorage.setItem('cookieConsent', 'declined');
localStorage.setItem('cookieConsentDate', new Date().toISOString());
banner.classList.add('hidden');
document.body.classList.remove('pb-cookie-banner-active');
});
}
document.addEventListener('DOMContentLoaded', initCookieBanner);
document.addEventListener('astro:page-load', initCookieBanner);
</script>
<style is:global>
body.pb-cookie-banner-active {
padding-bottom: 80px;
}
@media (min-width: 768px) {
body.pb-cookie-banner-active {
padding-bottom: 0;
}
}
</style>

View file

@ -158,6 +158,12 @@ const menu = [
>
Политика конфиденциальности
</a>
<a
href="/cookie-policy"
class="text-[10px] uppercase tracking-[0.1em] text-gray-600 hover:text-white transition-colors"
>
Cookie
</a>
<a
href="/legal-info"
class="text-[10px] uppercase tracking-[0.1em] text-gray-600 hover:text-white transition-colors"

View file

@ -1,5 +1,6 @@
---
import Toast from "@components/base/Toast.astro";
import ConsentCheckbox from "@components/base/ConsentCheckbox.astro";
---
<!-- Модальное окно для написания отзыва с проверкой авторизации -->
@ -212,6 +213,8 @@ import Toast from "@components/base/Toast.astro";
Отправить отзыв
</button>
</div>
<ConsentCheckbox formId="review-form" />
</form>
</div>
</div>
@ -463,6 +466,12 @@ import Toast from "@components/base/Toast.astro";
validateForm();
});
// Чекбокс согласия
const consentCheckbox = reviewForm.querySelector<HTMLInputElement>('#review-form-consent');
consentCheckbox?.addEventListener('change', () => {
validateForm();
});
// Валидация должности
const validateRoleField = () => {
if (!roleInput) return false;
@ -547,7 +556,7 @@ import Toast from "@components/base/Toast.astro";
}
};
// Общая валидация формы и активация кнопки
// Общая валидация формы и активация кнопки
const validateForm = () => {
if (!submitBtn) return;
@ -555,12 +564,14 @@ import Toast from "@components/base/Toast.astro";
const isCaseTypeValid = caseTypeSelect && caseTypeSelect.value !== "";
const isRatingValid = ratingInput.value && ratingInput.value !== "0";
const isTextValid = reviewText && reviewText.value.trim().length >= MIN_TEXT_LENGTH && reviewText.value.length <= MAX_CHARS;
const consentCheckbox = reviewForm.querySelector<HTMLInputElement>('#review-form-consent');
const isConsentValid = consentCheckbox ? consentCheckbox.checked : true;
const isValid = isRoleValid && isCaseTypeValid && isRatingValid && isTextValid;
const isValid = isRoleValid && isCaseTypeValid && isRatingValid && isTextValid && isConsentValid;
if (isValid) {
submitBtn.disabled = false;
submitBtn.classList.remove("bg-[var(--color-gray-600)", "text-[var(--color-gray-400)]", "cursor-not-allowed", "opacity-50");
submitBtn.classList.remove("bg-[var(--color-gray-600)]", "text-[var(--color-gray-400)]", "cursor-not-allowed", "opacity-50");
submitBtn.classList.add("bg-[var(--color-gold)]", "hover:bg-[var(--color-gold-hover)]", "text-[var(--color-white)]", "hover:shadow-lg", "hover:shadow-[var(--color-gold)]/30", "hover:cursor-pointer");
} else {
submitBtn.disabled = true;
@ -577,6 +588,8 @@ import Toast from "@components/base/Toast.astro";
const isRoleValid = validateRoleField();
const isCaseTypeValid = validateCaseTypeField();
const isTextValid = validateTextField();
const consentCheckbox = reviewForm.querySelector<HTMLInputElement>('#review-form-consent');
const isConsentValid = consentCheckbox ? consentCheckbox.checked : false;
// Проверка рейтинга
if (!ratingInput.value || ratingInput.value === "0") {
@ -587,7 +600,7 @@ import Toast from "@components/base/Toast.astro";
return;
}
if (!isRoleValid || !isCaseTypeValid || !isTextValid) {
if (!isRoleValid || !isCaseTypeValid || !isTextValid || !isConsentValid) {
if (typeof window.showToast === "function") {
window.showToast("Проверьте правильность заполнения полей", "error", 3000);
}

View file

@ -1,5 +1,6 @@
---
import Toast from "@components/base/Toast.astro";
import ConsentCheckbox from "@components/base/ConsentCheckbox.astro";
---
<!-- Модальное окно для написания отзыва -->
@ -154,6 +155,8 @@ import Toast from "@components/base/Toast.astro";
Отправить отзыв
</button>
</div>
<ConsentCheckbox formId="review-form" />
</form>
</div>
</div>
@ -374,6 +377,12 @@ import Toast from "@components/base/Toast.astro";
validateForm();
});
// Чекбокс согласия
const consentCheckbox = reviewForm.querySelector<HTMLInputElement>('#review-form-consent');
consentCheckbox?.addEventListener('change', () => {
validateForm();
});
// Валидация должности
const validateRoleField = () => {
if (!roleInput) return false;
@ -458,7 +467,7 @@ import Toast from "@components/base/Toast.astro";
}
};
// Общая валидация формы и активация кнопки
// Общая валидация формы и активация кнопки
const validateForm = () => {
if (!submitBtn) return;
@ -466,12 +475,14 @@ import Toast from "@components/base/Toast.astro";
const isCaseTypeValid = caseTypeSelect && caseTypeSelect.value !== "";
const isRatingValid = ratingInput.value && ratingInput.value !== "0";
const isTextValid = reviewText && reviewText.value.trim().length >= MIN_TEXT_LENGTH && reviewText.value.length <= MAX_CHARS;
const consentCheckbox = reviewForm.querySelector<HTMLInputElement>('#review-form-consent');
const isConsentValid = consentCheckbox ? consentCheckbox.checked : true;
const isValid = isRoleValid && isCaseTypeValid && isRatingValid && isTextValid;
const isValid = isRoleValid && isCaseTypeValid && isRatingValid && isTextValid && isConsentValid;
if (isValid) {
submitBtn.disabled = false;
submitBtn.classList.remove("bg-[var(--color-gray-600)", "text-[var(--color-gray-400)]", "cursor-not-allowed", "opacity-50");
submitBtn.classList.remove("bg-[var(--color-gray-600)]", "text-[var(--color-gray-400)]", "cursor-not-allowed", "opacity-50");
submitBtn.classList.add("bg-[var(--color-gold)]", "hover:bg-[var(--color-gold-hover)]", "text-[var(--color-white)]", "hover:shadow-lg", "hover:shadow-[var(--color-gold)]/30", "hover:cursor-pointer");
} else {
submitBtn.disabled = true;
@ -488,6 +499,8 @@ import Toast from "@components/base/Toast.astro";
const isRoleValid = validateRoleField();
const isCaseTypeValid = validateCaseTypeField();
const isTextValid = validateTextField();
const consentCheckbox = reviewForm.querySelector<HTMLInputElement>('#review-form-consent');
const isConsentValid = consentCheckbox ? consentCheckbox.checked : false;
// Проверка рейтинга
if (!ratingInput.value || ratingInput.value === "0") {
@ -498,7 +511,7 @@ import Toast from "@components/base/Toast.astro";
return;
}
if (!isRoleValid || !isCaseTypeValid || !isTextValid) {
if (!isRoleValid || !isCaseTypeValid || !isTextValid || !isConsentValid) {
if (typeof window.showToast === "function") {
window.showToast("Проверьте правильность заполнения полей", "error", 3000);
}

View file

@ -1,6 +1,7 @@
---
import { CONTACT_CONSTANTS } from "@constants/constants.ts";
import SocialIcons from "@components/base/SocialIcons.astro";
import ConsentCheckbox from "@components/base/ConsentCheckbox.astro";
---
<section

View file

@ -1,6 +1,7 @@
---
import { CONTACT_CONSTANTS } from "@constants/constants.ts";
import SocialIcons from "@components/base/SocialIcons.astro";
import ConsentCheckbox from "@components/base/ConsentCheckbox.astro";
const benefits = [
{ value: "15", label: "минут", desc: "Среднее время ответа" },
@ -58,17 +59,20 @@ const benefits = [
<form
class="space-y-3 md:space-y-4"
onsubmit="event.preventDefault(); alert('Заявка отправлена!');"
id="emergency-form"
>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 md:gap-4">
<input
type="text"
placeholder="Ваше имя"
required
minlength="2"
class="w-full px-4 py-3 bg-[var(--color-navy-dark)] border border-[var(--color-gray-600)]/20 rounded-xl text-[var(--color-white)] placeholder-[var(--color-gray-600)] focus:border-[var(--color-gold)] focus:outline-none transition-colors text-sm md:text-base"
/>
<input
type="tel"
placeholder="Телефон"
required
class="w-full px-4 py-3 bg-[var(--color-navy-dark)] border border-[var(--color-gray-600)]/20 rounded-xl text-[var(--color-white)] placeholder-[var(--color-gray-600)] focus:border-[var(--color-gold)] focus:outline-none transition-colors text-sm md:text-base"
/>
</div>
@ -108,11 +112,9 @@ const benefits = [
d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
</svg>
</button>
</form>
<p class="text-xs text-[var(--color-gray-600)] mt-4 text-center">
Нажимая кнопку, вы соглашаетесь с политикой конфиденциальности
</p>
<ConsentCheckbox formId="emergency-form" />
</form>
</div>
</div>

View file

@ -1,6 +1,7 @@
---
import { CONTACT_CONSTANTS } from "@constants/constants.ts";
import SocialIcons from "@components/base/SocialIcons.astro";
import ConsentCheckbox from "@components/base/ConsentCheckbox.astro";
---
<section id="contact" class="py-24 bg-[var(--color-navy-dark)] relative overflow-hidden">
@ -91,7 +92,7 @@ import SocialIcons from "@components/base/SocialIcons.astro";
<h3 class="text-2xl font-bold text-[var(--color-white)] mb-2">Заявка на консультацию</h3>
<p class="text-[var(--color-gray-500)] text-sm mb-6">Заполните форму — перезвоню в течение 15 минут</p>
<form class="space-y-4" onsubmit="event.preventDefault(); alert('Заявка отправлена!');">
<form class="space-y-4" id="final-cta-form">
<div>
<input
type="text"
@ -128,7 +129,7 @@ import SocialIcons from "@components/base/SocialIcons.astro";
<button
type="submit"
class="w-full py-4 bg-[var(--color-gold)] hover:bg-[var(--color-gold-hover)] text-[var(--color-white)] font-bold rounded-lg transition-all hover:shadow-[0_0_30px_rgba(191,155,88,0.4)] flex items-center justify-center gap-3 group"
class="w-full py-4 bg-[var(--color-gold)] hover:bg-[var(--color-gold-hover)] text-[var(--color-white)] font-bold rounded-lg transition-all hover:shadow-[0_0_30px_rgba(191,155,88,0.4)] flex items-center justify-center gap-3 group"
>
<span>Отправить заявку</span>
<svg class="w-5 h-5 group-hover:translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@ -136,9 +137,7 @@ import SocialIcons from "@components/base/SocialIcons.astro";
</svg>
</button>
<p class="text-xs text-[var(--color-gray-600)] text-center">
Нажимая кнопку, вы соглашаетесь с политикой конфиденциальности
</p>
<ConsentCheckbox formId="final-cta-form" />
</form>
</div>
</div>

View file

@ -1,6 +1,7 @@
---
import { CONTACT_CONSTANTS } from "@constants/constants.ts";
import SocialIcons from "@components/base/SocialIcons.astro";
import ConsentCheckbox from "@components/base/ConsentCheckbox.astro";
const benefits = [
{ value: "30", label: "минут", desc: "Среднее время ответа" },
@ -52,17 +53,20 @@ const benefits = [
<form
class="space-y-3 md:space-y-4"
onsubmit="event.preventDefault(); alert('Заявка отправлена!');"
id="emergency-form"
>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 md:gap-4">
<input
type="text"
placeholder="Ваше имя"
required
minlength="2"
class="w-full px-4 py-3 bg-[var(--color-navy-dark)] border border-[var(--color-gray-600)]/20 rounded-xl text-[var(--color-white)] placeholder-[var(--color-gray-600)] focus:border-[var(--color-gold)] focus:outline-none transition-colors text-sm md:text-base"
/>
<input
type="tel"
placeholder="Телефон"
required
class="w-full px-4 py-3 bg-[var(--color-navy-dark)] border border-[var(--color-gray-600)]/20 rounded-xl text-[var(--color-white)] placeholder-[var(--color-gray-600)] focus:border-[var(--color-gold)] focus:outline-none transition-colors text-sm md:text-base"
/>
</div>
@ -100,11 +104,9 @@ const benefits = [
d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
</svg>
</button>
</form>
<p class="text-xs text-[var(--color-gray-600)] mt-4 text-center">
Нажимая кнопку, вы соглашаетесь с политикой конфиденциальности
</p>
<ConsentCheckbox formId="emergency-form" />
</form>
</div>
</div>

View file

@ -1,6 +1,7 @@
---
import { CONTACT_CONSTANTS } from "@constants/constants.ts";
import SocialIcons from "@components/base/SocialIcons.astro";
import ConsentCheckbox from "@components/base/ConsentCheckbox.astro";
---
<section id="contact" class="py-24 bg-[var(--color-navy-dark)] relative overflow-hidden">
@ -111,7 +112,7 @@ import SocialIcons from "@components/base/SocialIcons.astro";
</div>
</div>
<form class="space-y-4" onsubmit="event.preventDefault(); alert('Заявка отправлена!');">
<form class="space-y-4" id="urgent-cta-form">
<div>
<input
type="text"
@ -156,9 +157,7 @@ import SocialIcons from "@components/base/SocialIcons.astro";
<span>Срочный вызов адвоката</span>
</button>
<p class="text-xs text-[var(--color-gray-600)] text-center">
Нажимая кнопку, вы соглашаетесь с политикой конфиденциальности
</p>
<ConsentCheckbox formId="urgent-cta-form" />
</form>
<!-- Гарантия -->

View file

@ -5,6 +5,7 @@ interface Props {
const { title = "Проверьте свои штрафы" } = Astro.props;
---
import ConsentCheckbox from "@components/base/ConsentCheckbox.astro";
<section id="fine-check" class="py-24 bg-white relative overflow-hidden">
<!-- Декоративный фон -->
@ -22,7 +23,7 @@ const { title = "Проверьте свои штрафы" } = Astro.props;
Узнайте о своих штрафах ГИБДД за 30 секунд. Мы подгрузим данные из официальных баз и рассчитаем риски.
</p>
<form class="space-y-6 bg-gray-50 p-8 rounded-3xl border border-gray-100" onsubmit="event.preventDefault(); alert('В реальном проекте здесь будет интеграция с API ГИБДД');">
<form class="space-y-6 bg-gray-50 p-8 rounded-3xl border border-gray-100" id="fine-check-form">
<div>
<label class="block text-xs font-bold text-gray-500 uppercase tracking-wider mb-2">Номер водительского удостоверения</label>
<input
@ -53,6 +54,8 @@ const { title = "Проверьте свои штрафы" } = Astro.props;
Проверить штрафы
</button>
<ConsentCheckbox formId="fine-check-form" />
<p class="text-xs text-gray-400 text-center">
Данные обрабатываются конфиденциально. Проверка бесплатна.
</p>

View file

@ -1,6 +1,7 @@
---
import { CONTACT_CONSTANTS } from "@constants/constants.ts";
import SocialIcons from "@components/base/SocialIcons.astro";
import ConsentCheckbox from "@components/base/ConsentCheckbox.astro";
const situations = [
"Супруг скрывает имущество",

View file

@ -1,6 +1,7 @@
---
import { CONTACT_CONSTANTS } from "@constants/constants.ts";
import SocialIcons from "@components/base/SocialIcons.astro";
import ConsentCheckbox from "@components/base/ConsentCheckbox.astro";
const panicSituations = [
{
@ -173,7 +174,7 @@ const panicSituations = [
<form
class="space-y-4"
onsubmit="event.preventDefault(); this.innerHTML='<div class=\'text-center py-8\'><div class=\'w-16 h-16 mx-auto mb-4 rounded-full bg-green-500/20 flex items-center justify-center animate-bounce\'><svg class=\'w-8 h-8 text-green-400\' fill=\'none\' stroke=\'currentColor\' viewBox=\'0 0 24 24\'><path stroke-linecap=\'round\' stroke-linejoin=\'round\' stroke-width=\'2\' d=\'M5 13l4 4L19 7\'></path></svg></div><p class=\'text-[var(--color-white)] font-bold text-lg\'>Заявка получена!</p><p class=\'text-[var(--color-gold)] font-bold\'>Юрист уже в пути</p></div>';"
id="emergency-form"
>
<div class="grid grid-cols-2 gap-4">
<input
@ -226,6 +227,8 @@ const panicSituations = [
d="M13 10V3L4 14h7v7l9-11h-7z"></path>
</svg>
</button>
<ConsentCheckbox formId="emergency-form" />
</form>
<div