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

This commit is contained in:
Web-serfer 2026-04-19 19:32:53 +05:00
parent d14d67893b
commit beeec4740e
3 changed files with 129 additions and 9 deletions

View file

@ -42,6 +42,7 @@ const {
role,
tabindex,
title,
'data-modal-target': modalTarget,
...dataAttrs // Все data-* атрибуты
}: Props = Astro.props;
@ -91,10 +92,26 @@ const commonAttrs = Object.fromEntries(
class={classes}
disabled={disabled}
{...commonAttrs}
{...(modalTarget ? { 'data-modal-target': modalTarget } : {})}
>
<slot />
</button>
)}
)}
{modalTarget && (
<script define:vars={{ modalTarget }}>
document.querySelectorAll(`[data-modal-target="${modalTarget}"]`).forEach(btn => {
btn.addEventListener('click', () => {
const modal = document.getElementById(modalTarget);
if (modal) {
modal.classList.add('active');
modal.setAttribute('aria-hidden', 'false');
document.body.style.overflow = 'hidden';
}
});
});
</script>
)}
<style>
.btn {

View file

@ -17,7 +17,10 @@ const title = 'Бесплатная консультация';
Оставьте свои контактные данные, и мы свяжемся с вами в течение 15 минут
</p>
<form class="modal-form" action="#" method="POST" id="consultation-form">
<!-- Honeypot поле для защиты от спама -->
<input type="text" name="website" id="website-field" class="hidden-field" tabindex="-1" autocomplete="off" />
<form class="modal-form" action="#" method="POST" id="consultation-form">
<div class="form-group">
<label for="name" class="form-label">Ваше имя</label>
<input
@ -101,11 +104,45 @@ const title = 'Бесплатная консультация';
});
// Обработка отправки формы
form?.addEventListener('submit', (e) => {
form?.addEventListener('submit', async (e) => {
e.preventDefault();
// Здесь добавь логику отправки формы
console.log('Форма отправлена');
closeModal();
const formData = new FormData(form);
const website = formData.get('website');
if (website) {
console.log('Spam detected');
return;
}
const name = formData.get('name');
const phone = formData.get('phone');
if (!name || !phone) {
alert('Пожалуйста, заполните имя и телефон');
return;
}
try {
const response = await fetch('/api/consultation', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, phone })
});
const result = await response.json();
if (result.success) {
alert('Спасибо! Мы свяжемся с вами в течение 15 минут.');
form.reset();
closeModal();
} else {
alert('Ошибка: ' + result.error);
}
} catch (error) {
console.error('Error:', error);
alert('Ошибка отправки. Попробуйте позже.');
}
});
// Для Astro View Transitions
@ -273,6 +310,13 @@ const title = 'Бесплатная консультация';
text-decoration: none;
}
.hidden-field {
position: absolute;
left: -9999px;
opacity: 0;
pointer-events: none;
}
@media (max-width: 480px) {
.modal-content {
padding: 32px 24px 24px;

View file

@ -1,11 +1,56 @@
import type { APIRoute } from 'astro';
import { pb } from '../../lib/pb';
const RATE_LIMIT_WINDOW = 60 * 1000;
const MAX_REQUESTS = 3;
const requestCounts = new Map<string, { count: number; timestamp: number }>();
function checkRateLimit(ip: string): boolean {
const now = Date.now();
const record = requestCounts.get(ip);
if (!record || now - record.timestamp > RATE_LIMIT_WINDOW) {
requestCounts.set(ip, { count: 1, timestamp: now });
return true;
}
if (record.count >= MAX_REQUESTS) {
return false;
}
record.count++;
return true;
}
function validatePhone(phone: string): boolean {
const cleaned = phone.replace(/\D/g, '');
return cleaned.length >= 10 && cleaned.length <= 15;
}
export const POST: APIRoute = async ({ request }) => {
const clientIP = request.headers.get('x-forwarded-for')?.split(',')[0] ||
request.headers.get('x-real-ip') ||
'unknown';
if (!checkRateLimit(clientIP)) {
return new Response(JSON.stringify({
success: false,
error: 'Слишком много запросов. Попробуйте позже.'
}), { status: 429 });
}
try {
const data = await request.json();
const { name, phone, service } = data;
const { name, phone, service, website } = data;
if (website) {
return new Response(JSON.stringify({
success: false,
error: 'Спам обнаружен'
}), { status: 400 });
}
if (!name || !phone) {
return new Response(JSON.stringify({
@ -14,9 +59,23 @@ export const POST: APIRoute = async ({ request }) => {
}), { status: 400 });
}
if (name.length < 2 || name.length > 100) {
return new Response(JSON.stringify({
success: false,
error: 'Некорректное имя'
}), { status: 400 });
}
if (!validatePhone(phone)) {
return new Response(JSON.stringify({
success: false,
error: 'Некорректный номер телефона'
}), { status: 400 });
}
const record = await pb.collection('consultations').create({
name,
phone,
name: name.trim(),
phone: phone.replace(/\D/g, ''),
service: service || '',
status: 'new',
created_at: new Date().toISOString(),