/* 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 (
);
}
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 (
);
}
function ContactPage() {
return (
CONTACT
Contact
お問い合わせ
事業に関するご相談、取材・協業のお問い合わせはこちらから。下記フォームよりお気軽にご連絡ください。
まずはお気軽に
ご相談ください。
いただいたお問い合わせは内容を確認のうえ、担当者より順次ご返信いたします。お急ぎの場合はメールにて直接ご連絡ください。
info@live-ons.com
);
}
function App() { return {}; }
ReactDOM.createRoot(document.getElementById("root")).render();