텍스트 스타일 및 프리뷰 렌더링 정리
CI / test (push) Successful in 11m5s
CI / pr_and_merge (push) Successful in 1m40s
CI / test (pull_request) Failing after 35m42s
CI / pr_and_merge (pull_request) Has been skipped

This commit is contained in:
2025-11-20 23:36:47 +09:00
parent 9e40ee405c
commit 2f59e3781e
19 changed files with 3831 additions and 556 deletions
+165 -2
View File
@@ -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`
- 텍스트 편집 모드가 아닐 때, 실제 텍스트 요소(`<div>`)에도 위 클래스와 `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`
- 퍼블릭 렌더러: 폼 블록을 `<form>` 으로 렌더링하고 `/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` 를 사용해 `<input>`/`<select>`/`<textarea>`/`checkbox`/`radio` 필드 렌더링
- `fieldIds` 가 없거나 매핑된 요소가 없으면 기존 v1 `FormBlockProps.fields` 또는 기본 name/email/message 폼을 fallback 으로 사용
- `submitButtonId` 가 설정된 경우 해당 버튼 블록의 `label` 을 폼의 submit 버튼 라벨로 사용
- submit 시 `/api/forms/submit` 으로 FormData 를 전송하고, 응답이 ok 이면 `successMessage` (기본값 "성공적으로 전송되었습니다.") 를 표시
- 결과: 프리뷰 폼 컨트롤러 v2 E2E 테스트 통과
- **v2 TDD 4단계(radio/checkbox 라벨 구조 정리)**
- 목표: 라디오/체크박스 필드에서 `groupLabel` 은 항상 그룹 타이틀(제목 텍스트)로만 사용하고, 각 옵션의 `label` 이 실제 라벨 역할을 하도록 분리
- 에디터 속성 패널(`EditorPage`)
- 폼 요소 선택 시 라디오/체크박스의 상단 라벨 인풋을 `그룹 타이틀` 로 표기
- 라벨 모드 셀렉트를 라디오/체크박스의 경우 `옵션 라벨 타입` 으로 표기하여 그룹 제목과 옵션 라벨 역할을 명확히 분리
- 에디터 캔버스 렌더링(`EditorPage`)
- `formRadio` / `formCheckbox` 블록에서 상단 헤더는 항상 `groupLabel` 텍스트로만 렌더링
- `labelMode === "image"` 인 경우, 라디오/체크박스 옆 옵션 라벨 위치에 `labelImageUrl`/`labelImageAlt` 를 사용해 이미지 라벨을 표시하고, 기본 모드에서는 옵션의 `label` 텍스트를 그대로 사용
- E2E 테스트(`tests/e2e/editor.spec.ts`)
- "폼 셀렉트/라디오/체크박스 블록도 우측 패널에서 기본 필드 속성을 편집할 수 있어야 한다" 시나리오를 groupLabel/옵션 라벨 타입 분리에 맞게 업데이트
- 라디오/체크박스 선택 시 라벨 입력 필드 이름을 `그룹 타이틀` 로 기대
- 라디오/체크박스 선택 시 `옵션 라벨 타입` 콤보박스 존재 여부를 검증
- **디버깅 기록**
- `tests/unit/editorStore.spec.ts` 하단에 템플릿/undo/redo/remove/duplicate 관련 테스트 블록이 중복 삽입되어 중괄호 정렬이 깨짐
- 증상: Vitest 실행 시 `Unexpected "}"` 파싱 에러 발생
- 조치: 중복된 it 블록들 제거, 기존 describe 내부의 `removeBlock`/`duplicateBlock` 테스트를 정리하고 그 뒤에 폼 관련 2개 테스트만 배치하여 정상 종료
- `tests/unit/PropertyControls.spec.ts` 에는 별도의 TS/JSX 문법 및 타입 오류(onChange, label 등)가 존재하지만, 현재 폼 기능 흐름과 직접 관련 없으므로 후속 단계에서 별도 TDD 리팩터링 대상으로 남겨둠
## 디버깅/CI 관련 기록 (요약)
- CI에서 `prisma generate` 단계가 `DATABASE_URL` 없음으로 실패 → 워크플로에서 더미 `DATABASE_URL` 주입으로 해결
- 유닛 테스트에서 API 테스트가 실제 DB 접속 시도 →