feat: 实现联系表单提交与邮件通知功能
添加联系表单提交接口和邮件通知功能,支持从环境变量读取 SMTP 配置 重构 SEO 配置到 site.json,新增 robots.txt 和 sitemap.xml 生成 更新公司电话并添加 PM2 生产运行配置
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { useState, type FormEvent, type ReactNode } from "react"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
@@ -9,7 +9,7 @@ import { SectionReveal } from "@/components/ui/section-reveal"
|
||||
import { Phone, Mail, MapPin, Clock3, ArrowRight, CheckCircle2 } from "lucide-react"
|
||||
import homeData from "@/data/home.json"
|
||||
|
||||
const iconMap: Record<string, React.ReactNode> = {
|
||||
const iconMap: Record<string, ReactNode> = {
|
||||
phone: <Phone className="w-5 h-5" />,
|
||||
mail: <Mail className="w-5 h-5" />,
|
||||
mappin: <MapPin className="w-5 h-5" />,
|
||||
@@ -27,10 +27,46 @@ export function ContactSection() {
|
||||
}))) as Array<{ icon: string; label: string; value: string; sub?: string }>
|
||||
const submitLabel = (contact.form as any).submitLabel ?? (contact.form as any).submit
|
||||
const [submitted, setSubmitted] = useState(false)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null)
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
setSubmitted(true)
|
||||
|
||||
const form = e.currentTarget
|
||||
const formData = new FormData(form)
|
||||
|
||||
setErrorMessage(null)
|
||||
setIsSubmitting(true)
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/contact", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: String(formData.get("name") ?? ""),
|
||||
company: String(formData.get("company") ?? ""),
|
||||
phone: String(formData.get("phone") ?? ""),
|
||||
scale: String(formData.get("scale") ?? ""),
|
||||
message: String(formData.get("message") ?? ""),
|
||||
}),
|
||||
})
|
||||
|
||||
const result = (await response.json().catch(() => null)) as { error?: string } | null
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(result?.error ?? "提交失败,请稍后重试。")
|
||||
}
|
||||
|
||||
form.reset()
|
||||
setSubmitted(true)
|
||||
} catch (error) {
|
||||
setErrorMessage(error instanceof Error ? error.message : "提交失败,请稍后重试。")
|
||||
} finally {
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -91,38 +127,46 @@ export function ContactSection() {
|
||||
<div>
|
||||
<h3 className="text-xl font-bold text-foreground mb-2">信息已提交</h3>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
工作时间内 2 小时响应,非工作时间次一工作日上午回复。
|
||||
管理人员已收到邮件通知,工作时间内 2 小时响应,非工作时间次一工作日上午回复。
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="outline" onClick={() => setSubmitted(false)} className="mt-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setSubmitted(false)
|
||||
setErrorMessage(null)
|
||||
}}
|
||||
className="mt-2"
|
||||
>
|
||||
重新提交
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit} className="flex flex-col gap-5" noValidate>
|
||||
<form onSubmit={handleSubmit} className="flex flex-col gap-5">
|
||||
<h3 className="text-lg font-bold text-foreground">留下信息,先拿一版初步建议</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="text-xs font-medium text-muted-foreground" htmlFor="name">姓名</label>
|
||||
<Input id="name" placeholder={contact.form.namePlaceholder} required className="h-11" />
|
||||
<Input id="name" name="name" placeholder={contact.form.namePlaceholder} required className="h-11" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="text-xs font-medium text-muted-foreground" htmlFor="company">公司 / 品牌</label>
|
||||
<Input id="company" placeholder={contact.form.companyPlaceholder} required className="h-11" />
|
||||
<Input id="company" name="company" placeholder={contact.form.companyPlaceholder} required className="h-11" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="text-xs font-medium text-muted-foreground" htmlFor="phone">电话</label>
|
||||
<Input id="phone" type="tel" placeholder={contact.form.phonePlaceholder} required className="h-11" />
|
||||
<Input id="phone" name="phone" type="tel" placeholder={contact.form.phonePlaceholder} required className="h-11" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="text-xs font-medium text-muted-foreground" htmlFor="scale">门店规模</label>
|
||||
<Input id="scale" placeholder={contact.form.scalePlaceholder} className="h-11" />
|
||||
<Input id="scale" name="scale" placeholder={contact.form.scalePlaceholder} className="h-11" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="text-xs font-medium text-muted-foreground" htmlFor="message">需求描述</label>
|
||||
<Textarea
|
||||
id="message"
|
||||
name="message"
|
||||
placeholder={contact.form.messagePlaceholder}
|
||||
rows={4}
|
||||
className="resize-none"
|
||||
@@ -131,13 +175,19 @@ export function ContactSection() {
|
||||
<Button
|
||||
type="submit"
|
||||
size="lg"
|
||||
disabled={isSubmitting}
|
||||
className="w-full bg-primary hover:bg-primary-dark text-white h-12 text-base font-semibold group shadow-lg shadow-primary/25"
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
{submitLabel}
|
||||
{isSubmitting ? "提交中..." : submitLabel}
|
||||
<ArrowRight className="w-4 h-4 group-hover:translate-x-0.5 transition-transform" />
|
||||
</span>
|
||||
</Button>
|
||||
{errorMessage ? (
|
||||
<p className="text-sm text-red-600 text-center" aria-live="polite">
|
||||
{errorMessage}
|
||||
</p>
|
||||
) : null}
|
||||
<p className="text-xs text-muted-foreground text-center">
|
||||
提交即视为同意{" "}
|
||||
<span className="text-primary cursor-pointer hover:underline">隐私政策</span>
|
||||
|
||||
Reference in New Issue
Block a user