300 lines
12 KiB
JavaScript
300 lines
12 KiB
JavaScript
(() => {
|
|
"use strict";
|
|
|
|
const fixture = new URLSearchParams(window.location.search).get("fixture") || "";
|
|
const form = document.querySelector("#franchise-form");
|
|
const formSteps = [...document.querySelectorAll("[data-step]")];
|
|
const indicators = [...document.querySelectorAll("[data-step-indicator]")];
|
|
const indicatorList = document.querySelector(".step-indicator");
|
|
const areaField = document.querySelector("#area-field");
|
|
const areaInput = document.querySelector("#exclusive-area");
|
|
const areaOutput = document.querySelector("#area-conversion");
|
|
const successPanel = document.querySelector("#inquiry-success");
|
|
const submitState = document.querySelector("#submit-state");
|
|
const submitButton = document.querySelector("#submit-inquiry");
|
|
let currentStep = 1;
|
|
|
|
const markBrokenImage = (image) => {
|
|
const shell = image.closest(".image-shell");
|
|
if (shell) shell.classList.add("is-broken");
|
|
};
|
|
|
|
document.querySelectorAll(".image-shell img").forEach((image) => {
|
|
image.addEventListener("error", () => markBrokenImage(image));
|
|
if (image.complete && image.naturalWidth === 0) markBrokenImage(image);
|
|
});
|
|
|
|
if (!form) return;
|
|
|
|
const getRadioValue = (name) => form.querySelector(`input[name="${name}"]:checked`)?.value || "";
|
|
const getSelectValue = (name) => {
|
|
const field = form.elements.namedItem(name);
|
|
return field instanceof HTMLSelectElement ? field.value : "";
|
|
};
|
|
const getInputValue = (name) => {
|
|
const field = form.elements.namedItem(name);
|
|
return field instanceof HTMLInputElement ? field.value.trim() : "";
|
|
};
|
|
|
|
const setStep = (step) => {
|
|
currentStep = step;
|
|
formSteps.forEach((section) => section.classList.toggle("is-current", Number(section.dataset.step) === step));
|
|
indicators.forEach((item) => {
|
|
const active = Number(item.dataset.stepIndicator) === step;
|
|
item.classList.toggle("is-current", active);
|
|
if (active) item.setAttribute("aria-current", "step");
|
|
else item.removeAttribute("aria-current");
|
|
});
|
|
const heading = form.querySelector(`[data-step="${step}"] h3`);
|
|
if (heading instanceof HTMLElement) {
|
|
heading.setAttribute("tabindex", "-1");
|
|
heading.focus({ preventScroll: true });
|
|
}
|
|
};
|
|
|
|
const clearFieldError = (fieldId, errorId) => {
|
|
const field = document.querySelector(`#${fieldId}`);
|
|
const error = document.querySelector(`#${errorId}`);
|
|
const wrapper = field?.closest(".form-field");
|
|
wrapper?.classList.remove("has-error");
|
|
field?.removeAttribute("aria-invalid");
|
|
field?.removeAttribute("aria-describedby");
|
|
if (error) error.textContent = "";
|
|
};
|
|
|
|
const setFieldError = (fieldId, errorId, message) => {
|
|
const field = document.querySelector(`#${fieldId}`);
|
|
const error = document.querySelector(`#${errorId}`);
|
|
const wrapper = field?.closest(".form-field");
|
|
wrapper?.classList.add("has-error");
|
|
field?.setAttribute("aria-invalid", "true");
|
|
field?.setAttribute("aria-describedby", errorId);
|
|
if (error) error.textContent = message;
|
|
return field;
|
|
};
|
|
|
|
const clearRadioError = (name, errorId) => {
|
|
const radios = [...form.querySelectorAll(`input[name="${name}"]`)];
|
|
const wrapper = radios[0]?.closest(".form-field");
|
|
wrapper?.classList.remove("has-error");
|
|
radios.forEach((radio) => {
|
|
radio.removeAttribute("aria-invalid");
|
|
radio.removeAttribute("aria-describedby");
|
|
});
|
|
const error = document.querySelector(`#${errorId}`);
|
|
if (error) error.textContent = "";
|
|
};
|
|
|
|
const setRadioError = (name, errorId, message) => {
|
|
const radios = [...form.querySelectorAll(`input[name="${name}"]`)];
|
|
const wrapper = radios[0]?.closest(".form-field");
|
|
wrapper?.classList.add("has-error");
|
|
radios.forEach((radio) => {
|
|
radio.setAttribute("aria-invalid", "true");
|
|
radio.setAttribute("aria-describedby", errorId);
|
|
});
|
|
const error = document.querySelector(`#${errorId}`);
|
|
if (error) error.textContent = message;
|
|
return radios[0];
|
|
};
|
|
|
|
const validateStepOne = () => {
|
|
const invalid = [];
|
|
clearFieldError("demo-name", "demo-name-error");
|
|
clearFieldError("region", "region-error");
|
|
clearRadioError("areaKind", "area-kind-error");
|
|
|
|
if (getInputValue("demoName").length < 2) {
|
|
invalid.push(setFieldError("demo-name", "demo-name-error", "예시 호칭을 두 글자 이상 입력해 주세요."));
|
|
}
|
|
if (!getSelectValue("region")) {
|
|
invalid.push(setFieldError("region", "region-error", "희망 지역을 골라 주세요."));
|
|
}
|
|
if (!getRadioValue("areaKind")) {
|
|
invalid.push(setRadioError("areaKind", "area-kind-error", "희망 상권을 골라 주세요."));
|
|
}
|
|
invalid[0]?.focus();
|
|
return invalid.length === 0;
|
|
};
|
|
|
|
const requiresArea = () => ["협의 중", "보유함"].includes(getRadioValue("storePossession"));
|
|
|
|
const updateAreaField = () => {
|
|
const required = requiresArea();
|
|
if (areaField) areaField.hidden = !required;
|
|
if (areaInput instanceof HTMLInputElement) {
|
|
areaInput.required = required;
|
|
if (!required) {
|
|
areaInput.value = "";
|
|
clearFieldError("exclusive-area", "exclusive-area-error");
|
|
if (areaOutput) areaOutput.textContent = "면적을 입력하면 평으로도 보여 드립니다.";
|
|
}
|
|
}
|
|
};
|
|
|
|
const updateAreaConversion = () => {
|
|
if (!(areaInput instanceof HTMLInputElement) || !areaOutput) return;
|
|
const squareMeters = Number(areaInput.value);
|
|
if (!Number.isFinite(squareMeters) || squareMeters <= 0) {
|
|
areaOutput.textContent = "면적을 입력하면 평으로도 보여 드립니다.";
|
|
return;
|
|
}
|
|
const pyeong = Math.round((squareMeters / 3.3058) * 10) / 10;
|
|
areaOutput.textContent = `${squareMeters.toLocaleString("ko-KR")}㎡ · 약 ${pyeong.toLocaleString("ko-KR")}평`;
|
|
};
|
|
|
|
const validateStepTwo = () => {
|
|
const invalid = [];
|
|
clearRadioError("storePossession", "store-possession-error");
|
|
clearFieldError("exclusive-area", "exclusive-area-error");
|
|
clearFieldError("opening-window", "opening-window-error");
|
|
clearRadioError("experience", "experience-error");
|
|
|
|
if (!getRadioValue("storePossession")) {
|
|
invalid.push(setRadioError("storePossession", "store-possession-error", "점포 상태를 골라 주세요."));
|
|
}
|
|
if (requiresArea()) {
|
|
const value = Number(getInputValue("exclusiveArea"));
|
|
if (!Number.isFinite(value) || value <= 0) {
|
|
invalid.push(setFieldError("exclusive-area", "exclusive-area-error", "면적은 0보다 큰 숫자로 입력해 주세요."));
|
|
}
|
|
}
|
|
if (!getSelectValue("openingWindow")) {
|
|
invalid.push(setFieldError("opening-window", "opening-window-error", "희망 시작 시기를 골라 주세요."));
|
|
}
|
|
if (!getRadioValue("experience")) {
|
|
invalid.push(setRadioError("experience", "experience-error", "운영 경험 여부를 골라 주세요."));
|
|
}
|
|
invalid[0]?.focus();
|
|
return invalid.length === 0;
|
|
};
|
|
|
|
const reviewValues = () => {
|
|
const squareMeters = Number(getInputValue("exclusiveArea"));
|
|
const areaLabel = requiresArea() && squareMeters > 0
|
|
? `${squareMeters.toLocaleString("ko-KR")}㎡ · 약 ${(Math.round((squareMeters / 3.3058) * 10) / 10).toLocaleString("ko-KR")}평`
|
|
: "해당 없음";
|
|
const values = {
|
|
demoName: getInputValue("demoName"),
|
|
region: getSelectValue("region"),
|
|
areaKind: getRadioValue("areaKind"),
|
|
storePossession: getRadioValue("storePossession"),
|
|
exclusiveArea: areaLabel,
|
|
openingWindow: getSelectValue("openingWindow"),
|
|
experience: getRadioValue("experience"),
|
|
};
|
|
Object.entries(values).forEach(([key, value]) => {
|
|
const target = document.querySelector(`[data-review="${key}"]`);
|
|
if (target) target.textContent = value || "—";
|
|
});
|
|
};
|
|
|
|
form.querySelectorAll("[data-next]").forEach((button) => {
|
|
button.addEventListener("click", () => {
|
|
if (currentStep === 1 && validateStepOne()) setStep(2);
|
|
else if (currentStep === 2 && validateStepTwo()) {
|
|
reviewValues();
|
|
setStep(3);
|
|
}
|
|
});
|
|
});
|
|
|
|
form.querySelectorAll("[data-back]").forEach((button) => {
|
|
button.addEventListener("click", () => setStep(Math.max(1, currentStep - 1)));
|
|
});
|
|
|
|
form.querySelectorAll("input[name='storePossession']").forEach((input) => {
|
|
input.addEventListener("change", updateAreaField);
|
|
});
|
|
areaInput?.addEventListener("input", updateAreaConversion);
|
|
|
|
form.addEventListener("input", (event) => {
|
|
const field = event.target;
|
|
if (!(field instanceof HTMLElement) || !field.id) return;
|
|
const errorId = `${field.id}-error`;
|
|
if (document.querySelector(`#${errorId}`)) clearFieldError(field.id, errorId);
|
|
});
|
|
|
|
form.addEventListener("change", (event) => {
|
|
const field = event.target;
|
|
if (!(field instanceof HTMLInputElement)) return;
|
|
if (field.type === "radio") {
|
|
const names = {
|
|
areaKind: "area-kind-error",
|
|
storePossession: "store-possession-error",
|
|
experience: "experience-error",
|
|
};
|
|
if (names[field.name]) clearRadioError(field.name, names[field.name]);
|
|
}
|
|
});
|
|
|
|
form.addEventListener("submit", (event) => {
|
|
event.preventDefault();
|
|
const consent = document.querySelector("#demo-consent");
|
|
const consentError = document.querySelector("#demo-consent-error");
|
|
if (consent instanceof HTMLInputElement) {
|
|
consent.removeAttribute("aria-invalid");
|
|
consent.removeAttribute("aria-describedby");
|
|
}
|
|
if (consentError) consentError.textContent = "";
|
|
if (!(consent instanceof HTMLInputElement) || !consent.checked) {
|
|
consent?.setAttribute("aria-invalid", "true");
|
|
consent?.setAttribute("aria-describedby", "demo-consent-error");
|
|
if (consentError) consentError.textContent = "문의 체험 안내를 확인해 주세요.";
|
|
consent?.focus();
|
|
return;
|
|
}
|
|
|
|
if (submitButton instanceof HTMLButtonElement) {
|
|
submitButton.disabled = true;
|
|
submitButton.textContent = "화면 상태 확인 중";
|
|
}
|
|
if (submitState) submitState.textContent = "";
|
|
|
|
window.setTimeout(() => {
|
|
if (fixture === "inquiry-fail") {
|
|
if (submitButton instanceof HTMLButtonElement) {
|
|
submitButton.disabled = false;
|
|
submitButton.textContent = "다시 시도하기";
|
|
}
|
|
if (submitState) {
|
|
submitState.textContent = "지금은 문의 준비 상태를 바꾸지 못했어요. 입력 내용은 화면에 그대로 두었습니다.";
|
|
}
|
|
return;
|
|
}
|
|
|
|
form.hidden = true;
|
|
if (indicatorList) indicatorList.hidden = true;
|
|
if (successPanel) {
|
|
successPanel.hidden = false;
|
|
successPanel.focus();
|
|
}
|
|
if (submitButton instanceof HTMLButtonElement) {
|
|
submitButton.disabled = false;
|
|
submitButton.textContent = "문의 준비 마치기";
|
|
}
|
|
}, 450);
|
|
});
|
|
|
|
document.querySelector("#review-again")?.addEventListener("click", () => {
|
|
if (successPanel) successPanel.hidden = true;
|
|
form.hidden = false;
|
|
if (indicatorList) indicatorList.hidden = false;
|
|
setStep(3);
|
|
});
|
|
|
|
document.querySelector("#restart-form")?.addEventListener("click", () => {
|
|
form.reset();
|
|
form.hidden = false;
|
|
if (successPanel) successPanel.hidden = true;
|
|
if (indicatorList) indicatorList.hidden = false;
|
|
if (submitState) submitState.textContent = "";
|
|
document.querySelectorAll(".field-error").forEach((error) => { error.textContent = ""; });
|
|
document.querySelectorAll(".has-error").forEach((field) => field.classList.remove("has-error"));
|
|
updateAreaField();
|
|
setStep(1);
|
|
});
|
|
|
|
updateAreaField();
|
|
setStep(1);
|
|
})();
|