feat: insert order to Supabase on lead form submit
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
import React, { useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Send, ShieldCheck } from 'lucide-react';
|
||||
import { supabase } from '../lib/supabase';
|
||||
|
||||
const LeadForm = () => {
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
phone: '',
|
||||
objectType: ''
|
||||
});
|
||||
|
||||
const handleChange = (e) => {
|
||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||
};
|
||||
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
setIsSubmitting(true);
|
||||
|
||||
const botToken = import.meta.env.VITE_TG_BOT_TOKEN;
|
||||
const chatId = import.meta.env.VITE_TG_CHAT_ID;
|
||||
|
||||
if (!botToken || !chatId) {
|
||||
console.warn("Telegram bot token or chat ID is missing.");
|
||||
alert("ВНИМАНИЕ: Не настроена интеграция с Telegram (отсутствуют ключи в .env). Заявка показана в консоли.");
|
||||
console.log("Данные заявки:", formData);
|
||||
setIsSubmitting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const text = `🚨 Новая заявка на замер и расчет!\n\n👤 Имя/Организация: ${formData.name}\n📞 Телефон: ${formData.phone}\n🏢 Тип объекта: ${formData.objectType}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
chat_id: chatId,
|
||||
text: text,
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Write to Supabase (non-blocking — Telegram already succeeded)
|
||||
try {
|
||||
await supabase.from('orders').insert({
|
||||
name: formData.name,
|
||||
phone: formData.phone,
|
||||
object_type: formData.objectType,
|
||||
status: 'новая',
|
||||
source: 'form',
|
||||
});
|
||||
} catch (dbErr) {
|
||||
console.error('Supabase insert failed:', dbErr);
|
||||
}
|
||||
|
||||
alert("Заявка успешно отправлена! Наши специалисты свяжутся с вами в ближайшее время для согласования замера.");
|
||||
setFormData({ name: '', phone: '', objectType: '' });
|
||||
} else {
|
||||
alert("Произошла ошибка при отправке заявки. Пожалуйста, попробуйте позже или позвоните нам напрямую.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Ошибка при отправке в Telegram:", error);
|
||||
alert("Ошибка сети. Проверьте подключение к интернету и попробуйте снова.");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section id="contact" style={{
|
||||
position: 'relative',
|
||||
background: 'linear-gradient(135deg, var(--bg-light) 0%, rgba(14, 165, 233, 0.05) 100%)',
|
||||
padding: '120px 0'
|
||||
}}>
|
||||
{/* Decorative Light Elements */}
|
||||
<div style={{
|
||||
position: 'absolute', top: 0, left: 0, width: '100%', height: '100%',
|
||||
backgroundImage: 'radial-gradient(circle at 15% 50%, rgba(245, 158, 11, 0.08), transparent 25%), radial-gradient(circle at 85% 30%, rgba(14, 165, 233, 0.08), transparent 25%)',
|
||||
pointerEvents: 'none'
|
||||
}} />
|
||||
|
||||
<div className="container" style={{ position: 'relative', zIndex: 1 }}>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '60px', justifyContent: 'center' }}>
|
||||
|
||||
<div style={{ flex: '1 1 400px', maxWidth: '550px' }}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, type: "spring" }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<h2 style={{ fontSize: '3.5rem', marginBottom: '25px', color: 'var(--text-main)' }}>
|
||||
ЗАЯВКА НА <span className="text-accent-blue">ЗАМЕР</span>
|
||||
</h2>
|
||||
<p style={{ color: 'var(--text-muted)', fontSize: '1.25rem', marginBottom: '45px', lineHeight: '1.6' }}>
|
||||
Оставьте заявку на бесплатный выезд инженера для замера и расчета предварительной стоимости бронирования ваших окон.
|
||||
</p>
|
||||
|
||||
<ul style={{ listStyle: 'none', padding: 0, display: 'flex', flexDirection: 'column', gap: '25px' }}>
|
||||
<motion.li whileHover={{ x: 10 }} style={{ display: 'flex', gap: '20px', alignItems: 'center' }}>
|
||||
<div style={{ background: 'rgba(14, 165, 233, 0.1)', padding: '12px', borderRadius: '50%' }}>
|
||||
<ShieldCheck className="text-accent-blue" size={24} />
|
||||
</div>
|
||||
<span style={{ fontSize: '1.2rem', fontWeight: 600, color: 'var(--text-main)' }}>Анализ векторов угроз</span>
|
||||
</motion.li>
|
||||
<motion.li whileHover={{ x: 10 }} style={{ display: 'flex', gap: '20px', alignItems: 'center' }}>
|
||||
<div style={{ background: 'rgba(14, 165, 233, 0.1)', padding: '12px', borderRadius: '50%' }}>
|
||||
<ShieldCheck className="text-accent-blue" size={24} />
|
||||
</div>
|
||||
<span style={{ fontSize: '1.2rem', fontWeight: 600, color: 'var(--text-main)' }}>Расчет необходимых материалов</span>
|
||||
</motion.li>
|
||||
<motion.li whileHover={{ x: 10 }} style={{ display: 'flex', gap: '20px', alignItems: 'center' }}>
|
||||
<div style={{ background: 'rgba(14, 165, 233, 0.1)', padding: '12px', borderRadius: '50%' }}>
|
||||
<ShieldCheck className="text-accent-blue" size={24} />
|
||||
</div>
|
||||
<span style={{ fontSize: '1.2rem', fontWeight: 600, color: 'var(--text-main)' }}>Планирование логистики монтажа</span>
|
||||
</motion.li>
|
||||
</ul>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
<div style={{ flex: '1 1 400px', maxWidth: '500px' }}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2, type: "spring" }}
|
||||
viewport={{ once: true }}
|
||||
className="glass-panel"
|
||||
style={{ padding: '45px', border: '1px solid var(--border-light)', boxShadow: '0 25px 50px rgba(15, 23, 42, 0.05)' }}
|
||||
>
|
||||
<form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: '25px' }}>
|
||||
|
||||
<div>
|
||||
<label style={{ display: 'block', marginBottom: '10px', fontSize: '0.95rem', fontWeight: 700, color: 'var(--text-muted)', textTransform: 'uppercase' }}>ФИО / Название организации</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
value={formData.name}
|
||||
onChange={handleChange}
|
||||
required
|
||||
style={{
|
||||
width: '100%', padding: '16px',
|
||||
background: 'var(--bg-lighter)',
|
||||
border: '2px solid var(--border-light)',
|
||||
borderRadius: '8px',
|
||||
color: 'var(--text-main)',
|
||||
fontSize: '1.05rem',
|
||||
outline: 'none',
|
||||
transition: 'border-color 0.3s'
|
||||
}}
|
||||
onFocus={(e) => e.target.style.borderColor = 'var(--accent-blue)'}
|
||||
onBlur={(e) => e.target.style.borderColor = 'var(--border-light)'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label style={{ display: 'block', marginBottom: '10px', fontSize: '0.95rem', fontWeight: 700, color: 'var(--text-muted)', textTransform: 'uppercase' }}>Контактный телефон</label>
|
||||
<input
|
||||
type="tel"
|
||||
name="phone"
|
||||
value={formData.phone}
|
||||
onChange={handleChange}
|
||||
required
|
||||
style={{
|
||||
width: '100%', padding: '16px',
|
||||
background: 'var(--bg-lighter)',
|
||||
border: '2px solid var(--border-light)',
|
||||
borderRadius: '8px',
|
||||
color: 'var(--text-main)',
|
||||
fontSize: '1.05rem',
|
||||
outline: 'none',
|
||||
transition: 'border-color 0.3s'
|
||||
}}
|
||||
onFocus={(e) => e.target.style.borderColor = 'var(--accent-blue)'}
|
||||
onBlur={(e) => e.target.style.borderColor = 'var(--border-light)'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label style={{ display: 'block', marginBottom: '10px', fontSize: '0.95rem', fontWeight: 700, color: 'var(--text-muted)', textTransform: 'uppercase' }}>Тип объекта</label>
|
||||
<select
|
||||
name="objectType"
|
||||
value={formData.objectType}
|
||||
onChange={handleChange}
|
||||
required
|
||||
style={{
|
||||
width: '100%', padding: '16px',
|
||||
background: 'var(--bg-lighter)',
|
||||
border: '2px solid var(--border-light)',
|
||||
borderRadius: '8px',
|
||||
color: 'var(--text-main)',
|
||||
fontSize: '1.05rem',
|
||||
outline: 'none',
|
||||
appearance: 'none',
|
||||
transition: 'border-color 0.3s',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
onFocus={(e) => e.target.style.borderColor = 'var(--accent-blue)'}
|
||||
onBlur={(e) => e.target.style.borderColor = 'var(--border-light)'}
|
||||
>
|
||||
<option value="" disabled>Выберите тип объекта</option>
|
||||
<option value="industrial">Промышленный объект</option>
|
||||
<option value="commercial">Коммерческая штаб-квартира</option>
|
||||
<option value="infrastructure">Критическая инфраструктура</option>
|
||||
<option value="residential">Частная резиденция</option>
|
||||
<option value="other">Другое</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className="btn-primary"
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
gap: '12px',
|
||||
marginTop: '15px',
|
||||
opacity: isSubmitting ? 0.7 : 1,
|
||||
cursor: isSubmitting ? 'not-allowed' : 'pointer'
|
||||
}}
|
||||
>
|
||||
<span>{isSubmitting ? 'Отправка...' : 'Оставить заявку'}</span>
|
||||
<Send size={20} />
|
||||
</motion.button>
|
||||
|
||||
</form>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default LeadForm;
|
||||
Reference in New Issue
Block a user