From ca2732a97c145bcc15a412e36275745c40890090 Mon Sep 17 00:00:00 2001 From: SiteAgent Bot Date: Thu, 22 Jan 2026 17:13:59 +0800 Subject: [PATCH] manual save(2026-01-22 17:13) --- src/App.tsx | 3 + src/components/PrivacyConsentModal.tsx | 127 +++++++++++++++++++++ src/pages/Assistant.tsx | 118 ++++++++++++++++---- src/pages/Privacy.tsx | 148 +++++++++++++++++++++++++ 4 files changed, 375 insertions(+), 21 deletions(-) create mode 100644 src/components/PrivacyConsentModal.tsx create mode 100644 src/pages/Privacy.tsx diff --git a/src/App.tsx b/src/App.tsx index 2e44a04..b64af25 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,6 +8,7 @@ import { Contact } from './pages/Contact' import { Cases } from './pages/Cases' import { Learning } from './pages/Learning' import { Assistant } from './pages/Assistant' +import { Privacy } from './pages/Privacy' // 页面切换动画配置 const pageVariants = { @@ -74,6 +75,7 @@ function App() { } /> } /> } /> + } /> } /> {/* English routes */} @@ -84,6 +86,7 @@ function App() { } /> } /> } /> + } /> } /> } /> diff --git a/src/components/PrivacyConsentModal.tsx b/src/components/PrivacyConsentModal.tsx new file mode 100644 index 0000000..22b1e62 --- /dev/null +++ b/src/components/PrivacyConsentModal.tsx @@ -0,0 +1,127 @@ +import React, { useId, useMemo, useState } from 'react' +import { X } from 'lucide-react' +import { Link } from 'react-router-dom' +import { getLocaleFromPathname, withLocalePath } from '../lib/i18n' + +type PrivacyConsentModalProps = { + open: boolean + onClose: () => void + onConfirm: (payload: { consentedAt: string; version: string }) => void + locale: 'zh' | 'en' +} + +const CONSENT_VERSION = '2026-01-01' + +export const PrivacyConsentModal: React.FC = ({ open, onClose, onConfirm, locale }) => { + const checkboxId = useId() + const [agreed, setAgreed] = useState(false) + + const privacyPath = useMemo(() => withLocalePath(locale, '/privacy'), [locale]) + + if (!open) return null + + return ( +
+ + ) +} + +export default PrivacyConsentModal diff --git a/src/pages/Assistant.tsx b/src/pages/Assistant.tsx index db2b690..d3917ae 100644 --- a/src/pages/Assistant.tsx +++ b/src/pages/Assistant.tsx @@ -14,6 +14,7 @@ import { import { Header } from '../components/Header' import { Footer } from '../components/Footer' import { Breadcrumbs } from '../components/Breadcrumbs' +import { PrivacyConsentModal } from '../components/PrivacyConsentModal' import { FormSubmissions, Forms, SearchResults } from '../clientsdk/sdk.gen' import type { Post, SearchResult, SearchResultQueryOperations } from '../clientsdk/types.gen' import { createClient } from '../clientsdk/client' @@ -182,8 +183,26 @@ export const Assistant: React.FC = () => { const [leadSubmitted, setLeadSubmitted] = useState(false) const [leadSubmitError, setLeadSubmitError] = useState(null) + const [privacyOpen, setPrivacyOpen] = useState(false) + const [pendingSubmitEvent, setPendingSubmitEvent] = useState(null) + const [privacyConsent, setPrivacyConsent] = useState<{ consentedAt: string; version: string } | null>(null) + const listRef = useRef(null) + useEffect(() => { + try { + const cached = localStorage.getItem('privacyConsent.callback') + if (cached) { + const parsed = JSON.parse(cached) as { consentedAt?: string; version?: string } + if (parsed.consentedAt && parsed.version) { + setPrivacyConsent({ consentedAt: parsed.consentedAt, version: parsed.version }) + } + } + } catch { + // ignore + } + }, []) + useEffect(() => { const load = async () => { try { @@ -293,21 +312,25 @@ export const Assistant: React.FC = () => { return Object.keys(next).length === 0 } - const submitLead = async (e: React.FormEvent) => { - e.preventDefault() + const doSubmitLead = async () => { setLeadSubmitError(null) - if (!validateLead()) return + if (!privacyConsent) { + setLeadSubmitError(locale === 'en' ? 'Please review and agree to the privacy notice first.' : '请先阅读并同意个人信息处理告知。') + return + } setLeadSubmitting(true) try { if (!leadFormId) { - const cacheKey = 'chengyu_leads_local' + const cacheKey = 'chengyu_callback_requests_local' const record = { ...lead, createdAt: new Date().toISOString(), source: 'assistant', + privacyConsentedAt: privacyConsent.consentedAt, + privacyVersion: privacyConsent.version, } const existing = JSON.parse(localStorage.getItem(cacheKey) || '[]') @@ -329,6 +352,8 @@ export const Assistant: React.FC = () => { { field: 'company', value: lead.company.trim() }, { field: 'demand', value: lead.demand.trim() }, { field: 'source', value: 'AI智能助手' }, + { field: 'privacyConsentedAt', value: privacyConsent.consentedAt }, + { field: 'privacyVersion', value: privacyConsent.version }, ].filter((item) => item.value), }, }) @@ -343,6 +368,16 @@ export const Assistant: React.FC = () => { } } + const submitLead = async (e: React.FormEvent) => { + e.preventDefault() + setLeadSubmitError(null) + + if (!validateLead()) return + + setPendingSubmitEvent(e) + setPrivacyOpen(true) + } + return (
@@ -513,8 +548,8 @@ export const Assistant: React.FC = () => {

{locale === 'en' - ? 'Phone number is required. The ops team can process submissions in the backend.' - : '手机号为必填项;提交后,后台运营人员可及时查看并处理(可在运营平台表单里配置邮件通知)。'} + ? 'Phone number is required for a callback. We will ask for your consent before submission.' + : '手机号为预约回电所必需;提交前会弹出“个人信息处理告知”,经你同意后才会提交。'}

@@ -543,7 +578,7 @@ export const Assistant: React.FC = () => { )} -
+
@@ -716,6 +771,27 @@ export const Assistant: React.FC = () => { + { + setPrivacyOpen(false) + setPendingSubmitEvent(null) + }} + onConfirm={(payload) => { + try { + localStorage.setItem('privacyConsent.callback', JSON.stringify(payload)) + } catch { + // ignore + } + + setPrivacyConsent(payload) + setPrivacyOpen(false) + setPendingSubmitEvent(null) + void doSubmitLead() + }} + /> +