|
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import React from "react";
|
|
|
|
|
import type { ContactData, ContactField } from "../types";
|
|
|
|
|
|
|
|
|
|
interface ContactSectionProps {
|
|
|
|
|
data: ContactData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type FormValues = Record<string, string>;
|
|
|
|
|
|
|
|
|
|
function resolveFieldType(field: ContactField) {
|
|
|
|
|
if (field.type) return field.type;
|
|
|
|
|
return "text";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function ContactSection({ data }: ContactSectionProps) {
|
|
|
|
|
const fields = data.form.fields;
|
|
|
|
|
|
|
|
|
|
const [values, setValues] = React.useState<FormValues>(() =>
|
|
|
|
|
fields.reduce<FormValues>((acc, field) => {
|
|
|
|
|
acc[field.id] = "";
|
|
|
|
|
return acc;
|
|
|
|
|
}, {})
|
|
|
|
|
);
|
|
|
|
|
const [submitting, setSubmitting] = React.useState(false);
|
|
|
|
|
const [submitted, setSubmitted] = React.useState(false);
|
|
|
|
|
|
|
|
|
|
const handleChange = (fieldId: string) => (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
|
|
|
|
|
setValues((prev) => ({
|
|
|
|
|
...prev,
|
|
|
|
|
[fieldId]: event.target.value,
|
|
|
|
|
}));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
setSubmitting(true);
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
setSubmitting(false);
|
|
|
|
|
setSubmitted(true);
|
|
|
|
|
}, 800);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<section id="contact" className="relative bg-[#f3f6fc] py-12 md:py-20">
|
|
|
|
|
<div className="absolute inset-0 pointer-events-none">
|
|
|
|
|
<div className="h-full w-full bg-[radial-gradient(circle_at_top_left,_rgba(88,133,255,0.08),_transparent_55%)]" />
|
|
|
|
|
</div>
|
|
|
|
|
<div className="relative mx-auto flex max-w-5xl flex-col gap-10 px-4 md:px-8">
|
|
|
|
|
<div className="w-full rounded-3xl bg-white p-6 shadow-[0_20px_60px_-25px_rgba(72,99,178,0.35)] md:p-10">
|
|
|
|
|
<div className="mb-8 border-b border-[#e1e7f5] pb-8">
|
|
|
|
|
<p className="text-sm font-medium uppercase tracking-[0.2em] text-[#7090ff]">
|
|
|
|
|
{data.hero?.title ?? "Business Partnership"}
|
|
|
|
|
</p>
|
|
|
|
|
<h2 className="mt-3 text-2xl font-semibold text-[#102052] md:text-[30px]">
|
|
|
|
|
{data.form.title}
|
|
|
|
|
</h2>
|
|
|
|
|
{data.form.description && (
|
|
|
|
|
<p className="mt-3 max-w-3xl text-sm leading-relaxed text-[#5c6b91] md:text-base">
|
|
|
|
|
{data.form.description}
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
{data.form.note && (
|
|
|
|
|
<p className="mt-4 rounded-2xl bg-[#f6f8ff] px-4 py-3 text-xs text-[#6b7ba8] md:text-sm">
|
|
|
|
|
{data.form.note}
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<form onSubmit={handleSubmit} className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
|
|
|
|
{fields.map((field) => {
|
|
|
|
|
const type = resolveFieldType(field);
|
|
|
|
|
const value = values[field.id] ?? "";
|
|
|
|
|
const hasCounter = typeof field.maxLength === "number" && field.maxLength > 0;
|
|
|
|
|
const counter = hasCounter ? `${value.length}/${field.maxLength}` : undefined;
|
|
|
|
|
const baseClassName =
|
|
|
|
|
"w-full rounded-2xl border border-[#dde5f7] bg-[#f9fbff] px-4 py-3 text-sm text-[#1b2559] placeholder:text-[#9aa7ca] shadow-[0_10px_30px_-20px_rgba(38,71,150,0.45)] transition focus:border-[#4a7dff] focus:bg-white focus:outline-none focus:ring-4 focus:ring-[#4a7dff]/10";
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
key={field.id}
|
|
|
|
|
className={`flex flex-col gap-2 ${field.span === "full" ? "md:col-span-2" : ""}`}
|
|
|
|
|
>
|
|
|
|
|
{field.label && (
|
|
|
|
|
<label
|
|
|
|
|
htmlFor={field.id}
|
|
|
|
|
className="text-sm font-medium text-[#374570]"
|
|
|
|
|
>
|
|
|
|
|
{field.label}
|
|
|
|
|
</label>
|
|
|
|
|
)}
|
|
|
|
|
<div className="relative">
|
|
|
|
|
{type === "textarea" ? (
|
|
|
|
|
<textarea
|
|
|
|
|
id={field.id}
|
|
|
|
|
name={field.id}
|
|
|
|
|
value={value}
|
|
|
|
|
onChange={handleChange(field.id)}
|
|
|
|
|
placeholder={field.placeholder}
|
|
|
|
|
maxLength={field.maxLength}
|
|
|
|
|
rows={6}
|
|
|
|
|
className={`${baseClassName} resize-none md:min-h-[168px]`}
|
|
|
|
|
/>
|
|
|
|
|
) : type === "select" ? (
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<select
|
|
|
|
|
id={field.id}
|
|
|
|
|
name={field.id}
|
|
|
|
|
value={value}
|
|
|
|
|
onChange={handleChange(field.id)}
|
|
|
|
|
className={`${baseClassName} appearance-none`}
|
|
|
|
|
>
|
|
|
|
|
<option value="" disabled>
|
|
|
|
|
{field.placeholder}
|
|
|
|
|
</option>
|
|
|
|
|
{(field.options ?? []).map((option) => (
|
|
|
|
|
<option key={option} value={option}>
|
|
|
|
|
{option}
|
|
|
|
|
</option>
|
|
|
|
|
))}
|
|
|
|
|
</select>
|
|
|
|
|
<span className="pointer-events-none absolute inset-y-0 right-4 flex items-center text-[#7c8db8]">
|
|
|
|
|
▾
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<input
|
|
|
|
|
id={field.id}
|
|
|
|
|
name={field.id}
|
|
|
|
|
type={type}
|
|
|
|
|
value={value}
|
|
|
|
|
onChange={handleChange(field.id)}
|
|
|
|
|
placeholder={field.placeholder}
|
|
|
|
|
maxLength={field.maxLength}
|
|
|
|
|
className={baseClassName}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
{counter && (
|
|
|
|
|
<span className="pointer-events-none absolute bottom-3 right-4 text-xs font-medium text-[#8e9ec9]">
|
|
|
|
|
{counter}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
|
|
|
|
|
<div className="flex flex-col gap-3 md:col-span-2 md:flex-row md:items-center md:justify-between">
|
|
|
|
|
<p className="text-sm text-[#7381ab]">
|
|
|
|
|
{submitted
|
|
|
|
|
? "感谢您的提交!我们将尽快与您联系。"
|
|
|
|
|
: "提交后,我们的商务团队将在一个工作日内回复。"}
|
|
|
|
|
</p>
|
|
|
|
|
<button
|
|
|
|
|
type="submit"
|
|
|
|
|
disabled={submitting}
|
|
|
|
|
className="inline-flex items-center justify-center rounded-full bg-[#2f6bff] px-8 py-3 text-sm font-semibold text-white transition hover:bg-[#2556d6] disabled:cursor-not-allowed disabled:opacity-70"
|
|
|
|
|
>
|
|
|
|
|
{submitting ? "提交中..." : data.form.submit}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
);
|
|
|
|
|
}
|