|
@@ -11,23 +11,14 @@ import SharedHeader from '../components/SharedHeader';
|
|
|
import StepProgress from '../components/StepProgress';
|
|
import StepProgress from '../components/StepProgress';
|
|
|
import { useTranslation } from 'react-i18next';
|
|
import { useTranslation } from 'react-i18next';
|
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { useNavigate } from 'react-router-dom';
|
|
|
-import { useState, useEffect } from 'react';
|
|
|
|
|
|
|
+import { useState } from 'react';
|
|
|
|
|
+import { useGlobalSettings } from '../hooks/useGlobalSettings';
|
|
|
|
|
+import { useEmailSubmission } from '../hooks/useEmailSubmission';
|
|
|
|
|
|
|
|
const Step1 = () => {
|
|
const Step1 = () => {
|
|
|
- const { t, i18n } = useTranslation();
|
|
|
|
|
|
|
+ const { t } = useTranslation();
|
|
|
const navigate = useNavigate();
|
|
const navigate = useNavigate();
|
|
|
-
|
|
|
|
|
- // Theme state
|
|
|
|
|
- const [isDark, setIsDark] = useState(localStorage.getItem('theme') === 'dark');
|
|
|
|
|
-
|
|
|
|
|
- useEffect(() => {
|
|
|
|
|
- // Sync theme class to document body if needed, or just handle locally
|
|
|
|
|
- // For consistency with SimpleHome, we might want to store in localStorage
|
|
|
|
|
- localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
|
|
|
|
- }, [isDark]);
|
|
|
|
|
-
|
|
|
|
|
- const toggleTheme = () => setIsDark(!isDark);
|
|
|
|
|
- const toggleLang = () => i18n.changeLanguage(i18n.language === 'zh' ? 'en' : 'zh');
|
|
|
|
|
|
|
+ const { isDark, currentLang, toggleTheme, toggleLang, themeClasses } = useGlobalSettings();
|
|
|
|
|
|
|
|
const scrollToSection = (id: string) => {
|
|
const scrollToSection = (id: string) => {
|
|
|
const element = document.getElementById(id);
|
|
const element = document.getElementById(id);
|
|
@@ -36,16 +27,14 @@ const Step1 = () => {
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- const currentLang = i18n.language === 'zh' ? 'zh' : 'en';
|
|
|
|
|
-
|
|
|
|
|
// Form State
|
|
// Form State
|
|
|
const [formData, setFormData] = useState({
|
|
const [formData, setFormData] = useState({
|
|
|
name: '',
|
|
name: '',
|
|
|
contact: '',
|
|
contact: '',
|
|
|
industry: ''
|
|
industry: ''
|
|
|
});
|
|
});
|
|
|
- const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
|
|
|
|
|
- const [message, setMessage] = useState('');
|
|
|
|
|
|
|
+ const { submitEmail, status, message } = useEmailSubmission();
|
|
|
|
|
+ const [localValidationError, setLocalValidationError] = useState<string>('');
|
|
|
|
|
|
|
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
|
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
|
|
|
const { name, value } = e.target;
|
|
const { name, value } = e.target;
|
|
@@ -54,74 +43,31 @@ const Step1 = () => {
|
|
|
|
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
|
e.preventDefault();
|
|
e.preventDefault();
|
|
|
|
|
+ setLocalValidationError('');
|
|
|
|
|
|
|
|
// Basic validation
|
|
// Basic validation
|
|
|
if (!formData.contact) {
|
|
if (!formData.contact) {
|
|
|
- setStatus('error');
|
|
|
|
|
- setMessage(currentLang === 'zh' ? '请填写联系方式' : 'Please enter your contact info');
|
|
|
|
|
|
|
+ setLocalValidationError(currentLang === 'zh' ? '请填写联系方式' : 'Please enter your contact info');
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Email validation (assuming contact is email for the API)
|
|
|
|
|
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
|
|
|
- if (!emailRegex.test(formData.contact)) {
|
|
|
|
|
- setStatus('error');
|
|
|
|
|
- setMessage(currentLang === 'zh' ? '请输入有效的邮箱地址' : 'Please enter a valid email');
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 使用新的API服务提交数据
|
|
|
|
|
+ const payload = {
|
|
|
|
|
+ email: formData.contact,
|
|
|
|
|
+ name: formData.name,
|
|
|
|
|
+ industry: formData.industry,
|
|
|
|
|
+ source: 'Step1_Consulting'
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
- setStatus('loading');
|
|
|
|
|
- setMessage('');
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- // We verify email format, and pass other fields as extra data
|
|
|
|
|
- const payload = {
|
|
|
|
|
- email: formData.contact,
|
|
|
|
|
- name: formData.name,
|
|
|
|
|
- industry: formData.industry,
|
|
|
|
|
- source: 'Step1_Consulting'
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const response = await fetch('/api/send-welcome-email', {
|
|
|
|
|
- method: 'POST',
|
|
|
|
|
- headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
- body: JSON.stringify(payload),
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- const data = await response.json();
|
|
|
|
|
-
|
|
|
|
|
- if (data.success) {
|
|
|
|
|
- setStatus('success');
|
|
|
|
|
- setMessage(data.message || (currentLang === 'zh' ? '提交成功!我们会尽快联系您。' : 'Submitted successfully! We will contact you soon.'));
|
|
|
|
|
- setFormData({ name: '', contact: '', industry: '' });
|
|
|
|
|
- } else {
|
|
|
|
|
- setStatus('error');
|
|
|
|
|
- setMessage(data.message || (currentLang === 'zh' ? '提交失败,请稍后重试。' : 'Submission failed. Please try again later.'));
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- setStatus('error');
|
|
|
|
|
- setMessage(currentLang === 'zh' ? '网络错误,请稍后重试。' : 'Network error. Please try again later.');
|
|
|
|
|
|
|
+ const response = await submitEmail(payload);
|
|
|
|
|
+
|
|
|
|
|
+ // 如果提交成功,清空表单
|
|
|
|
|
+ if (response.success) {
|
|
|
|
|
+ setFormData({ name: '', contact: '', industry: '' });
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- // Theme utility classes
|
|
|
|
|
- const themeClasses = {
|
|
|
|
|
- bg: isDark ? 'bg-stone-900' : 'bg-white',
|
|
|
|
|
- text: isDark ? 'text-stone-100' : 'text-slate-900',
|
|
|
|
|
- textMuted: isDark ? 'text-stone-400' : 'text-slate-600',
|
|
|
|
|
- textLight: isDark ? 'text-stone-500' : 'text-slate-400',
|
|
|
|
|
- border: isDark ? 'border-stone-800' : 'border-slate-100',
|
|
|
|
|
- borderStrong: isDark ? 'border-stone-700' : 'border-slate-200',
|
|
|
|
|
- bgSecondary: isDark ? 'bg-stone-800' : 'bg-slate-50',
|
|
|
|
|
- cardBg: isDark ? 'bg-stone-900' : 'bg-white',
|
|
|
|
|
- cardHover: isDark ? 'hover:bg-stone-800' : 'hover:bg-white',
|
|
|
|
|
- inputBorder: isDark ? 'border-stone-700 focus:border-stone-500' : 'border-slate-300 focus:border-black',
|
|
|
|
|
- buttonPrimary: isDark ? 'bg-stone-100 text-stone-900 hover:bg-white' : 'bg-black text-white hover:bg-slate-800',
|
|
|
|
|
- highlight: isDark ? 'text-stone-100' : 'text-black',
|
|
|
|
|
- codeBlock: isDark ? 'bg-stone-800 border-stone-700 text-stone-400' : 'bg-slate-50 border-slate-100 text-slate-500',
|
|
|
|
|
- marker: isDark ? 'bg-blue-900 text-blue-200' : 'bg-yellow-200',
|
|
|
|
|
- shadow: isDark ? 'shadow-none' : 'shadow-sm',
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ // Theme classes are now provided by useGlobalSettings hook
|
|
|
|
|
|
|
|
return (
|
|
return (
|
|
|
<div className={`min-h-screen font-sans selection:bg-black selection:text-white transition-colors duration-300 ${themeClasses.bg} ${themeClasses.text}`}>
|
|
<div className={`min-h-screen font-sans selection:bg-black selection:text-white transition-colors duration-300 ${themeClasses.bg} ${themeClasses.text}`}>
|
|
@@ -431,9 +377,9 @@ const Step1 = () => {
|
|
|
</select>
|
|
</select>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- {message && (
|
|
|
|
|
|
|
+ {(message || localValidationError) && (
|
|
|
<div className={`text-sm font-medium ${status === 'success' ? 'text-green-500' : 'text-red-500'}`}>
|
|
<div className={`text-sm font-medium ${status === 'success' ? 'text-green-500' : 'text-red-500'}`}>
|
|
|
- {message}
|
|
|
|
|
|
|
+ {localValidationError || message}
|
|
|
</div>
|
|
</div>
|
|
|
)}
|
|
)}
|
|
|
|
|
|