- A01~A05 hero 다섯 벌 · 구역 제목 복제 CSS 걷고 공개 화면 틀 한 벌(wide 옵션 · 구역 제목 가운데 · 머리 아랫줄은 자리 안 먹는 그림자) - 카드 = 카드 부품(로그인 · 가입 · 지원 · 약관 · 기능 카드 · 소식 줄 · B11 단계 카드) · 지원 성공 · B10 안내 = 띠 알림 · 태그 = 배지 - A07 약관 날 체크 · 날 details = 체크 + 접이 부품 · A08 날 textarea = 입력 여러 줄 · 빈 소식 = 빈 상태 - 죽은 A07_Register_UI_Page · 쓰이지 않는 CSS 걷음 · 띠 알림 hidden 받음 - 지킴 시험 test_553_7_public_pages Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
173 lines
5.7 KiB
TypeScript
173 lines
5.7 KiB
TypeScript
/* =============================================================================
|
|
* A08_Support_UI_Page.ts
|
|
* 로그인 전 08: 기술지원 요청 (이름 + 이메일 + 제목 + 문의내용 폼)
|
|
*
|
|
* 제약 준수:
|
|
* - 모든 문구는 ui_template_locale.ts 참조 (하드코딩 금지).
|
|
* - 색상/간격은 A08_Support_UI_Style.css + theme.css 변수만 사용.
|
|
* - 공통 부품(createCard/createBanner/createInputField/createButton) 우선 사용.
|
|
* - 이벤트 핸들러는 on[페이지]_[기능]_[액션] 명명.
|
|
* - 백엔드 전송 전 1차 유효성 검사 (frontend.md §4).
|
|
* ========================================================================== */
|
|
|
|
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
|
|
import { createBanner } from "@ui/ui_template_banner";
|
|
import { createButton, createCard, 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];
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* 페이지 진입점
|
|
* -------------------------------------------------------------------------- */
|
|
export function renderA08Support(root: HTMLElement): void {
|
|
const page = document.createElement("div");
|
|
page.className = "a08-support";
|
|
|
|
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 = createBanner({ tone: "success", text: 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 = createInputField({
|
|
label: L("A08_Support_Field_Message"),
|
|
placeholder: L("A08_Support_Field_Message_Placeholder"),
|
|
multiline: 6,
|
|
});
|
|
|
|
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.input.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.input.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,
|
|
);
|
|
|
|
const card = createCard({ body: [title, subtitle, successMsg, form] });
|
|
card.classList.add("a08-support__card");
|
|
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;
|
|
})();
|
|
}
|