/* ============================================================================= * A08_Support_UI_Page.ts * 로그인 전 08: 기술지원 요청 (이름 + 이메일 + 제목 + 문의내용 폼) * * 제약 준수: * - 모든 문구는 ui_template_locale.ts 참조 (하드코딩 금지). * - 색상/간격은 A08_Support_UI_Style.css + theme.css 변수만 사용. * - 공통 컴포넌트(createButton/createInputField) 우선 사용. * - 이벤트 핸들러는 on[페이지]_[기능]_[액션] 명명. * - 백엔드 전송 전 1차 유효성 검사 (frontend.md §4). * ========================================================================== */ import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale"; import { createButton, createInputField } from "@ui/ui_template_elements"; import { isBlank, isValidEmail } from "@util/common_util_validate"; import { fetchSessionUser } from "../A06_Login/A06_Login_Api_Fetch"; import "./A08_Support_UI_Style.css"; /** locale 헬퍼 */ function L(key: keyof typeof ui_locales): string { return ui_locales[key][currentLanguageIndex]; } /** 문의내용 textarea 핸들 (createInputField와 유사한 형태로 에러 슬롯 포함) */ interface TextAreaHandle { root: HTMLDivElement; textarea: HTMLTextAreaElement; setError: (msg?: string) => void; } /** 여러 줄 입력용 textarea 필드 생성 (공통 .ui-field/.ui-input 스타일 상속). */ function createTextAreaField(label: string, placeholder: string): TextAreaHandle { const root = document.createElement("div"); root.className = "ui-field"; const labelEl = document.createElement("label"); labelEl.className = "ui-field__label"; labelEl.textContent = label; const textarea = document.createElement("textarea"); textarea.className = "ui-input a08-support__textarea"; textarea.placeholder = placeholder; textarea.rows = 6; const errorSlot = document.createElement("span"); errorSlot.className = "ui-field__error"; errorSlot.setAttribute("role", "alert"); root.append(labelEl, textarea, errorSlot); const setError = (msg?: string): void => { if (msg) { textarea.classList.add("ui-input--error"); errorSlot.textContent = msg; errorSlot.classList.add("is-visible"); } else { textarea.classList.remove("ui-input--error"); errorSlot.textContent = ""; errorSlot.classList.remove("is-visible"); } }; return { root, textarea, setError }; } /* ----------------------------------------------------------------------------- * 페이지 진입점 * -------------------------------------------------------------------------- */ export function renderA08Support(root: HTMLElement): void { const page = document.createElement("div"); page.className = "a08-support"; const card = document.createElement("div"); card.className = "a08-support__card"; const title = document.createElement("h1"); title.className = "a08-support__title"; title.textContent = L("A08_Support_Title"); const subtitle = document.createElement("p"); subtitle.className = "a08-support__subtitle"; subtitle.textContent = L("A08_Support_Subtitle"); // 성공 메시지 슬롯 (제출 성공 시 표시) const successMsg = document.createElement("p"); successMsg.className = "a08-support__success"; successMsg.textContent = L("A08_Support_Success"); successMsg.hidden = true; // 입력 필드 const nameField = createInputField({ label: L("A08_Support_Field_Name"), placeholder: L("A08_Support_Field_Name_Placeholder"), type: "text", required: true, }); const emailField = createInputField({ label: L("A08_Support_Field_Email"), placeholder: L("A08_Support_Field_Email_Placeholder"), type: "email", required: true, }); const phoneField = createInputField({ label: L("A08_Support_Field_Phone"), placeholder: L("A08_Support_Field_Phone_Placeholder"), type: "tel", required: false, }); const subjectField = createInputField({ label: L("A08_Support_Field_Subject"), placeholder: L("A08_Support_Field_Subject_Placeholder"), type: "text", required: true, }); const messageField = createTextAreaField( L("A08_Support_Field_Message"), L("A08_Support_Field_Message_Placeholder"), ); const form = document.createElement("form"); form.className = "a08-support__form"; form.noValidate = true; const submitBtn = createButton({ label: L("A08_Support_Submit"), variant: "filled", type: "submit", }); submitBtn.classList.add("a08-support__submit"); /* 이벤트 핸들러: 제출 시 1차 유효성 검사 */ function onA08_Support_Submit_Click(event: Event): void { event.preventDefault(); successMsg.hidden = true; nameField.setError(); emailField.setError(); subjectField.setError(); messageField.setError(); const name = nameField.input.value; const email = emailField.input.value; const phone = phoneField.input.value; const subject = subjectField.input.value; const message = messageField.textarea.value; // 필수값 검사 let hasError = false; if (isBlank(name)) { nameField.setError(L("A08_Support_Error_Required")); hasError = true; } if (isBlank(email)) { emailField.setError(L("A08_Support_Error_Required")); hasError = true; } if (isBlank(subject)) { subjectField.setError(L("A08_Support_Error_Required")); hasError = true; } if (isBlank(message)) { messageField.setError(L("A08_Support_Error_Required")); hasError = true; } if (hasError) return; // 형식 검사 if (!isValidEmail(email)) { emailField.setError(L("A08_Support_Error_Email")); return; } // API 호출: POST /api/a08/support (async () => { try { const response = await fetch("/api/a08/support", { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name, email, phone: phone || null, subject, message }), }); if (!response.ok) { const error = (await response.json()) as { detail?: string }; throw new Error(error.detail ?? "요청 실패"); } form.reset(); messageField.textarea.value = ""; successMsg.hidden = false; } catch (err) { const msg = err instanceof Error ? err.message : "오류 발생"; nameField.setError(msg); } })(); } form.addEventListener("submit", onA08_Support_Submit_Click); form.append( nameField.root, emailField.root, phoneField.root, subjectField.root, messageField.root, submitBtn, ); card.append(title, subtitle, successMsg, form); page.append(card); root.append(page); // 백그라운드에서 세션 정보 로드 (페이지 렌더링을 지연시키지 않음) (async () => { const sessionUser = await fetchSessionUser(); if (sessionUser?.name) nameField.input.value = sessionUser.name; if (sessionUser?.email) emailField.input.value = sessionUser.email; })(); }