Новые изменения в компоенты
This commit is contained in:
parent
762727290f
commit
1ee9cbe9fe
27 changed files with 845 additions and 229 deletions
|
|
@ -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>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -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] Валидация не пройдена");
|
||||
|
|
|
|||
|
|
@ -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] Валидация не пройдена");
|
||||
|
|
|
|||
|
|
@ -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", () => {
|
||||
|
|
|
|||
29
frontend/src/components/base/ConsentCheckbox.astro
Normal file
29
frontend/src/components/base/ConsentCheckbox.astro
Normal 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>
|
||||
|
|
@ -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", () => {
|
||||
|
|
|
|||
78
frontend/src/components/base/CookieBanner.astro
Normal file
78
frontend/src/components/base/CookieBanner.astro
Normal 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>
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
<!-- Гарантия -->
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 = [
|
||||
"Супруг скрывает имущество",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue