diff --git a/MAIN_PLAN.md b/MAIN_PLAN.md index 7654f58..95ca2ff 100644 --- a/MAIN_PLAN.md +++ b/MAIN_PLAN.md @@ -20,10 +20,33 @@ - 키보드 숏컷 확장 및 포커스 관리 개선 - 인라인 편집 UX 다듬기 -3. **builder-12-theme-output** +3. **## builder-12-theme-output 상세 플랜 - Tailwind 기반 디자인 시스템 정리 (색상/타이포/간격 스케일) - Public/Preview 렌더링 품질 개선 (반응형, 여백, 타이포그래피) - - 샘플 테마 프리셋 추가 + - 샘플 테마 프리셋 추가 + +### 3) 텍스트 스타일 시스템 정리 (에디터 쪽 선행 작업) + +- 에디터 텍스트 블록 속성 구조와 렌더링 규칙 정리 + - `TextBlockProps` 에서 정렬/폰트 크기/줄 간격/자간/색상/최대 너비/장식 등 타이포 관련 속성 확장 + - `TextPropertiesPanel` 에서 위 속성들을 슬라이더/프리셋/컬러 피커로 제어하도록 구현 + - `SortableEditorBlock` 에서 텍스트 블록 렌더링 시 + - `align` → `pb-text-left/center/right` + - `fontSizeMode`/`fontSizeScale`/`fontSizeCustom` → `pb-text-*` 또는 `style.fontSize` + - `lineHeightMode`/`lineHeightScale`/`lineHeightCustom` → `pb-leading-*` 또는 `style.lineHeight` + - `letterSpacingCustom` → `style.letterSpacing` + - `colorPalette`/`colorCustom` → `pb-text-color-*` 또는 `style.color` + - `maxWidthMode`/`maxWidthScale`/`maxWidthCustom` → `pb-text-maxw-*` 또는 `style.maxWidth` + - `underline`/`strike`/`italic` → `pb-underline`/`pb-line-through`/`pb-italic` + - 텍스트 편집 모드가 아닐 때, 실제 텍스트 요소(`
`)에도 위 클래스와 `textStyleOverrides` 를 직접 적용해 시각적 일관성 확보 +- 테스트 정렬 + - `tests/e2e/editor.spec.ts` 에서 텍스트/구분선/리스트 블록 관련 정렬/크기 기대값을 Tailwind `text-*` 가 아니라 `pb-text-*` 유틸리티 네이밍에 맞게 조정 + - JSON 내보내기/불러오기 시나리오에서도 `pb-text-left/right/sm/lg` 클래스가 복원되는지 검증 + +4. **builder-13-forms** + - 폼 전송 구조 설계 및 구현 (internal API + Webhook/Google Sheets) + - 폼 전송 설정(헤더/토큰/추가 파라미터) 및 다중 폼 컨트롤러 구조 설계 + - 폼 요소 블록(input/select/radio/checkbox) 추가 및 자유 레이아웃 배치 지원 --- @@ -112,6 +135,146 @@ --- +## builder-13-forms 상세 플랜 + +### 1) 폼 전송 구조 (v1: 단일 폼 + 필드 배열) +- **목표** + - SPA 스타일로 페이지 리로드 없이 폼을 전송하고, CORS 문제 없이 외부 API/Google Sheets 로 연동 + +- **현재 구현 상태 요약** + - `FormBlockProps` 에 전송 설정 추가 + - `submitTarget: "internal" | "webhook"` + - `destinationUrl`, `method`, `headers`, `extraParams` + - `successMessage`, `errorMessage` + - 퍼블릭 렌더러: 폼 블록을 `
` 으로 렌더링하고 `/api/forms/submit` 으로 `FormData` 전송 + - `/api/forms/submit` 라우트: + - `__config` hidden 필드로 넘어온 `FormBlockProps` 를 파싱 + - `submitTarget === "internal"` 일 때 서버 내부 처리 (향후 DB 저장/메일 발송용) + - `submitTarget === "webhook"` 일 때 `destinationUrl` 로 서버사이드 POST (x-www-form-urlencoded) + - `headers`/`extraParams` 를 함께 전송하여 토큰/고정 파라미터 전달 + - 폼 필드 정의: + - `FormFieldConfig` (`id`, `name`, `label`, `type`, `required`) + - `FormBlockProps.fields?: FormFieldConfig[]` 로 필드 배열 관리 + - 에디터 우측 패널에서 필드 추가/삭제/순서/라벨/name/type/필수 여부 편집 UI 제공 + +- **TDD 순서 (v1)** + 1. `tests/unit/editorStore.spec.ts` + - `FormBlockProps`/`FormFieldConfig` 초기값, `addFormBlock` 동작 테스트 + 2. `editorStore.ts` + - `FormFieldConfig`, `FormBlockProps` 정의 및 `addFormBlock` 구현 + 3. `PublicPageRenderer.tsx` + - `fields` 기반 폼 렌더링 및 `/api/forms/submit` 호출 로직 구현 + 4. `src/app/api/forms/submit/route.ts` + - internal/webhook 분기 및 Webhook/Google Sheets 전송 로직 구현 + 5. `tests/e2e/editor.spec.ts`/`tests/e2e/preview.spec.ts` + - 폼 추가 → 필드 수정 → 프리뷰에서 폼 전송 → 성공/에러 메시지 확인 시나리오 추가 + +### 2) 폼 요소 블록 + 폼 컨트롤러 구조 (v2: 자유 레이아웃) +- **목표** + - 폼 요소(input/select/radio/checkbox)를 텍스트 블록처럼 섹션/컬럼 안에 자유롭게 배치 + - 폼 블록은 전송 설정 + 어떤 요소(id)들을 묶어서 어디로 보낼지 제어하는 컨트롤러 역할만 수행 + - 페이지 내에 여러 폼 블록이 있어도 각기 다른 필드/submit 버튼/전송 설정을 사용 가능 + +- **설계 개요** + - 새 블록 타입 추가: + - `formInput`, `formSelect`, `formRadio`, `formCheckbox` 등 + - 각 props 예시: + - `FormInputBlockProps`: `label`, `placeholder`, `formFieldName?`, `formFieldType`, `required?` + - `FormSelectBlockProps`: `label`, `formFieldName?`, `options[]`, `required?` + - `FormRadioBlockProps`: `groupLabel`, `formFieldName?`, `options[]`, `required?` + - `FormCheckboxBlockProps`: `label`, `formFieldName?`, `required?` + - `FormBlockProps` 확장: + - `fieldIds: string[]` (이 폼이 담당하는 폼 요소 블록들의 id) + - `submitButtonId?: string` (이 버튼이 눌리면 이 폼을 전송) + - 좌측 사이드바에 "폼 요소" 섹션 추가: + - 텍스트 입력/셀렉트/라디오 그룹/체크박스 블록 추가 버튼 + - `editorStore` 에 `addFormInputBlock`/`addFormSelectBlock` 등 액션 추가 + - 런타임 동작: + - 버튼 클릭 → 해당 버튼 id 를 `submitButtonId` 로 가진 FormBlock 검색 + - FormBlock.fieldIds 에 포함된 블록들 중 `formFieldName` 이 설정된 요소만 수집 + - 현재 값(value)을 모아 `FormData` 또는 JSON 으로 `/api/forms/submit` 에 전송 + +- **TDD 순서 (v2)** + 1. `tests/unit/editorStore.spec.ts` + - 새 폼 요소 블록 타입 추가 액션(addFormInputBlock 등) 테스트 + - FormBlock 의 `fieldIds`/`submitButtonId` 기본값 및 업데이트 동작 테스트 + 2. `editorStore.ts` + - `BlockType` 에 폼 요소 타입 추가, 각 props/interface 정의 + - `addFormInputBlock` 등 구현 (섹션/컬럼 내 자유 배치) + 3. `src/app/editor/forms/elements/*.tsx` + - `InputElement`/`SelectElement`/`RadioGroupElement`/`CheckboxElement` 컴포넌트 구현 및 에디터/퍼블릭 렌더링 연계 + 4. `src/app/editor/page.tsx` + - 좌측 사이드바에 "폼 요소" 섹션 추가 및 액션 연결 + - 블록 렌더링 분기에 새 폼 요소 블록 타입 처리 추가 + - 우측 폼 패널에 FormBlock 의 `fieldIds`/`submitButtonId` 매핑 UI 추가 + 5. `tests/e2e/editor.spec.ts`/`tests/e2e/preview.spec.ts` + - 섹션/컬럼 안에 폼 요소들을 자유 배치 → FormBlock 에 id 매핑 → 버튼 클릭 시 해당 요소 값만 전송되는지 검증 + +폼 기능 역시 다른 단계와 동일하게 **실패하는 테스트 → 최소 구현 → 리팩터링** 순서를 유지하며, Webhook/Google Sheets 연동 시 CORS 문제를 피하기 위해 항상 `/api/forms/submit` 경유 구조를 사용한다. + +### 3) 진행 현황 (2025-11-20) +- **v2 TDD 1단계(editorStore)** + - `tests/unit/editorStore.spec.ts` + - `formInput` 블록 추가 액션(`addFormInputBlock`)에 대한 유닛 테스트 작성 + - FormBlock 컨트롤러 기본 속성(`fieldIds`, `submitButtonId`) 초기값 유닛 테스트 작성 + - `src/features/editor/state/editorStore.ts` + - `BlockType`에 `"formInput"` 타입 추가 + - `FormInputBlockProps` 정의 및 공통 `Block.props` 유니온에 포함 + - `FormBlockProps`에 `fieldIds?: string[]`, `submitButtonId?: string | null` 추가 + - `addFormBlock`에서 `fieldIds: []`, `submitButtonId: null` 기본값 설정 + - `addFormInputBlock` 액션 구현 (루트에 기본 label/formFieldName 으로 추가, 선택 상태 업데이트) + - 결과: `editorStore.spec.ts` 전체 25개 테스트 모두 통과 + +- **v2 TDD 2단계(editorStore + Editor UI)** + - `tests/unit/editorStore.spec.ts` + - `formSelect`/`formRadio`/`formCheckbox` 블록 추가 액션 및 FormBlock `fieldIds`/`submitButtonId` 업데이트 테스트 추가 + - `src/features/editor/state/editorStore.ts` + - `BlockType`에 `"formSelect"`, `"formRadio"`, `"formCheckbox"` 추가 + - `FormSelectBlockProps`/`FormRadioBlockProps`/`FormCheckboxBlockProps` 정의 및 `Block.props`/`updateBlock`에 반영 + - `addFormSelectBlock`/`addFormRadioBlock`/`addFormCheckboxBlock` 구현 (루트에 기본 label/options 로 추가) + - `src/app/editor/page.tsx` + - 좌측 사이드바에 "폼 요소" 섹션 추가 + - 우측 속성 패널에서 FormBlock 선택 시 "폼 컨트롤러" 섹션 추가 + - 체크박스/셀렉트 변경 시 `updateBlock` 으로 FormBlock 의 `fieldIds`/`submitButtonId` 업데이트 + - `tests/e2e/editor.spec.ts` + - "폼 요소 사이드바에서 폼 입력/셀렉트/라디오/체크박스 블록을 추가할 수 있어야 한다" 시나리오 추가 + - "폼 블록을 선택하면 우측 속성 패널에서 폼 필드/버튼 매핑 UI가 보여야 한다" 시나리오 추가 + - 결과: 관련 유닛/E2E 테스트 모두 통과 + +- **v2 TDD 3단계(Preview/Public 렌더러 폼 제출 v2)** + - `tests/e2e/preview.spec.ts` + - "폼 컨트롤러에 매핑한 폼 요소와 버튼이 프리뷰에서 함께 동작해야 한다" 시나리오 추가 + - 에디터에서 `formInput`/`button`/`form` 블록을 추가 + - FormBlock 의 `fieldIds`/`submitButtonId` 매핑 설정 후 `/preview` 이동 + - 프리뷰에서 입력값을 채우고 "버튼" 클릭 시 성공 메시지("성공적으로 전송되었습니다.")가 표시되는지 검증 + - `src/features/editor/components/PublicPageRenderer.tsx` + - FormBlock 렌더링 시 v2 컨트롤러 우선 사용 + - `fieldIds` 가 설정된 경우 해당 id 를 가진 `formInput`/`formSelect`/`formCheckbox`/`formRadio` 블록들만 수집 + - 각 폼 요소 블록의 `formFieldName`/`label`/`options` 를 사용해 ``/`