/* contact.jsx — お問い合わせページ(フォーム + バリデーション + 送信完了) */ const TYPES = ["事業に関するお問い合わせ", "取材・協業のご相談", "その他"]; const TURNSTILE_SITE_KEY = "0x4AAAAAADiidKoYsm0RVERD"; function Field({ id, label, required, error, children }) { return (
{children}

{error}

); } function TurnstileWidget({ error, onVerify, onReset, resetKey }) { const boxRef = React.useRef(null); const widgetIdRef = React.useRef(null); const callbacksRef = React.useRef({ onVerify, onReset }); callbacksRef.current = { onVerify, onReset }; React.useEffect(() => { let cancelled = false; let timer = null; const render = () => { if (cancelled || widgetIdRef.current || !boxRef.current || !window.turnstile) return; widgetIdRef.current = window.turnstile.render(boxRef.current, { sitekey: TURNSTILE_SITE_KEY, theme: "light", callback: (token) => callbacksRef.current.onVerify(token), "expired-callback": () => callbacksRef.current.onReset("認証の有効期限が切れました。もう一度お試しください。"), "error-callback": () => callbacksRef.current.onReset("認証に失敗しました。もう一度お試しください。"), }); }; render(); timer = window.setInterval(render, 300); return () => { cancelled = true; if (timer) window.clearInterval(timer); if (widgetIdRef.current && window.turnstile) window.turnstile.remove(widgetIdRef.current); widgetIdRef.current = null; }; }, []); React.useEffect(() => { if (!resetKey || !widgetIdRef.current || !window.turnstile) return; window.turnstile.reset(widgetIdRef.current); }, [resetKey]); return (

{error}

); } function ContactForm() { const [v, setV] = React.useState({ name: "", company: "", email: "", tel: "", type: "", body: "", agree: false, website: "" }); const [errs, setErrs] = React.useState({}); const [formError, setFormError] = React.useState(""); const [sent, setSent] = React.useState(false); const [submitting, setSubmitting] = React.useState(false); const [stableHeight, setStableHeight] = React.useState(null); const [turnstileToken, setTurnstileToken] = React.useState(""); const [turnstileResetKey, setTurnstileResetKey] = React.useState(0); const shellRef = React.useRef(null); const restoreScrollRef = React.useRef(null); const set = (k) => (e) => setV(s => ({ ...s, [k]: e.target.type === "checkbox" ? e.target.checked : e.target.value })); React.useLayoutEffect(() => { if (!sent || !restoreScrollRef.current) return; const { x, y } = restoreScrollRef.current; const restore = () => window.scrollTo({ left: x, top: y, behavior: "auto" }); restore(); let raf2 = 0; const raf1 = window.requestAnimationFrame(() => { restore(); raf2 = window.requestAnimationFrame(restore); }); const timer1 = window.setTimeout(restore, 0); const timer2 = window.setTimeout(() => { restore(); restoreScrollRef.current = null; }, 80); return () => { window.cancelAnimationFrame(raf1); if (raf2) window.cancelAnimationFrame(raf2); window.clearTimeout(timer1); window.clearTimeout(timer2); }; }, [sent]); const validate = () => { const e = {}; if (!v.name.trim()) e.name = "お名前を入力してください。"; if (!v.email.trim()) e.email = "メールアドレスを入力してください。"; else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v.email)) e.email = "メールアドレスの形式が正しくありません。"; if (!v.type) e.type = "お問い合わせ種別を選択してください。"; if (!v.body.trim()) e.body = "お問い合わせ内容を入力してください。"; if (!v.agree) e.agree = "個人情報の取扱いに同意してください。"; if (!turnstileToken) e.turnstile = "認証を完了してください。"; return e; }; const resetTurnstile = (message = "") => { setTurnstileToken(""); setTurnstileResetKey(k => k + 1); if (message) setErrs(e => ({ ...e, turnstile: message })); }; const onSubmit = async (ev) => { ev.preventDefault(); if (submitting) return; const e = validate(); setErrs(e); setFormError(""); if (Object.keys(e).length !== 0) return; setSubmitting(true); try { const res = await fetch("api/contact.php", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ...v, turnstileToken }), }); const result = await res.json().catch(() => ({})); if (!res.ok || result.ok !== true) { throw new Error(result.message || "送信に失敗しました。時間をおいて再度お試しください。"); } restoreScrollRef.current = { x: window.scrollX, y: window.scrollY }; if (shellRef.current) setStableHeight(Math.ceil(shellRef.current.getBoundingClientRect().height)); setSent(true); } catch (error) { setFormError(error.message || "送信に失敗しました。時間をおいて再度お試しください。"); resetTurnstile(); } finally { setSubmitting(false); } }; if (sent) { return (

お問い合わせありがとうございます

内容を確認のうえ、担当者よりスピード感を持ってご返信いたします。今しばらくお待ちください。

トップページへ戻る
); } return (
{formError &&

{formError}

}