docs(core,extensions): 확장 20개 개발자 문서 완비와 문서 소유 이관
번들 확장 20개 전부에 AGENTS.md · README.md · docs/ 를 채우고, 코어가 들고 있던 확장 소유 문서 두 갈래를 그 확장으로 옮긴다. 번들 템플릿의 컴포넌트· 핸들러·레이아웃 상세와 확장이 구독하는 활동 로그 훅 목록이 그 대상이며, 코어에는 총계와 링크만 남아 확장이 기능을 늘릴 때 코어 문서를 고쳐야 하던 역방향 의존이 사라진다. 전수 완비를 확인하고 강제를 조인다 — 문서 동반 룰을 대상 목록 없는 error 로 승격하고, 검사 스크립트가 문서 미보유를 실패로 올리며, 미채움 마커 baseline 을 0 으로 기록한다. 한쪽만 조이면 "새 확장이 문서 없이 들어와도 초록" 인 상태가 남는데 그 결과는 이상 0건과 구분되지 않는다. 집필 과정에서 드러난 생성기 결함 셋을 함께 고친다. 스케줄 주기 열이 계약 키를 읽지 않아 모든 확장에서 '-' 였고, 네임스페이스를 붙인 핸들러 등록 키가 수집에서 통째로 빠졌으며, README 골격이 폐기된 히어로 배지를 계속 찍어내고 있었다. 셋 다 산출물이 아니라 원천이 틀린 것이라, 가드의 모집단에 생성기 출력 자체를 넣어 다음 확장이 같은 상태로 태어나는 경로를 막는다.
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
# Hello Admin Template — 에이전트 가이드
|
||||
|
||||
> 이 문서는 이 템플릿을 수정하는 에이전트·확장개발자를 위한 것입니다. 도입 검토·운영 관점은 [README.md](README.md) 를 보세요.
|
||||
|
||||
## TL;DR (5초 요약)
|
||||
|
||||
```text
|
||||
1. 유형: 템플릿 (gnuboard7-hello_admin_template, type=admin) — 학습용 최소 Admin 템플릿. Basic 컴포넌트 8 + 베이스 1 + 대시보드 1 + **오류 6종**이 전부. `hidden: true`
|
||||
2. 확장 방식: 훅 없음 — 확장점은 화면 구조다. 제공 컴포넌트 목록이 곧 계약이며, 모듈 조각은 그 안의 컴포넌트만 쓸 수 있다
|
||||
3. 건드리면 안 되는 것: 오류 레이아웃 6종 축소, 401 에서 직접 리다이렉트, 컴포넌트 등록 재시도 제거, 샘플에 실제 관리 화면 추가
|
||||
4. 작업 위치: `templates/_bundled/gnuboard7-hello_admin_template` — 활성 디렉토리 직접 수정 금지
|
||||
5. 반영: `php artisan template:update gnuboard7-hello_admin_template --force`
|
||||
```
|
||||
|
||||
## 1. 이 확장은 무엇인가
|
||||
|
||||
<!-- @intent START -->
|
||||
**학습용 최소 Admin 템플릿**입니다. 관리자 템플릿이 성립하기 위한 **최소 구성**만 담아
|
||||
"템플릿은 이 정도만 있으면 동작한다" 를 보이는 것이 목적입니다.
|
||||
|
||||
담긴 것은 넷뿐입니다 — Basic 컴포넌트 8개(`Div` · `Button` · `H1` · `H2` · `H3` · `A` ·
|
||||
`Span` · `Img`), 베이스 레이아웃 `_admin_base`, 대시보드 화면 하나, 그리고 **오류 레이아웃
|
||||
6종**(401 · 403 · 404 · 500 · 503 · maintenance).
|
||||
|
||||
**오류 6종이 필수인 것이 이 샘플의 핵심 학습 포인트**입니다. 코어는 오류 상황에서 활성 템플릿의
|
||||
해당 레이아웃을 부르는데, 없으면 그 오류가 화면에 나타나지 못합니다 — 사용자에게는 백지가
|
||||
됩니다. 화면이 하나뿐인 템플릿에도 이 6종은 있어야 합니다.
|
||||
|
||||
`manifest.hidden = true` 라 관리자 UI 의 템플릿 목록에 나타나지 않습니다. artisan CLI 로는
|
||||
정상 설치·활성화됩니다.
|
||||
|
||||
**의도적으로 하지 않는 것**: 실제 관리 화면·composite/layout 컴포넌트·핸들러·다국어 확장·
|
||||
SEO 설정. `sirsoft-admin_basic` 이 컴포넌트 125개와 화면 145개를 갖는 것과 대비해 보면, 무엇이
|
||||
**필수**이고 무엇이 그 템플릿의 선택인지가 드러납니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 2. 디렉토리 지도
|
||||
|
||||
<!-- @generated:directory-map START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 경로 | 역할 | 수정 시 필요한 절차 |
|
||||
|---|---|---|
|
||||
| `template.json` | manifest (버전 SSoT) | version 변경 시 package.json·package-lock.json 동기화 |
|
||||
| `routes.json` | 라우트 → 레이아웃 매핑 | `php artisan template:update gnuboard7-hello_admin_template --force` |
|
||||
| `layouts/` | 레이아웃 JSON | `php artisan template:update gnuboard7-hello_admin_template --force` (빌드 불필요) |
|
||||
| `src/components/` | React 컴포넌트 | `php artisan template:build` → `php artisan template:update gnuboard7-hello_admin_template --force` |
|
||||
| `dist/` | 커밋되는 빌드 산출물 | `--production` 으로 재빌드 (sourceMappingURL 잔존 금지) |
|
||||
| `CHANGELOG.md` | 변경 이력 | 버전 상향 시 항목 추가 (미기재 시 버전 상향 불가) |
|
||||
| `components.json` | 편집기 컴포넌트 선언 (레이아웃 저작자가 읽는 props 계약) | `php artisan template:update gnuboard7-hello_admin_template --force` |
|
||||
| `docs/` | 개발자 문서 | 표면 변경 시 `php artisan ext:docgen` 재실행 |
|
||||
| `lang/` | 다국어 | 키 추가 시 ko·en 동시 반영 + 번들 ja 팩 동기화 |
|
||||
<!-- @generated:directory-map END -->
|
||||
|
||||
## 3. 핵심 흐름
|
||||
|
||||
<!-- @intent START -->
|
||||
서버 코드가 없으므로 흐름은 **부트스트랩 → 컴포넌트 등록 → 라우트 → 레이아웃** 입니다.
|
||||
|
||||
**부트스트랩**: `src/index.ts` 가 모듈 로드 시 `initTemplate()` 을 스스로 실행합니다. 그 안에서
|
||||
코어의 `ComponentRegistry` 를 찾아 Basic 8개를 등록하는데, **레지스트리가 아직 준비되지 않았을
|
||||
수 있으므로 100ms 간격으로 최대 50회 재시도**합니다(`window.load` 이후 시작). 이 재시도가 없으면
|
||||
로드 순서에 따라 컴포넌트가 등록되지 않고, 그러면 레이아웃이 참조하는 이름을 찾지 못해 화면이
|
||||
비게 됩니다.
|
||||
|
||||
**화면 렌더**: `routes.json` 이 `*/admin` 을 `admin_dashboard` 에 대응 → 그 레이아웃이
|
||||
`_admin_base` 를 상속해 공통 뼈대를 얻고 콘텐츠를 채웁니다.
|
||||
|
||||
**오류 렌더**: 코어가 401/403/404/500/503/maintenance 상황을 만나면 활성 템플릿의 해당
|
||||
레이아웃을 부릅니다. 여섯 모두 `_admin_base` 를 상속하므로 오류 화면에서도 공통 뼈대가
|
||||
유지됩니다.
|
||||
|
||||
`401` 레이아웃에서 **로그인 리다이렉트를 직접 구현하지 않습니다** — 코어
|
||||
`TemplateApp.showRouteError` 가드가 처리하므로, 여기서 다시 이동시키면 이중 리다이렉트가
|
||||
됩니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 4. 확장점
|
||||
|
||||
<!-- @generated:extension-points-summary START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 확장점 | 수 | 상세 |
|
||||
|---|---|---|
|
||||
| 제공 컴포넌트 | 8개 | [제공 컴포넌트](docs/components.md#제공-컴포넌트) |
|
||||
| 레이아웃 | 8개 | [레이아웃 목록](docs/layouts.md#레이아웃-목록) |
|
||||
| 전용 핸들러 | 0개 | [템플릿 전용 핸들러](docs/handlers.md#템플릿-전용-핸들러) |
|
||||
| 확장 오버라이드 | 0개 | [확장 오버라이드](docs/layouts.md#확장-오버라이드) |
|
||||
<!-- @generated:extension-points-summary END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
템플릿의 확장점은 훅이 아니라 **화면 구조**입니다. 이 샘플은 그 구조의 최소 형태를 보입니다.
|
||||
|
||||
| 확장점 | 이 샘플에서 |
|
||||
|---|---|
|
||||
| 제공 컴포넌트 | Basic 8개. 다른 확장이 조각을 끼워 넣을 때 **여기 있는 것만** 쓸 수 있습니다 |
|
||||
| 레이아웃 | 8개(베이스 1 + 화면 1 + 오류 6). 모듈이 `layout_extensions` 로 조각을 끼울 자리는 대시보드뿐입니다 |
|
||||
| 확장 오버라이드 | 없음. `extensions/{확장}/` 에 같은 이름의 조각을 두면 그 확장이 제공한 원본을 대체합니다 |
|
||||
| 전용 핸들러 | 없음 |
|
||||
|
||||
**컴포넌트 목록이 곧 계약**이라는 점이 중요합니다. 모듈이 관리자 화면 조각을 만들 때 `DataGrid`
|
||||
같은 컴포넌트를 쓰면, 그것을 제공하지 않는 이 템플릿에서는 **그 조각이 렌더되지 않습니다.**
|
||||
모듈이 어느 템플릿에서든 동작하려면 코어가 정한 **필수 컴포넌트 집합**만 써야 하며, 그 목록의
|
||||
SSoT 는 코어 `config/template.php` 의 `required_admin_components` 입니다.
|
||||
|
||||
이 샘플이 Basic 8개만 갖는 것은 그래서 실험이기도 합니다 — 이 템플릿으로 바꿨을 때 깨지는
|
||||
화면이 있다면, 그 화면이 템플릿 고유 컴포넌트에 의존하고 있다는 뜻입니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 5. 수정 시 동반 의무
|
||||
|
||||
- [ ] `_bundled` 에서만 수정하고 `php artisan template:update gnuboard7-hello_admin_template --force` 로 반영
|
||||
- [ ] manifest version 상향 시 `package.json` · `package-lock.json` 동기화 + CHANGELOG 기재
|
||||
- [ ] 레이아웃 JSON 변경 시 빌드 없이 update 만 — 신규 Tailwind 클래스는 빌드된 CSS 에 존재하는지 확인
|
||||
- [ ] TSX/TS 변경 시 `--production` 재빌드 후 `dist/` 커밋 (sourceMappingURL 잔존 금지)
|
||||
- [ ] 다국어 키 추가 시 ko·en 동시 반영 + 번들 ja 언어팩 증분 동기화
|
||||
- [ ] 오류 레이아웃 6종(401·403·404·500·503·maintenance)이 모두 있는지 확인 — 하나라도 없으면 그 상황에서 백지가 된다
|
||||
- [ ] 컴포넌트를 추가·삭제했다면 소스 · `template.json` 레지스트리 · `components.json` 을 함께 갱신
|
||||
- [ ] `manifest.hidden = true` 를 유지 (복제본에서만 제거)
|
||||
- [ ] TSX 를 고쳤다면 `template:build --production` 후 `dist/` 동반 커밋 (`sourceMappingURL` 잔존 금지)
|
||||
- [ ] `docs/extension/sample-extensions.md` 의 계층 표와 어긋나지 않는지 확인
|
||||
- [ ] 레이아웃 렌더링 테스트(`__tests__/layouts/*.test.tsx`)는 이 템플릿 디렉토리에 둔다 — 코어 디렉토리에 두지 않는다
|
||||
|
||||
## 6. 금지 패턴
|
||||
|
||||
<!-- @intent START -->
|
||||
| 금지 | 올바른 사용 | 이유 |
|
||||
|---|---|---|
|
||||
| 오류 레이아웃 6종 중 일부를 빼기 | 401 · 403 · 404 · 500 · 503 · maintenance 전부 유지 | 코어가 그 상황에서 활성 템플릿의 레이아웃을 부른다 — 없으면 사용자에게 백지가 된다 |
|
||||
| `401` 레이아웃에서 로그인으로 직접 리다이렉트 | 코어 `TemplateApp.showRouteError` 가드에 위임 | 이중 리다이렉트가 되고, 돌아올 위치를 코어가 이미 관리한다 |
|
||||
| 컴포넌트 등록에 재시도 없이 한 번만 시도 | `initTemplate()` 의 재시도 루프 유지 | 로드 순서에 따라 레지스트리가 아직 없을 수 있다 — 등록에 실패하면 레이아웃이 이름을 못 찾아 화면이 빈다 |
|
||||
| `extends` 없는 독립 레이아웃에서 `toast`·`openModal` 사용 | `_admin_base` 를 상속하거나 호스트 컴포넌트를 직접 마운트 | 호스트가 없으면 핸들러는 성공으로 기록되는데 화면에는 아무것도 나타나지 않는다 |
|
||||
| 이 샘플에 실제 관리 화면을 더해 "쓸모 있게" 만들기 | 짧게 유지하고, 필요한 화면은 별도 템플릿으로 | 샘플의 가치는 "최소 구성이 무엇인가" 를 보이는 것이다 |
|
||||
| `manifest.hidden` 을 제거 | 그대로 둔다 (복제본에서만 제거) | 학습용 템플릿이 운영 사이트의 템플릿 목록에 섞인다 |
|
||||
| 컴포넌트를 추가하면서 `template.json` 의 레지스트리를 갱신하지 않기 | 소스·레지스트리·`components.json` 을 함께 | 레이아웃이 참조하는 이름을 찾지 못해 그 컴포넌트만 조용히 렌더되지 않는다 |
|
||||
| TSX 를 고치고 `dist/` 재빌드 없이 커밋 | `template:build --production` 후 `dist/` 동반 커밋 | 브라우저가 받는 것은 커밋된 `dist/` 다 — 소스 수정이 사문화된다 |
|
||||
<!-- @intent END -->
|
||||
|
||||
## 7. 테스트 실행
|
||||
|
||||
<!-- @generated:test-commands START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 종류 | 개수 | 위치 |
|
||||
|---|---|---|
|
||||
| PHPUnit | 0개 | — |
|
||||
| Vitest | 2개 | `vitest.config.ts` |
|
||||
| Playwright | 0개 | — |
|
||||
| 시나리오 매니페스트 | 0개 | — |
|
||||
|
||||
```bash
|
||||
# Vitest (확장 디렉토리에서) (PowerShell)
|
||||
cd templates/_bundled/gnuboard7-hello_admin_template && powershell -Command "npm run test:run -- <대상>"
|
||||
|
||||
```
|
||||
|
||||
무필터 전체 실행은 금지되어 있습니다 — 변경 범위에 걸리는 대상만 지정해 실행합니다.
|
||||
<!-- @generated:test-commands END -->
|
||||
|
||||
## 8. 문서 목차
|
||||
|
||||
<!-- @generated:docs-index START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 문서 | 내용 | 상태 |
|
||||
|---|---|---|
|
||||
| [docs/README.md](docs/README.md) | 문서 통합 목차와 실측 집계 | ✅ |
|
||||
| [docs/architecture.md](docs/architecture.md) | 설계 의도·계층 지도·디렉토리 맵 | ✅ |
|
||||
| [docs/components.md](docs/components.md) | 템플릿이 제공하는 컴포넌트 | ✅ |
|
||||
| [docs/layouts.md](docs/layouts.md) | 레이아웃 목록과 라우트 매핑 | ✅ |
|
||||
| [docs/handlers.md](docs/handlers.md) | 템플릿 전용 핸들러와 부트스트랩 | ✅ |
|
||||
| [CHANGELOG.md](CHANGELOG.md) | 변경 이력 | ✅ |
|
||||
<!-- @generated:docs-index END -->
|
||||
@@ -9,6 +9,7 @@
|
||||
### Added
|
||||
|
||||
- 아이콘(Font Awesome)을 템플릿에 함께 담아 외부 CDN 없이 동작합니다.
|
||||
- 개발자와 AI 에이전트를 위한 문서를 추가했습니다. 확장 폴더의 `AGENTS.md`(설계 의도·확장점·수정 시 확인할 것)와 `README.md`(도입·운영 안내), `docs/`(상세 문서)로 구성됩니다.
|
||||
|
||||
## [0.1.0] - 2026-07-01
|
||||
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
# Hello Admin Template
|
||||
|
||||
**G7 템플릿 · gnuboard7-hello_admin_template**
|
||||
그누보드7 학습용 최소 Admin 템플릿 스켈레톤
|
||||
|
||||
<!-- @generated:badges START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/version-0.1.1-0066FF?style=flat-square" alt="version 0.1.1">
|
||||
<img src="https://img.shields.io/badge/type-%ED%85%9C%ED%94%8C%EB%A6%BF-555555?style=flat-square" alt="type 템플릿">
|
||||
<img src="https://img.shields.io/badge/G7-%3E%3D7.0.10-1F883D?style=flat-square" alt="G7 >=7.0.10">
|
||||
<img src="https://img.shields.io/badge/license-MIT-8250DF?style=flat-square" alt="license MIT">
|
||||
</p>
|
||||
<!-- @generated:badges END -->
|
||||
|
||||
---
|
||||
|
||||
[소개](#소개) · [주요 기능](#주요-기능) · [동작 방식](#동작-방식) · [요구 사항](#요구-사항) · [설치](#설치) · [제공 컴포넌트](#제공-컴포넌트) · [사용 방법](#사용-방법) · [다른 확장과의 연동](#다른-확장과의-연동) · [문서](#문서) · [트러블슈팅](#트러블슈팅) · [변경 이력](#변경-이력) · [라이선스](#라이선스)
|
||||
|
||||
---
|
||||
|
||||
## 소개
|
||||
|
||||
<!-- @intent START -->
|
||||
그누보드7 **관리자 템플릿이 어떻게 생겼는지 보여주는 학습용 샘플**입니다. 실제 운영에 쓰기
|
||||
위한 것이 아니라, "관리자 템플릿은 최소한 무엇을 갖춰야 하는가" 를 보이는 것이 목적입니다.
|
||||
|
||||
담긴 것은 화면 부품 8개, 공통 뼈대 하나, 대시보드 한 장, 그리고 **오류 화면 6종**입니다.
|
||||
오류 화면은 선택이 아니라 필수입니다 — 없으면 오류가 생겼을 때 방문자에게 아무것도 보이지
|
||||
않습니다.
|
||||
|
||||
관리자 화면의 템플릿 목록에는 나타나지 않습니다(학습용이 운영 목록에 섞이지 않도록). 명령줄로
|
||||
설치·활성화할 수 있으며, 활성화하면 관리자 화면이 이 최소 템플릿으로 바뀝니다 — 기본 템플릿과
|
||||
나란히 비교해 보면 무엇이 필수이고 무엇이 그 템플릿의 선택인지 드러납니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 주요 기능
|
||||
|
||||
<!-- @intent START -->
|
||||
| 영역 | 설명 |
|
||||
|---|---|
|
||||
| 화면 부품 | 가장 기본적인 8개 (영역·버튼·제목 3종·링크·글자·이미지) |
|
||||
| 공통 뼈대 | 모든 화면이 물려받는 관리자 기본 골격 |
|
||||
| 대시보드 | 관리자 첫 화면 예시 한 장 |
|
||||
| 오류 화면 | 401·403·404·500·503·점검 중 6종 |
|
||||
| 다국어 | 한국어·영어 공통 문구 |
|
||||
| 테스트 | 화면이 실제로 그려지는지 확인하는 렌더링 테스트 예시 |
|
||||
<!-- @intent END -->
|
||||
|
||||
## 동작 방식
|
||||
|
||||
<!-- @intent START -->
|
||||
```mermaid
|
||||
flowchart TD
|
||||
B[_admin_base<br/>관리자 공통 뼈대] --> D[admin_dashboard<br/>대시보드]
|
||||
B --> E401[401 권한 없음]
|
||||
B --> E403[403 접근 거부]
|
||||
B --> E404[404 없는 페이지]
|
||||
B --> E500[500 서버 오류]
|
||||
B --> E503[503 이용 불가]
|
||||
B --> EM[maintenance 점검 중]
|
||||
```
|
||||
|
||||
모든 화면이 하나의 공통 뼈대를 물려받습니다. 그래서 오류 화면에서도 관리자 화면의 골격이
|
||||
유지되고, 뼈대를 한 번 고치면 전 화면에 반영됩니다.
|
||||
|
||||
오류 화면 6종은 오류가 났을 때 시스템이 직접 찾아 부르는 화면입니다. 하나라도 없으면 그
|
||||
상황에서 아무것도 보이지 않으므로, 화면이 한 장뿐인 템플릿에도 6종은 있어야 합니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 요구 사항
|
||||
|
||||
<!-- @generated:requirements START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 항목 | 값 |
|
||||
|---|---|
|
||||
| G7 코어 | `>=7.0.10` |
|
||||
| PHP | `^8.2` |
|
||||
<!-- @generated:requirements END -->
|
||||
|
||||
## 설치
|
||||
|
||||
<!-- @generated:install START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
```bash
|
||||
# 번들 설치 (코어에 동봉된 소스에서 설치)
|
||||
php artisan template:install gnuboard7-hello_admin_template
|
||||
|
||||
# 활성화
|
||||
php artisan template:activate gnuboard7-hello_admin_template
|
||||
|
||||
# 업데이트 (번들 소스 기준 강제 반영)
|
||||
php artisan template:update gnuboard7-hello_admin_template --force
|
||||
```
|
||||
<!-- @generated:install END -->
|
||||
|
||||
## 제공 컴포넌트
|
||||
|
||||
<!-- @generated:settings-summary START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
컴포넌트 8개 (루트: `src/components`).
|
||||
|
||||
| 분류 | 개수 |
|
||||
|---|---|
|
||||
| `basic` | 8개 |
|
||||
<!-- @generated:settings-summary END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
관리자 화면을 그리는 데 쓰는 **부품 8개**입니다. 기본 관리자 템플릿이 100개가 넘는 부품을 갖는
|
||||
것과 비교하면, 이 샘플이 얼마나 최소한만 담고 있는지 알 수 있습니다.
|
||||
|
||||
부품 목록은 **모듈과의 계약**이기도 합니다. 모듈이 관리자 화면 조각을 만들 때 이 템플릿에 없는
|
||||
부품(예: 표 형태의 목록 부품)을 쓰면, 그 조각은 이 템플릿에서 그려지지 않습니다. 그래서 모듈이
|
||||
어느 템플릿에서든 동작하려면 정해진 **필수 부품**만 써야 합니다.
|
||||
|
||||
이 템플릿으로 바꿔 보면 그 규칙을 지키지 않은 화면이 드러납니다 — 어떤 모듈 화면이 깨진다면 그
|
||||
화면이 특정 템플릿의 고유 부품에 기대고 있다는 뜻입니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 사용 방법
|
||||
|
||||
<!-- @intent START -->
|
||||
**설치해 보기**: 관리자 목록에 나타나지 않으므로 명령줄로 설치합니다.
|
||||
|
||||
```bash
|
||||
php artisan template:install gnuboard7-hello_admin_template
|
||||
php artisan template:activate gnuboard7-hello_admin_template
|
||||
```
|
||||
|
||||
활성화하면 관리자 화면이 이 최소 템플릿으로 바뀝니다. 원래 템플릿으로 되돌리려면 그 템플릿을
|
||||
다시 활성화하면 됩니다.
|
||||
|
||||
**모듈 호환성 확인용으로 쓰기**: 만들고 있는 모듈의 관리자 화면이 특정 템플릿에만 의존하지
|
||||
않는지 확인할 때 유용합니다. 이 템플릿으로 바꿔서 화면이 깨진다면 그 화면이 필수 부품 밖의
|
||||
것을 쓰고 있다는 뜻입니다.
|
||||
|
||||
**새 관리자 템플릿의 출발점으로 쓰기**: 이 디렉토리를 복제한 뒤 식별자를 바꾸고 학습용 표시를
|
||||
지우면 새 템플릿이 됩니다. 부품과 화면을 필요한 만큼 더해 나가면 됩니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 다른 확장과의 연동
|
||||
|
||||
<!-- @generated:integrations START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
**이 확장이 의존하는 확장**
|
||||
|
||||
없음 — 코어만으로 동작합니다.
|
||||
|
||||
**이 확장에 의존하는 확장** (이 확장을 비활성화하면 함께 영향을 받습니다)
|
||||
|
||||
없음.
|
||||
<!-- @generated:integrations END -->
|
||||
|
||||
## 문서
|
||||
|
||||
<!-- @generated:docs-index START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 문서 | 내용 | 상태 |
|
||||
|---|---|---|
|
||||
| [docs/README.md](docs/README.md) | 문서 통합 목차와 실측 집계 | ✅ |
|
||||
| [docs/architecture.md](docs/architecture.md) | 설계 의도·계층 지도·디렉토리 맵 | ✅ |
|
||||
| [docs/components.md](docs/components.md) | 템플릿이 제공하는 컴포넌트 | ✅ |
|
||||
| [docs/layouts.md](docs/layouts.md) | 레이아웃 목록과 라우트 매핑 | ✅ |
|
||||
| [docs/handlers.md](docs/handlers.md) | 템플릿 전용 핸들러와 부트스트랩 | ✅ |
|
||||
| [CHANGELOG.md](CHANGELOG.md) | 변경 이력 | ✅ |
|
||||
<!-- @generated:docs-index END -->
|
||||
|
||||
## 트러블슈팅
|
||||
|
||||
<!-- @intent START -->
|
||||
| 증상 | 원인 | 조치 |
|
||||
|---|---|---|
|
||||
| 관리자 템플릿 목록에 이 템플릿이 없음 | 학습용이라 목록에서 제외됨 | 정상입니다. 명령줄로 설치·활성화합니다 |
|
||||
| 활성화했더니 일부 모듈 화면이 비어 보임 | 그 화면이 이 템플릿에 없는 부품을 사용 | 정상입니다. 모듈이 필수 부품만 쓰도록 고치거나 원래 템플릿으로 되돌립니다 |
|
||||
| 화면이 아무것도 안 그려짐 | 빌드 결과물이 없거나 오래됨 | 템플릿을 다시 빌드하고 반영합니다 |
|
||||
| 오류가 났는데 아무 화면도 안 보임 | 해당 오류 화면이 없음 | 오류 화면 6종이 모두 있는지 확인합니다 |
|
||||
| 복제해서 만든 템플릿이 목록에 안 보임 | 복제본에 학습용 표시가 남아 있음 | 복제본의 `hidden` 표시를 지웁니다 |
|
||||
<!-- @intent END -->
|
||||
|
||||
## 변경 이력
|
||||
|
||||
[CHANGELOG.md](CHANGELOG.md)
|
||||
|
||||
## 라이선스
|
||||
|
||||
MIT
|
||||
@@ -0,0 +1,20 @@
|
||||
# Hello Admin Template 개발자 문서
|
||||
|
||||
> templates/_bundled/gnuboard7-hello_admin_template · 템플릿
|
||||
|
||||
<!-- @generated:stats START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
**훅 수**: 0 · **구독 훅 수**: 0 · **라우트 수**: 1 · **모델 수**: 0 · **테이블 수**: 0 · **마이그레이션 수**: 0 · **레이아웃 수**: 8 · **핸들러 수**: 0
|
||||
<!-- @generated:stats END -->
|
||||
|
||||
## 문서 목차
|
||||
|
||||
<!-- @generated:doc-toc START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 문서 | 내용 |
|
||||
|---|---|
|
||||
| [architecture.md](architecture.md) | 설계 의도·계층 지도·디렉토리 맵 |
|
||||
| [components.md](components.md) | 템플릿이 제공하는 컴포넌트 |
|
||||
| [layouts.md](layouts.md) | 레이아웃 목록과 라우트 매핑 |
|
||||
| [handlers.md](handlers.md) | 템플릿 전용 핸들러와 부트스트랩 |
|
||||
| [../AGENTS.md](../AGENTS.md) | 에이전트·확장개발자 진입점 |
|
||||
| [../README.md](../README.md) | 사람(도입검토자·운영자) 진입점 |
|
||||
<!-- @generated:doc-toc END -->
|
||||
@@ -0,0 +1,73 @@
|
||||
# Hello Admin Template — 아키텍처
|
||||
|
||||
> 설계 의도와 계층 구조 · 진입점: [AGENTS.md](../AGENTS.md)
|
||||
|
||||
## 설계 의도
|
||||
|
||||
<!-- @intent START -->
|
||||
"관리자 템플릿이 성립하기 위한 **최소 구성**은 무엇인가" 에 답하는 것이 목적입니다. 그래서
|
||||
담긴 것이 넷뿐입니다 — Basic 컴포넌트 8개, 베이스 레이아웃, 화면 하나, **오류 레이아웃 6종**.
|
||||
|
||||
**오류 6종이 왜 최소 구성에 들어가는가**가 이 샘플의 핵심입니다. 코어는 오류 상황에서 활성
|
||||
템플릿의 해당 레이아웃을 부르는데, 없으면 그 오류를 화면에 표시할 수단이 없습니다 — 사용자에게는
|
||||
백지가 됩니다. 화면이 하나뿐인 템플릿에도 6종은 있어야 하며, 그래서 "선택" 이 아니라 "구성" 입니다.
|
||||
|
||||
**컴포넌트를 8개로 제한한 것도 의도**입니다. 이 템플릿으로 바꿨을 때 깨지는 모듈 화면이 있다면,
|
||||
그 화면이 템플릿 고유 컴포넌트에 의존하고 있다는 뜻입니다 — 모듈 호환성을 확인하는 실험 도구로
|
||||
쓸 수 있습니다.
|
||||
|
||||
`manifest.hidden = true` 는 학습용이 운영 사이트의 템플릿 목록에 섞이지 않게 하면서도 CLI 로는
|
||||
실제로 설치·동작하게 하는 장치입니다.
|
||||
|
||||
**의도적으로 하지 않는 것**: 실제 관리 화면 · composite/layout 컴포넌트 · 액션 핸들러 ·
|
||||
SEO 설정 · 확장 오버라이드. `sirsoft-admin_basic` 과 나란히 보면 무엇이 필수이고 무엇이 그
|
||||
템플릿의 선택인지가 드러납니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 계층 지도
|
||||
|
||||
<!-- @intent START -->
|
||||
```
|
||||
template.json manifest — type: admin · 컴포넌트 레지스트리 · error_config
|
||||
routes.json `*/admin` → admin_dashboard
|
||||
│
|
||||
layouts/_admin_base.json 관리자 공통 뼈대 (헤더 · 사이드바 · 콘텐츠 슬롯)
|
||||
│ extends
|
||||
├─ layouts/admin_dashboard.json
|
||||
└─ layouts/errors/{401,403,404,500,503,maintenance}.json ← 6종 필수
|
||||
│
|
||||
src/components/basic/ Div · Button · H1 · H2 · H3 · A · Span · Img
|
||||
│
|
||||
src/index.ts initTemplate() — ComponentRegistry 에 8개 등록 (재시도 루프)
|
||||
│
|
||||
dist/ 커밋되는 빌드 산출물
|
||||
__tests__/layouts/ createLayoutTest() 기반 렌더링 테스트
|
||||
```
|
||||
|
||||
**서버 코드가 없습니다.** 템플릿은 화면만 담당하며 데이터는 코어·모듈의 공개 API 에서
|
||||
옵니다 — 그래서 템플릿을 갈아 끼워도 데이터는 그대로입니다.
|
||||
|
||||
`src/index.ts` 의 **재시도 루프**가 눈여겨볼 부분입니다. `initTemplate()` 이 모듈 로드 시
|
||||
스스로 실행되지만, 그 시점에 코어 `ComponentRegistry` 가 아직 없을 수 있어 100ms 간격으로 최대
|
||||
50회 다시 시도합니다(`window.load` 이후 시작). 재시도가 없으면 로드 순서에 따라 등록이
|
||||
건너뛰어지고, 레이아웃이 참조하는 컴포넌트 이름을 찾지 못해 화면이 조용히 빕니다.
|
||||
|
||||
레이아웃 렌더링 테스트는 **이 템플릿 디렉토리에 둡니다**(`__tests__/layouts/`). 코어
|
||||
디렉토리에 두면 그 템플릿을 지울 때 테스트만 남습니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 디렉토리
|
||||
|
||||
<!-- @generated:directory-map START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 경로 | 역할 | 수정 시 필요한 절차 |
|
||||
|---|---|---|
|
||||
| `template.json` | manifest (버전 SSoT) | version 변경 시 package.json·package-lock.json 동기화 |
|
||||
| `routes.json` | 라우트 → 레이아웃 매핑 | `php artisan template:update gnuboard7-hello_admin_template --force` |
|
||||
| `layouts/` | 레이아웃 JSON | `php artisan template:update gnuboard7-hello_admin_template --force` (빌드 불필요) |
|
||||
| `src/components/` | React 컴포넌트 | `php artisan template:build` → `php artisan template:update gnuboard7-hello_admin_template --force` |
|
||||
| `dist/` | 커밋되는 빌드 산출물 | `--production` 으로 재빌드 (sourceMappingURL 잔존 금지) |
|
||||
| `CHANGELOG.md` | 변경 이력 | 버전 상향 시 항목 추가 (미기재 시 버전 상향 불가) |
|
||||
| `components.json` | 편집기 컴포넌트 선언 (레이아웃 저작자가 읽는 props 계약) | `php artisan template:update gnuboard7-hello_admin_template --force` |
|
||||
| `docs/` | 개발자 문서 | 표면 변경 시 `php artisan ext:docgen` 재실행 |
|
||||
| `lang/` | 다국어 | 키 추가 시 ko·en 동시 반영 + 번들 ja 팩 동기화 |
|
||||
<!-- @generated:directory-map END -->
|
||||
@@ -0,0 +1,31 @@
|
||||
# Hello Admin Template — 컴포넌트
|
||||
|
||||
> 템플릿이 제공하는 컴포넌트 · 진입점: [AGENTS.md](../AGENTS.md)
|
||||
|
||||
## 제공 컴포넌트
|
||||
|
||||
<!-- @generated:components START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
컴포넌트 8개 (루트: `src/components`).
|
||||
|
||||
| 분류 | 개수 |
|
||||
|---|---|
|
||||
| `basic` | 8개 |
|
||||
<!-- @generated:components END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
Basic 8개뿐이며 composite·layout 은 없습니다 — HTML 태그를 그대로 래핑한 최소 단위
|
||||
(`Div`→`<div>`)만으로 관리자 화면 골격을 그릴 수 있음을 보이는 구성입니다.
|
||||
|
||||
**이 목록이 곧 모듈과의 계약**입니다. 모듈이 관리자 화면 조각에서 이 템플릿에 없는 컴포넌트
|
||||
(`DataGrid` · `MultilingualInput` 등)를 쓰면 그 조각은 여기서 **렌더되지 않습니다.** 모듈이
|
||||
어느 Admin 템플릿에서든 동작하려면 코어가 정한 필수 컴포넌트 집합만 써야 하며, 그 목록의
|
||||
SSoT 는 코어 `config/template.php` 의 `required_admin_components` 입니다.
|
||||
|
||||
이 템플릿으로 바꿔 보면 그 규칙을 지키지 않은 화면이 드러납니다 — **모듈 호환성 확인용
|
||||
실험 도구**로 쓸 수 있습니다.
|
||||
|
||||
컴포넌트를 추가할 때는 소스(`src/components/{분류}/`) · `template.json` 의 레지스트리 ·
|
||||
`components.json` 을 함께 갱신합니다. 하나라도 빠지면 레이아웃이 그 이름을 찾지 못해 그
|
||||
컴포넌트만 조용히 렌더되지 않습니다. TSX 를 고쳤으면 `template:build --production` 후
|
||||
`dist/` 를 함께 커밋합니다.
|
||||
<!-- @intent END -->
|
||||
@@ -0,0 +1,50 @@
|
||||
# Hello Admin Template — 핸들러
|
||||
|
||||
> 템플릿 전용 핸들러와 부트스트랩 · 진입점: [AGENTS.md](../AGENTS.md)
|
||||
|
||||
## 템플릿 전용 핸들러
|
||||
|
||||
<!-- @generated:handlers START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
_등록하는 액션 핸들러가 없습니다._
|
||||
<!-- @generated:handlers END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
없습니다. 이 샘플의 화면은 코어 엔진의 기본 핸들러(`apiCall` · `navigate` · `setState` 등)
|
||||
만으로 그려집니다.
|
||||
|
||||
템플릿 전용 핸들러가 필요해지는 경우는 **그 템플릿의 화면 구조에만 있는 동작**을 다룰 때입니다 —
|
||||
테마 전환, 장바구니 선택 상태, 브라우저 저장소 관리 같은 것들이며,
|
||||
`sirsoft-basic` 이 32개를 갖는 것이 그 예입니다.
|
||||
|
||||
핸들러를 도입할 때는 `initTemplate()` 안에서 `ActionDispatcher` 에 등록합니다. 등록도
|
||||
컴포넌트 등록과 같은 재시도 루프 안에 두어야 합니다 — 그 시점에 디스패처가 아직 없을 수
|
||||
있습니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 부트스트랩
|
||||
|
||||
<!-- @generated:frontend-entry START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 항목 | 값 |
|
||||
|---|---|
|
||||
| 엔트리 파일 | `src/index.ts` |
|
||||
| 전역 객체 | **미노출** |
|
||||
| 재등록 진입점 | `initTemplate()` |
|
||||
|
||||
재등록 진입점이 전역에 고정 이름으로 노출되지 않으면 로케일 전환 후 이 확장의 액션이 전부 무반응이 됩니다 (오류·토스트 없음).
|
||||
<!-- @generated:frontend-entry END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
전역 객체가 **미노출**입니다. 모듈·플러그인은 `window.__[Name].initModule/initPlugin` 을 고정
|
||||
이름으로 노출해야 하지만(로케일 전환 후 코어가 그것을 다시 부릅니다), 템플릿은 코어가 부트스트랩
|
||||
경로를 직접 알고 있어 전역 노출이 필요하지 않습니다.
|
||||
|
||||
`initTemplate()` 이 진입점이며 모듈 로드 시 스스로 실행됩니다. 하는 일은 하나 — 코어
|
||||
`ComponentRegistry` 에 Basic 8개를 등록하는 것입니다.
|
||||
|
||||
**재시도 루프가 이 함수의 핵심**입니다. 실행 시점에 레지스트리가 아직 없을 수 있어 100ms 간격으로
|
||||
최대 50회 다시 시도하며, `window.load` 이후에 시작합니다. 재시도가 없으면 로드 순서에 따라
|
||||
등록이 건너뛰어지고, 레이아웃이 참조하는 컴포넌트 이름을 찾지 못해 **화면이 조용히 빕니다** —
|
||||
오류도 경고도 남지 않습니다.
|
||||
|
||||
최대 재시도를 넘기면 `logger.error` 로 사실을 남깁니다. 조용히 포기하지 않는 것이 규약입니다.
|
||||
<!-- @intent END -->
|
||||
@@ -0,0 +1,84 @@
|
||||
# Hello Admin Template — 레이아웃
|
||||
|
||||
> 레이아웃 목록과 라우트 매핑 · 진입점: [AGENTS.md](../AGENTS.md)
|
||||
|
||||
## 레이아웃 목록
|
||||
|
||||
<!-- @generated:layouts START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
레이아웃 8개 (루트: `layouts`).
|
||||
|
||||
| 그룹 | 개수 |
|
||||
|---|---|
|
||||
| `(root)` | 2개 |
|
||||
| `errors` | 6개 |
|
||||
|
||||
| 레이아웃 | 그룹 | 종류 | extends |
|
||||
|---|---|---|---|
|
||||
| `_admin_base` | `(root)` | partial | - |
|
||||
| `admin_dashboard` | `(root)` | 화면 | `_admin_base` |
|
||||
| `401` | `errors` | 화면 | `_admin_base` |
|
||||
| `403` | `errors` | 화면 | `_admin_base` |
|
||||
| `404` | `errors` | 화면 | `_admin_base` |
|
||||
| `500` | `errors` | 화면 | `_admin_base` |
|
||||
| `503` | `errors` | 화면 | `_admin_base` |
|
||||
| `maintenance` | `errors` | 화면 | `_admin_base` |
|
||||
<!-- @generated:layouts END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
8개 중 **6개가 오류 레이아웃**입니다. 이 비율이 이 샘플이 말하려는 것 그 자체입니다 — 오류
|
||||
레이아웃 6종(401 · 403 · 404 · 500 · 503 · maintenance)은 선택이 아니라 **최소 구성**입니다.
|
||||
|
||||
코어는 오류 상황에서 활성 템플릿의 해당 레이아웃을 부릅니다. 없으면 그 오류를 표시할 수단이
|
||||
없어 사용자에게는 백지가 되므로, 화면이 하나뿐인 템플릿에도 6종은 있어야 합니다.
|
||||
|
||||
여섯 모두 `_admin_base` 를 상속합니다. 오류 화면에서도 관리자 골격이 유지되고, 뼈대를 한 번
|
||||
고치면 전 화면에 반영됩니다. **`extends` 없는 독립 레이아웃을 만들면** 그 화면에서는
|
||||
`toast`·`openModal` 이 성공으로 기록되지만 화면에는 아무것도 나타나지 않습니다(호스트
|
||||
컴포넌트가 마운트되지 않아서).
|
||||
|
||||
`401` 레이아웃에서 **로그인 리다이렉트를 직접 구현하지 않습니다** — 코어
|
||||
`TemplateApp.showRouteError` 가드가 처리하므로, 여기서 다시 이동시키면 이중 리다이렉트가 되고
|
||||
돌아올 위치도 어긋납니다.
|
||||
|
||||
레이아웃 JSON 만 고쳤다면 빌드 없이
|
||||
`php artisan template:update gnuboard7-hello_admin_template --force` 로 반영합니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 라우트 매핑
|
||||
|
||||
<!-- @generated:layout-map START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 경로 | 레이아웃 | 이름 |
|
||||
|---|---|---|
|
||||
| `*/admin` | `admin_dashboard` | - |
|
||||
<!-- @generated:layout-map END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
하나뿐입니다 — `*/admin` → `admin_dashboard`.
|
||||
|
||||
앞의 `*` 는 로케일 접두 등 가변 구간을 받는 자리입니다. Admin 템플릿의 라우트는 이 형태로
|
||||
`*/admin/...` 네임스페이스 안에 있어야 하며, 그 밖의 경로를 선언하면 모듈·User 템플릿이 소유한
|
||||
경로와 다투게 되고 어느 쪽이 이기는지가 설치 순서에 좌우됩니다.
|
||||
|
||||
오류 레이아웃에는 라우트가 없습니다 — 코어가 상황을 보고 직접 부르므로 경로로 도달하는 화면이
|
||||
아닙니다.
|
||||
|
||||
라우트를 바꾼 뒤에는 `template:update --force` 로 반영합니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 확장 오버라이드
|
||||
|
||||
<!-- @generated:template-overrides START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
_오버라이드하는 레이아웃 확장 조각이 없습니다._
|
||||
<!-- @generated:template-overrides END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
없습니다. 이 샘플은 다른 확장이 제공한 조각을 대체하지 않습니다.
|
||||
|
||||
오버라이드는 `extensions/{확장}/` 에 그 확장의 조각과 같은 이름의 파일을 두면 성립합니다 —
|
||||
플러그인이 제공한 조각의 디자인이 이 템플릿과 어긋날 때 **조각을 고치는 대신 템플릿이 자기
|
||||
버전을 얹는** 방향입니다.
|
||||
|
||||
그 대가로 원본이 바뀌어도 사본은 따라가지 않습니다. 오버라이드를 두었다면 그 확장을 업그레이드한
|
||||
뒤 동작을 확인해야 합니다 — 원본이 핸들러 이름이나 필드 계약을 바꾸면 오버라이드만 옛 계약을
|
||||
붙들고 있게 되고, 증상은 "그 자리만 무반응" 으로 나타납니다.
|
||||
<!-- @intent END -->
|
||||
@@ -0,0 +1,167 @@
|
||||
# Hello 사용자 템플릿 — 에이전트 가이드
|
||||
|
||||
> 이 문서는 이 템플릿을 수정하는 에이전트·확장개발자를 위한 것입니다. 도입 검토·운영 관점은 [README.md](README.md) 를 보세요.
|
||||
|
||||
## TL;DR (5초 요약)
|
||||
|
||||
```text
|
||||
1. 유형: 템플릿 (gnuboard7-hello_user_template, type=user) — 학습용 최소 User 템플릿. Basic 컴포넌트 8 + 베이스 1 + 홈 1 + **오류 6종**. 홈이 학습용 모듈 API 를 `data_sources` 로 연동한다. `hidden: true`
|
||||
2. 확장 방식: 훅 없음 — 확장점은 화면 구조와 데이터소스 선언이다. 제공 컴포넌트 목록이 곧 계약
|
||||
3. 건드리면 안 되는 것: 오류 레이아웃 6종 축소, 401 에서 직접 리다이렉트, 컴포넌트 등록을 한 번만 시도(재시도 필요), 샘플에 실제 사이트 화면 추가
|
||||
4. 작업 위치: `templates/_bundled/gnuboard7-hello_user_template` — 활성 디렉토리 직접 수정 금지
|
||||
5. 반영: `php artisan template:update gnuboard7-hello_user_template --force`
|
||||
```
|
||||
|
||||
## 1. 이 확장은 무엇인가
|
||||
|
||||
<!-- @intent START -->
|
||||
**학습용 최소 User 템플릿**입니다. 방문자 화면 템플릿이 성립하기 위한 최소 구성과, **모듈의
|
||||
공개 API 를 화면에 연결하는 법**을 보이는 것이 목적입니다.
|
||||
|
||||
담긴 것은 넷입니다 — Basic 컴포넌트 8개, 베이스 레이아웃 `_user_base`, 홈 화면 하나,
|
||||
**오류 레이아웃 6종**(401 · 403 · 404 · 500 · 503 · maintenance).
|
||||
|
||||
**홈 화면이 이 샘플의 핵심**입니다. `data_sources` 로 학습용 모듈의 메모 API
|
||||
(`/api/modules/gnuboard7-hello_module/memos`)를 호출해 목록을 그립니다 — 모듈이 데이터를,
|
||||
템플릿이 화면을 담당하는 G7 의 기본 경계를 가장 짧게 보여주는 예시입니다. 실제 게시판·이커머스
|
||||
모듈도 방문자 화면을 갖지 않고 같은 방식으로 템플릿에 맡깁니다.
|
||||
|
||||
`manifest.hidden = true` 라 관리자 UI 의 템플릿 목록에 나타나지 않습니다. artisan CLI 로는
|
||||
정상 설치·활성화됩니다.
|
||||
|
||||
**의도적으로 하지 않는 것**: 실제 사이트 화면 · composite/layout 컴포넌트 · 액션 핸들러 ·
|
||||
SEO 설정 · 확장 오버라이드. `sirsoft-basic` 이 컴포넌트 79개와 레이아웃 166개를 갖는 것과
|
||||
나란히 보면 무엇이 필수이고 무엇이 그 템플릿의 선택인지 드러납니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 2. 디렉토리 지도
|
||||
|
||||
<!-- @generated:directory-map START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 경로 | 역할 | 수정 시 필요한 절차 |
|
||||
|---|---|---|
|
||||
| `template.json` | manifest (버전 SSoT) | version 변경 시 package.json·package-lock.json 동기화 |
|
||||
| `routes.json` | 라우트 → 레이아웃 매핑 | `php artisan template:update gnuboard7-hello_user_template --force` |
|
||||
| `layouts/` | 레이아웃 JSON | `php artisan template:update gnuboard7-hello_user_template --force` (빌드 불필요) |
|
||||
| `src/components/` | React 컴포넌트 | `php artisan template:build` → `php artisan template:update gnuboard7-hello_user_template --force` |
|
||||
| `dist/` | 커밋되는 빌드 산출물 | `--production` 으로 재빌드 (sourceMappingURL 잔존 금지) |
|
||||
| `CHANGELOG.md` | 변경 이력 | 버전 상향 시 항목 추가 (미기재 시 버전 상향 불가) |
|
||||
| `components.json` | 편집기 컴포넌트 선언 (레이아웃 저작자가 읽는 props 계약) | `php artisan template:update gnuboard7-hello_user_template --force` |
|
||||
| `docs/` | 개발자 문서 | 표면 변경 시 `php artisan ext:docgen` 재실행 |
|
||||
| `lang/` | 다국어 | 키 추가 시 ko·en 동시 반영 + 번들 ja 팩 동기화 |
|
||||
<!-- @generated:directory-map END -->
|
||||
|
||||
## 3. 핵심 흐름
|
||||
|
||||
<!-- @intent START -->
|
||||
서버 코드가 없으므로 흐름은 **컴포넌트 등록 → 라우트 → 레이아웃 → 데이터소스** 입니다.
|
||||
|
||||
**컴포넌트 등록**: `src/index.ts` 가 모듈 로드 시점에 코어 `ComponentRegistry` 를 찾아 Basic
|
||||
8개를 등록합니다. 레지스트리가 없으면 경고를 남기고 건너뜁니다.
|
||||
|
||||
> 이 지점이 Admin 샘플과 다릅니다. `gnuboard7-hello_admin_template` 은 `initTemplate()`
|
||||
> 안에서 **100ms 간격 최대 50회 재시도**로 등록하지만, 이 템플릿은 모듈 로드 시점에 한 번만
|
||||
> 시도합니다. 로드 순서에 따라 그 시점에 레지스트리가 아직 없으면 등록이 건너뛰어지고, 그러면
|
||||
> 레이아웃이 참조하는 컴포넌트 이름을 찾지 못해 화면이 빕니다 — 콘솔 경고 외에는 흔적이 없습니다.
|
||||
> 새 템플릿을 만들 때는 **Admin 샘플의 재시도 형태를 따르는 것**이 안전합니다.
|
||||
|
||||
**화면 렌더**: `routes.json` 이 `/` 를 `home` 에 대응 → 그 레이아웃이 `_user_base` 를 상속해
|
||||
헤더·푸터를 얻고 콘텐츠를 채웁니다 → `data_sources` 의 `memos` 가 학습용 모듈의 API 를
|
||||
`auto_fetch` 로 호출(`loading_strategy: progressive`) → 응답이 목록 컴포넌트에 바인딩됩니다.
|
||||
|
||||
**오류 렌더**: 코어가 401/403/404/500/503/maintenance 상황을 만나면 활성 템플릿의 해당
|
||||
레이아웃을 부릅니다. 여섯 모두 `_user_base` 를 상속하므로 오류 화면에서도 공통 뼈대가
|
||||
유지됩니다. `401` 에서 **로그인 리다이렉트를 직접 구현하지 않습니다** — 코어
|
||||
`TemplateApp.showRouteError` 가드가 처리합니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 4. 확장점
|
||||
|
||||
<!-- @generated:extension-points-summary START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 확장점 | 수 | 상세 |
|
||||
|---|---|---|
|
||||
| 제공 컴포넌트 | 8개 | [제공 컴포넌트](docs/components.md#제공-컴포넌트) |
|
||||
| 레이아웃 | 8개 | [레이아웃 목록](docs/layouts.md#레이아웃-목록) |
|
||||
| 전용 핸들러 | 0개 | [템플릿 전용 핸들러](docs/handlers.md#템플릿-전용-핸들러) |
|
||||
| 확장 오버라이드 | 0개 | [확장 오버라이드](docs/layouts.md#확장-오버라이드) |
|
||||
<!-- @generated:extension-points-summary END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
템플릿의 확장점은 훅이 아니라 **화면 구조**입니다.
|
||||
|
||||
| 확장점 | 이 샘플에서 |
|
||||
|---|---|
|
||||
| 제공 컴포넌트 | Basic 8개. 다른 확장이 조각을 끼워 넣을 때 **여기 있는 것만** 쓸 수 있습니다 |
|
||||
| 레이아웃 | 8개(베이스 1 + 홈 1 + 오류 6). 모듈이 `layout_extensions` 로 조각을 끼울 자리는 홈뿐입니다 |
|
||||
| 확장 오버라이드 | 없음. `extensions/{확장}/` 에 같은 이름의 조각을 두면 그 확장이 제공한 원본을 대체합니다 |
|
||||
| 전용 핸들러 | 없음 |
|
||||
|
||||
**데이터소스가 이 템플릿의 실질적인 확장점**입니다. 홈 화면이 학습용 모듈의 API 를 호출하듯,
|
||||
User 템플릿은 자기가 그리고 싶은 화면에 필요한 API 를 `data_sources` 로 선언해 가져옵니다.
|
||||
모듈을 바꾸거나 늘려도 템플릿의 구조는 그대로이고 선언만 바뀝니다.
|
||||
|
||||
그 대가로 **모듈의 응답 형태가 바뀌면 화면이 조용히 빕니다.** 의존한 모듈을 업그레이드한 뒤에는
|
||||
그 화면이 여전히 데이터를 그리는지 확인해야 합니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 5. 수정 시 동반 의무
|
||||
|
||||
- [ ] `_bundled` 에서만 수정하고 `php artisan template:update gnuboard7-hello_user_template --force` 로 반영
|
||||
- [ ] manifest version 상향 시 `package.json` · `package-lock.json` 동기화 + CHANGELOG 기재
|
||||
- [ ] 레이아웃 JSON 변경 시 빌드 없이 update 만 — 신규 Tailwind 클래스는 빌드된 CSS 에 존재하는지 확인
|
||||
- [ ] TSX/TS 변경 시 `--production` 재빌드 후 `dist/` 커밋 (sourceMappingURL 잔존 금지)
|
||||
- [ ] 다국어 키 추가 시 ko·en 동시 반영 + 번들 ja 언어팩 증분 동기화
|
||||
- [ ] 오류 레이아웃 6종(401·403·404·500·503·maintenance)이 모두 있는지 확인 — 하나라도 없으면 그 상황에서 백지가 된다
|
||||
- [ ] 컴포넌트 등록 경로를 손댔다면 재시도 여부를 확인 — 한 번만 시도하면 로드 순서에 따라 화면이 조용히 빈다
|
||||
- [ ] 의존 모듈(`gnuboard7-hello_module`)의 API 응답 형태가 바뀌면 홈 화면이 조용히 빈다 — 그 모듈을 올릴 때 함께 확인
|
||||
- [ ] 컴포넌트를 추가·삭제했다면 소스 · `template.json` 레지스트리 · `components.json` 을 함께 갱신
|
||||
- [ ] `manifest.hidden = true` 를 유지 (복제본에서만 제거)
|
||||
- [ ] TSX 를 고쳤다면 `template:build --production` 후 `dist/` 동반 커밋 (`sourceMappingURL` 잔존 금지)
|
||||
- [ ] 레이아웃 렌더링 테스트(`__tests__/layouts/*.test.tsx`)는 이 템플릿 디렉토리에 둔다
|
||||
|
||||
## 6. 금지 패턴
|
||||
|
||||
<!-- @intent START -->
|
||||
| 금지 | 올바른 사용 | 이유 |
|
||||
|---|---|---|
|
||||
| 오류 레이아웃 6종 중 일부를 빼기 | 401 · 403 · 404 · 500 · 503 · maintenance 전부 유지 | 코어가 그 상황에서 활성 템플릿의 레이아웃을 부른다 — 없으면 방문자에게 백지가 된다 |
|
||||
| `401` 레이아웃에서 로그인으로 직접 리다이렉트 | 코어 `TemplateApp.showRouteError` 가드에 위임 | 이중 리다이렉트가 되고, 돌아올 위치를 코어가 이미 관리한다 |
|
||||
| 컴포넌트 등록을 한 번만 시도 | Admin 샘플처럼 재시도 루프를 둔다 | 로드 순서에 따라 레지스트리가 아직 없을 수 있다 — 등록에 실패하면 화면이 조용히 빈다 |
|
||||
| `extends` 없는 독립 레이아웃에서 `toast`·`openModal` 사용 | `_user_base` 를 상속하거나 호스트 컴포넌트를 직접 마운트 | 호스트가 없으면 핸들러는 성공으로 기록되는데 화면에는 아무것도 나타나지 않는다 |
|
||||
| 모듈 API 응답 필드를 화면에서 그대로 가정하고 방어 없이 바인딩 | `{{값 ?? ''}}` 같은 폴백과 배열 경로 확인 | 응답 형태가 바뀌면 화면이 조용히 빈다 |
|
||||
| 이 샘플에 실제 사이트 화면을 더해 "쓸모 있게" 만들기 | 짧게 유지하고, 필요한 화면은 별도 템플릿으로 | 샘플의 가치는 "최소 구성이 무엇인가" 를 보이는 것이다 |
|
||||
| `manifest.hidden` 을 제거 | 그대로 둔다 (복제본에서만 제거) | 학습용 템플릿이 운영 사이트의 템플릿 목록에 섞인다 |
|
||||
| 컴포넌트를 추가하면서 `template.json` 의 레지스트리를 갱신하지 않기 | 소스·레지스트리·`components.json` 을 함께 | 레이아웃이 참조하는 이름을 찾지 못해 그 컴포넌트만 조용히 렌더되지 않는다 |
|
||||
| TSX 를 고치고 `dist/` 재빌드 없이 커밋 | `template:build --production` 후 `dist/` 동반 커밋 | 브라우저가 받는 것은 커밋된 `dist/` 다 — 소스 수정이 사문화된다 |
|
||||
<!-- @intent END -->
|
||||
|
||||
## 7. 테스트 실행
|
||||
|
||||
<!-- @generated:test-commands START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 종류 | 개수 | 위치 |
|
||||
|---|---|---|
|
||||
| PHPUnit | 0개 | — |
|
||||
| Vitest | 2개 | `vitest.config.ts` |
|
||||
| Playwright | 0개 | — |
|
||||
| 시나리오 매니페스트 | 0개 | — |
|
||||
|
||||
```bash
|
||||
# Vitest (확장 디렉토리에서) (PowerShell)
|
||||
cd templates/_bundled/gnuboard7-hello_user_template && powershell -Command "npm run test:run -- <대상>"
|
||||
|
||||
```
|
||||
|
||||
무필터 전체 실행은 금지되어 있습니다 — 변경 범위에 걸리는 대상만 지정해 실행합니다.
|
||||
<!-- @generated:test-commands END -->
|
||||
|
||||
## 8. 문서 목차
|
||||
|
||||
<!-- @generated:docs-index START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 문서 | 내용 | 상태 |
|
||||
|---|---|---|
|
||||
| [docs/README.md](docs/README.md) | 문서 통합 목차와 실측 집계 | ✅ |
|
||||
| [docs/architecture.md](docs/architecture.md) | 설계 의도·계층 지도·디렉토리 맵 | ✅ |
|
||||
| [docs/components.md](docs/components.md) | 템플릿이 제공하는 컴포넌트 | ✅ |
|
||||
| [docs/layouts.md](docs/layouts.md) | 레이아웃 목록과 라우트 매핑 | ✅ |
|
||||
| [docs/handlers.md](docs/handlers.md) | 템플릿 전용 핸들러와 부트스트랩 | ✅ |
|
||||
| [CHANGELOG.md](CHANGELOG.md) | 변경 이력 | ✅ |
|
||||
<!-- @generated:docs-index END -->
|
||||
@@ -9,6 +9,7 @@
|
||||
### Added
|
||||
|
||||
- 아이콘(Font Awesome)을 템플릿에 함께 담아 외부 CDN 없이 동작합니다.
|
||||
- 개발자와 AI 에이전트를 위한 문서를 추가했습니다. 확장 폴더의 `AGENTS.md`(설계 의도·확장점·수정 시 확인할 것)와 `README.md`(도입·운영 안내), `docs/`(상세 문서)로 구성됩니다.
|
||||
|
||||
## [0.1.0] - 2026-07-01
|
||||
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
# Hello 사용자 템플릿
|
||||
|
||||
**G7 템플릿 · gnuboard7-hello_user_template**
|
||||
학습용 최소 샘플 사용자 템플릿 (Basic 8개 컴포넌트)
|
||||
|
||||
<!-- @generated:badges START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/version-0.1.1-0066FF?style=flat-square" alt="version 0.1.1">
|
||||
<img src="https://img.shields.io/badge/type-%ED%85%9C%ED%94%8C%EB%A6%BF-555555?style=flat-square" alt="type 템플릿">
|
||||
<img src="https://img.shields.io/badge/G7-%3E%3D7.0.10-1F883D?style=flat-square" alt="G7 >=7.0.10">
|
||||
<img src="https://img.shields.io/badge/license-MIT-8250DF?style=flat-square" alt="license MIT">
|
||||
<img src="https://img.shields.io/badge/requires-gnuboard7--hello__module-BF8700?style=flat-square" alt="requires gnuboard7-hello_module">
|
||||
</p>
|
||||
<!-- @generated:badges END -->
|
||||
|
||||
---
|
||||
|
||||
[소개](#소개) · [주요 기능](#주요-기능) · [동작 방식](#동작-방식) · [요구 사항](#요구-사항) · [설치](#설치) · [제공 컴포넌트](#제공-컴포넌트) · [사용 방법](#사용-방법) · [다른 확장과의 연동](#다른-확장과의-연동) · [문서](#문서) · [트러블슈팅](#트러블슈팅) · [변경 이력](#변경-이력) · [라이선스](#라이선스)
|
||||
|
||||
---
|
||||
|
||||
## 소개
|
||||
|
||||
<!-- @intent START -->
|
||||
그누보드7 **사용자 템플릿이 어떻게 생겼는지 보여주는 학습용 샘플**입니다. 실제 운영에 쓰기
|
||||
위한 것이 아니라, "방문자 화면 템플릿은 최소한 무엇을 갖춰야 하는가" 와 "모듈의 데이터를 화면에
|
||||
어떻게 연결하는가" 를 보이는 것이 목적입니다.
|
||||
|
||||
담긴 것은 화면 부품 8개, 공통 뼈대 하나, 홈 화면 한 장, 그리고 **오류 화면 6종**입니다.
|
||||
홈 화면은 학습용 모듈의 메모 목록을 불러와 보여줍니다 — **모듈은 데이터를, 템플릿은 화면을**
|
||||
담당하는 그누보드7 의 기본 구조를 가장 짧게 보여주는 예시입니다.
|
||||
|
||||
관리자 화면의 템플릿 목록에는 나타나지 않습니다(학습용이 운영 목록에 섞이지 않도록). 명령줄로
|
||||
설치·활성화할 수 있으며, 학습용 모듈이 함께 설치되어 있어야 홈 화면에 목록이 나옵니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 주요 기능
|
||||
|
||||
<!-- @intent START -->
|
||||
| 영역 | 설명 |
|
||||
|---|---|
|
||||
| 화면 부품 | 가장 기본적인 8개 (영역·버튼·제목 3종·링크·글자·이미지) |
|
||||
| 공통 뼈대 | 모든 화면이 물려받는 방문자 기본 골격 (헤더 + 콘텐츠 + 푸터) |
|
||||
| 홈 화면 | 학습용 모듈의 메모 목록을 불러와 표시 |
|
||||
| 오류 화면 | 401·403·404·500·503·점검 중 6종 |
|
||||
| 다국어 | 한국어·영어 공통 문구 |
|
||||
| 테스트 | 데이터를 모의로 넣고 화면이 그려지는지 확인하는 예시 |
|
||||
<!-- @intent END -->
|
||||
|
||||
## 동작 방식
|
||||
|
||||
<!-- @intent START -->
|
||||
```mermaid
|
||||
flowchart TD
|
||||
B[_user_base<br/>헤더 + 콘텐츠 + 푸터] --> H[home<br/>메모 목록]
|
||||
B --> E401[401 권한 없음]
|
||||
B --> E403[403 접근 거부]
|
||||
B --> E404[404 없는 페이지]
|
||||
B --> E500[500 서버 오류]
|
||||
B --> E503[503 이용 불가]
|
||||
B --> EM[maintenance 점검 중]
|
||||
```
|
||||
|
||||
모든 화면이 하나의 공통 뼈대를 물려받습니다. 오류 화면에서도 사이트 골격이 유지되고, 뼈대를
|
||||
한 번 고치면 전 화면에 반영됩니다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
V[방문자] --> H[홈 화면]
|
||||
H -->|목록 요청| M[학습용 모듈]
|
||||
M --> DB[(메모 데이터)]
|
||||
```
|
||||
|
||||
홈 화면은 자기 데이터를 갖지 않고 모듈에 요청해서 받아옵니다. 그래서 템플릿을 바꿔도 데이터는
|
||||
그대로 남고, 같은 데이터를 다른 디자인으로 보여줄 수 있습니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 요구 사항
|
||||
|
||||
<!-- @generated:requirements START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 항목 | 값 |
|
||||
|---|---|
|
||||
| G7 코어 | `>=7.0.10` |
|
||||
| PHP | `^8.2` |
|
||||
| 의존 모듈 | `gnuboard7-hello_module` `>=0.1.0` |
|
||||
<!-- @generated:requirements END -->
|
||||
|
||||
## 설치
|
||||
|
||||
<!-- @generated:install START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
```bash
|
||||
# 번들 설치 (코어에 동봉된 소스에서 설치)
|
||||
php artisan template:install gnuboard7-hello_user_template
|
||||
|
||||
# 활성화
|
||||
php artisan template:activate gnuboard7-hello_user_template
|
||||
|
||||
# 업데이트 (번들 소스 기준 강제 반영)
|
||||
php artisan template:update gnuboard7-hello_user_template --force
|
||||
```
|
||||
<!-- @generated:install END -->
|
||||
|
||||
## 제공 컴포넌트
|
||||
|
||||
<!-- @generated:settings-summary START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
컴포넌트 8개 (루트: `src/components`).
|
||||
|
||||
| 분류 | 개수 |
|
||||
|---|---|
|
||||
| `basic` | 8개 |
|
||||
<!-- @generated:settings-summary END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
방문자 화면을 그리는 데 쓰는 **부품 8개**입니다. 기본 사용자 템플릿이 79개를 갖는 것과
|
||||
비교하면 이 샘플이 얼마나 최소한만 담고 있는지 알 수 있습니다.
|
||||
|
||||
부품 목록은 **다른 확장과의 계약**이기도 합니다. 모듈이나 플러그인이 이 템플릿의 화면에 조각을
|
||||
끼워 넣을 때 여기 없는 부품을 쓰면 그 조각은 그려지지 않습니다.
|
||||
|
||||
전체 목록과 사용법은 [docs/components.md](docs/components.md) 에 있습니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 사용 방법
|
||||
|
||||
<!-- @intent START -->
|
||||
**설치해 보기**: 학습용 모듈을 먼저 설치한 뒤 이 템플릿을 설치합니다.
|
||||
|
||||
```bash
|
||||
php artisan module:install gnuboard7-hello_module
|
||||
php artisan module:activate gnuboard7-hello_module
|
||||
php artisan template:install gnuboard7-hello_user_template
|
||||
php artisan template:activate gnuboard7-hello_user_template
|
||||
```
|
||||
|
||||
활성화하면 사이트 첫 화면이 이 최소 템플릿으로 바뀌고, 관리자에서 등록한 메모가 홈 화면에
|
||||
목록으로 나옵니다. 원래 템플릿으로 되돌리려면 그 템플릿을 다시 활성화하면 됩니다.
|
||||
|
||||
**모듈 데이터를 화면에 연결하는 법 배우기**: 홈 화면 파일(`layouts/home.json`)의 데이터 요청
|
||||
선언을 보면, 어떤 주소에서 무엇을 받아 화면 어디에 넣는지가 한눈에 들어옵니다. 새 템플릿을
|
||||
만들 때 이 선언 형태를 그대로 따라 쓰면 됩니다.
|
||||
|
||||
**새 사용자 템플릿의 출발점으로 쓰기**: 이 디렉토리를 복제한 뒤 식별자를 바꾸고 학습용 표시를
|
||||
지우면 새 템플릿이 됩니다. 부품과 화면을 필요한 만큼 더해 나가면 됩니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 다른 확장과의 연동
|
||||
|
||||
<!-- @generated:integrations START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
**이 확장이 의존하는 확장**
|
||||
|
||||
| 확장 | 유형 | 버전 제약 | 번들 |
|
||||
|---|---|---|---|
|
||||
| `gnuboard7-hello_module` | 모듈 | `>=0.1.0` | ✅ |
|
||||
|
||||
**이 확장에 의존하는 확장** (이 확장을 비활성화하면 함께 영향을 받습니다)
|
||||
|
||||
없음.
|
||||
<!-- @generated:integrations END -->
|
||||
|
||||
## 문서
|
||||
|
||||
<!-- @generated:docs-index START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 문서 | 내용 | 상태 |
|
||||
|---|---|---|
|
||||
| [docs/README.md](docs/README.md) | 문서 통합 목차와 실측 집계 | ✅ |
|
||||
| [docs/architecture.md](docs/architecture.md) | 설계 의도·계층 지도·디렉토리 맵 | ✅ |
|
||||
| [docs/components.md](docs/components.md) | 템플릿이 제공하는 컴포넌트 | ✅ |
|
||||
| [docs/layouts.md](docs/layouts.md) | 레이아웃 목록과 라우트 매핑 | ✅ |
|
||||
| [docs/handlers.md](docs/handlers.md) | 템플릿 전용 핸들러와 부트스트랩 | ✅ |
|
||||
| [CHANGELOG.md](CHANGELOG.md) | 변경 이력 | ✅ |
|
||||
<!-- @generated:docs-index END -->
|
||||
|
||||
## 트러블슈팅
|
||||
|
||||
<!-- @intent START -->
|
||||
| 증상 | 원인 | 조치 |
|
||||
|---|---|---|
|
||||
| 관리자 템플릿 목록에 이 템플릿이 없음 | 학습용이라 목록에서 제외됨 | 정상입니다. 명령줄로 설치·활성화합니다 |
|
||||
| 홈 화면에 메모 목록이 비어 있음 | 학습용 모듈이 설치·활성화되지 않았거나 메모가 없음 | 모듈을 활성화하고 관리자에서 메모를 몇 건 등록합니다 |
|
||||
| 화면이 아무것도 안 그려짐 | 빌드 결과물이 없거나 오래됨 | 템플릿을 다시 빌드하고 반영합니다 |
|
||||
| 오류가 났는데 아무 화면도 안 보임 | 해당 오류 화면이 없음 | 오류 화면 6종이 모두 있는지 확인합니다 |
|
||||
| 복제해서 만든 템플릿이 목록에 안 보임 | 복제본에 학습용 표시가 남아 있음 | 복제본의 `hidden` 표시를 지웁니다 |
|
||||
| 새로 고칠 때마다 화면이 나오다 안 나오다 함 | 화면 부품 등록이 로드 순서를 탐 | 부품 등록에 재시도를 두는 형태(관리자 샘플 참고)로 고칩니다 |
|
||||
<!-- @intent END -->
|
||||
|
||||
## 변경 이력
|
||||
|
||||
[CHANGELOG.md](CHANGELOG.md)
|
||||
|
||||
## 라이선스
|
||||
|
||||
MIT
|
||||
@@ -0,0 +1,20 @@
|
||||
# Hello 사용자 템플릿 개발자 문서
|
||||
|
||||
> templates/_bundled/gnuboard7-hello_user_template · 템플릿
|
||||
|
||||
<!-- @generated:stats START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
**훅 수**: 0 · **구독 훅 수**: 0 · **라우트 수**: 1 · **모델 수**: 0 · **테이블 수**: 0 · **마이그레이션 수**: 0 · **레이아웃 수**: 8 · **핸들러 수**: 0
|
||||
<!-- @generated:stats END -->
|
||||
|
||||
## 문서 목차
|
||||
|
||||
<!-- @generated:doc-toc START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 문서 | 내용 |
|
||||
|---|---|
|
||||
| [architecture.md](architecture.md) | 설계 의도·계층 지도·디렉토리 맵 |
|
||||
| [components.md](components.md) | 템플릿이 제공하는 컴포넌트 |
|
||||
| [layouts.md](layouts.md) | 레이아웃 목록과 라우트 매핑 |
|
||||
| [handlers.md](handlers.md) | 템플릿 전용 핸들러와 부트스트랩 |
|
||||
| [../AGENTS.md](../AGENTS.md) | 에이전트·확장개발자 진입점 |
|
||||
| [../README.md](../README.md) | 사람(도입검토자·운영자) 진입점 |
|
||||
<!-- @generated:doc-toc END -->
|
||||
@@ -0,0 +1,76 @@
|
||||
# Hello 사용자 템플릿 — 아키텍처
|
||||
|
||||
> 설계 의도와 계층 구조 · 진입점: [AGENTS.md](../AGENTS.md)
|
||||
|
||||
## 설계 의도
|
||||
|
||||
<!-- @intent START -->
|
||||
목표가 둘입니다 — "User 템플릿의 **최소 구성**은 무엇인가" 와 "모듈의 데이터를 화면에 **어떻게
|
||||
연결하는가**".
|
||||
|
||||
앞의 답은 Admin 샘플과 같습니다: Basic 컴포넌트 8개 · 베이스 레이아웃 · 화면 하나 ·
|
||||
**오류 레이아웃 6종**. 오류 6종이 선택이 아닌 이유도 같습니다 — 코어가 그 상황에서 활성
|
||||
템플릿의 레이아웃을 부르는데 없으면 방문자에게 백지가 됩니다.
|
||||
|
||||
뒤의 답이 이 샘플만의 것입니다. 홈 화면이 `data_sources` 로 학습용 모듈의 메모 API 를 호출해
|
||||
목록을 그립니다 — **모듈이 데이터를, 템플릿이 화면을** 담당하는 경계를 가장 짧게 보여주는
|
||||
예시이며, 실제 게시판·이커머스 모듈도 방문자 화면을 갖지 않고 같은 방식으로 템플릿에
|
||||
맡깁니다.
|
||||
|
||||
**의도적으로 하지 않는 것**: 실제 사이트 화면 · composite/layout 컴포넌트 · 액션 핸들러 ·
|
||||
SEO 설정 · 확장 오버라이드. `sirsoft-basic` 과 나란히 보면 무엇이 필수이고 무엇이 그 템플릿의
|
||||
선택인지가 드러납니다.
|
||||
|
||||
`manifest.hidden = true` 는 학습용이 운영 사이트의 템플릿 목록에 섞이지 않게 하면서도 CLI 로는
|
||||
실제로 설치·동작하게 하는 장치입니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 계층 지도
|
||||
|
||||
<!-- @intent START -->
|
||||
```
|
||||
template.json manifest — type: user · features 플래그 · 컴포넌트 레지스트리
|
||||
routes.json `/` → home (auth_required: false)
|
||||
│
|
||||
layouts/_user_base.json 방문자 공통 뼈대 (헤더 + 콘텐츠 + 푸터)
|
||||
│ extends
|
||||
├─ layouts/home.json data_sources: 학습용 모듈 메모 API
|
||||
└─ layouts/errors/{401,403,404,500,503,maintenance}.json ← 6종 필수
|
||||
│
|
||||
src/components/basic/ Div · Button · H1 · H2 · H3 · A · Span · Img
|
||||
│
|
||||
src/index.ts ComponentRegistry 에 8개 등록 (모듈 로드 시점, 1회)
|
||||
│
|
||||
dist/ 커밋되는 빌드 산출물
|
||||
__tests__/layouts/ createLayoutTest() + API 모킹 기반 렌더링 테스트
|
||||
```
|
||||
|
||||
**서버 코드가 없습니다.** 데이터는 전부 모듈·코어의 공개 API 에서 오며, 그래서 템플릿을 갈아
|
||||
끼워도 데이터는 그대로입니다. 반대로 이 템플릿만으로는 홈 화면이 비어 있습니다 — manifest 가
|
||||
학습용 모듈을 의존으로 선언하는 이유입니다.
|
||||
|
||||
> **등록 방식이 Admin 샘플과 다릅니다.** `gnuboard7-hello_admin_template` 은
|
||||
> `initTemplate()` 안에서 100ms 간격 최대 50회 **재시도**로 컴포넌트를 등록하지만, 이 템플릿은
|
||||
> 모듈 로드 시점에 한 번만 시도하고 레지스트리가 없으면 경고를 남기고 건너뜁니다. 로드 순서에
|
||||
> 따라 등록이 누락되면 레이아웃이 컴포넌트 이름을 찾지 못해 화면이 조용히 빕니다 — 새 템플릿을
|
||||
> 만들 때는 **Admin 샘플의 재시도 형태를 따르는 것**이 안전합니다.
|
||||
|
||||
레이아웃 렌더링 테스트는 **이 템플릿 디렉토리에 둡니다**(`__tests__/layouts/`). API 응답을
|
||||
모킹해 데이터소스 연동까지 검증하는 것이 이 샘플 테스트의 학습 포인트입니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 디렉토리
|
||||
|
||||
<!-- @generated:directory-map START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 경로 | 역할 | 수정 시 필요한 절차 |
|
||||
|---|---|---|
|
||||
| `template.json` | manifest (버전 SSoT) | version 변경 시 package.json·package-lock.json 동기화 |
|
||||
| `routes.json` | 라우트 → 레이아웃 매핑 | `php artisan template:update gnuboard7-hello_user_template --force` |
|
||||
| `layouts/` | 레이아웃 JSON | `php artisan template:update gnuboard7-hello_user_template --force` (빌드 불필요) |
|
||||
| `src/components/` | React 컴포넌트 | `php artisan template:build` → `php artisan template:update gnuboard7-hello_user_template --force` |
|
||||
| `dist/` | 커밋되는 빌드 산출물 | `--production` 으로 재빌드 (sourceMappingURL 잔존 금지) |
|
||||
| `CHANGELOG.md` | 변경 이력 | 버전 상향 시 항목 추가 (미기재 시 버전 상향 불가) |
|
||||
| `components.json` | 편집기 컴포넌트 선언 (레이아웃 저작자가 읽는 props 계약) | `php artisan template:update gnuboard7-hello_user_template --force` |
|
||||
| `docs/` | 개발자 문서 | 표면 변경 시 `php artisan ext:docgen` 재실행 |
|
||||
| `lang/` | 다국어 | 키 추가 시 ko·en 동시 반영 + 번들 ja 팩 동기화 |
|
||||
<!-- @generated:directory-map END -->
|
||||
@@ -0,0 +1,30 @@
|
||||
# Hello 사용자 템플릿 — 컴포넌트
|
||||
|
||||
> 템플릿이 제공하는 컴포넌트 · 진입점: [AGENTS.md](../AGENTS.md)
|
||||
|
||||
## 제공 컴포넌트
|
||||
|
||||
<!-- @generated:components START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
컴포넌트 8개 (루트: `src/components`).
|
||||
|
||||
| 분류 | 개수 |
|
||||
|---|---|
|
||||
| `basic` | 8개 |
|
||||
<!-- @generated:components END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
Basic 8개뿐이며 composite·layout 은 없습니다 — HTML 태그를 그대로 래핑한 최소 단위만으로
|
||||
방문자 화면 골격을 그릴 수 있음을 보이는 구성입니다.
|
||||
|
||||
`sirsoft-basic` 이 79개(basic 38 · composite 36 · layout 5)를 갖는 것과 비교하면 그 차이가
|
||||
곧 "필수" 와 "그 템플릿의 선택" 의 경계입니다. 상품 카드·이미지 뷰어·모바일 메뉴 같은 것들은
|
||||
그 템플릿이 자기 화면을 위해 만든 것이지 템플릿의 요건이 아닙니다.
|
||||
|
||||
**이 목록이 다른 확장과의 계약**입니다. 모듈·플러그인이 이 템플릿의 화면에 조각을 끼워 넣을 때
|
||||
여기 없는 컴포넌트를 쓰면 그 조각은 렌더되지 않습니다.
|
||||
|
||||
컴포넌트를 추가할 때는 소스(`src/components/{분류}/`) · `template.json` 의 레지스트리 ·
|
||||
`components.json` 을 함께 갱신합니다. 하나라도 빠지면 레이아웃이 그 이름을 찾지 못해 그
|
||||
컴포넌트만 조용히 렌더되지 않습니다. TSX 를 고쳤으면 `template:build --production` 후
|
||||
`dist/` 를 함께 커밋합니다.
|
||||
<!-- @intent END -->
|
||||
@@ -0,0 +1,47 @@
|
||||
# Hello 사용자 템플릿 — 핸들러
|
||||
|
||||
> 템플릿 전용 핸들러와 부트스트랩 · 진입점: [AGENTS.md](../AGENTS.md)
|
||||
|
||||
## 템플릿 전용 핸들러
|
||||
|
||||
<!-- @generated:handlers START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
_등록하는 액션 핸들러가 없습니다._
|
||||
<!-- @generated:handlers END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
없습니다. 이 샘플의 화면은 코어 엔진의 기본 핸들러(`apiCall` · `navigate` · `setState` 등)
|
||||
만으로 그려집니다. 홈 화면의 목록도 `data_sources` 선언만으로 채워지므로 별도 핸들러가 필요
|
||||
없습니다.
|
||||
|
||||
템플릿 전용 핸들러가 필요해지는 경우는 **그 템플릿의 화면 구조에만 있는 동작**을 다룰 때입니다 —
|
||||
테마 전환, 장바구니 선택 상태, 브라우저 저장소 관리 같은 것들이며, `sirsoft-basic` 이 32개를
|
||||
갖는 것이 그 예입니다.
|
||||
|
||||
핸들러를 도입할 때는 `ActionDispatcher` 에 등록하는 부트스트랩 함수가 함께 필요하고, 그 등록도
|
||||
**레지스트리·디스패처 가용을 기다리는 재시도 루프** 안에 두어야 합니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 부트스트랩
|
||||
|
||||
<!-- @generated:frontend-entry START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
_프론트 엔트리포인트가 없습니다._
|
||||
<!-- @generated:frontend-entry END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
전용 부트스트랩 함수가 없습니다. `src/index.ts` 가 **모듈 로드 시점에 곧바로** 코어
|
||||
`ComponentRegistry` 를 찾아 Basic 8개를 등록하고, 레지스트리가 없으면 경고를 남기고
|
||||
건너뜁니다.
|
||||
|
||||
> **이 형태는 따라 하지 않는 것이 좋습니다.** 같은 샘플군의
|
||||
> `gnuboard7-hello_admin_template` 은 `initTemplate()` 안에서 100ms 간격 최대 50회
|
||||
> **재시도**하며 `window.load` 이후에 시작합니다. 한 번만 시도하는 이 형태는 로드 순서에 따라
|
||||
> 등록이 누락될 수 있고, 그러면 레이아웃이 컴포넌트 이름을 찾지 못해 **화면이 조용히
|
||||
> 빕니다** — 콘솔 경고 외에는 흔적이 없습니다.
|
||||
|
||||
템플릿은 전역 객체를 노출하지 않습니다. 모듈·플러그인은
|
||||
`window.__[Name].initModule/initPlugin` 을 고정 이름으로 노출해야 하지만(로케일 전환 후 코어가
|
||||
그것을 다시 부릅니다), 템플릿은 코어가 부트스트랩 경로를 직접 알고 있습니다.
|
||||
|
||||
액션 핸들러를 도입하면 그때 `ActionDispatcher` 등록이 추가되며, 그 등록도 같은 재시도 루프
|
||||
안에 두어야 합니다.
|
||||
<!-- @intent END -->
|
||||
@@ -0,0 +1,89 @@
|
||||
# Hello 사용자 템플릿 — 레이아웃
|
||||
|
||||
> 레이아웃 목록과 라우트 매핑 · 진입점: [AGENTS.md](../AGENTS.md)
|
||||
|
||||
## 레이아웃 목록
|
||||
|
||||
<!-- @generated:layouts START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
레이아웃 8개 (루트: `layouts`).
|
||||
|
||||
| 그룹 | 개수 |
|
||||
|---|---|
|
||||
| `(root)` | 2개 |
|
||||
| `errors` | 6개 |
|
||||
|
||||
| 레이아웃 | 그룹 | 종류 | extends |
|
||||
|---|---|---|---|
|
||||
| `_user_base` | `(root)` | partial | - |
|
||||
| `401` | `errors` | 화면 | `_user_base` |
|
||||
| `403` | `errors` | 화면 | `_user_base` |
|
||||
| `404` | `errors` | 화면 | `_user_base` |
|
||||
| `500` | `errors` | 화면 | `_user_base` |
|
||||
| `503` | `errors` | 화면 | `_user_base` |
|
||||
| `maintenance` | `errors` | 화면 | `_user_base` |
|
||||
| `home` | `(root)` | 화면 | `_user_base` |
|
||||
<!-- @generated:layouts END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
8개 중 **6개가 오류 레이아웃**입니다. 오류 6종(401 · 403 · 404 · 500 · 503 · maintenance)은
|
||||
선택이 아니라 **최소 구성**입니다 — 코어가 그 상황에서 활성 템플릿의 레이아웃을 부르는데
|
||||
없으면 방문자에게 백지가 됩니다.
|
||||
|
||||
나머지 둘이 베이스(`_user_base`)와 홈(`home`)입니다. **홈이 이 샘플의 학습 포인트**로,
|
||||
`data_sources` 로 학습용 모듈의 메모 API 를 `auto_fetch` 호출해 목록을 그립니다
|
||||
(`loading_strategy: progressive`). 모듈이 데이터를, 템플릿이 화면을 담당하는 경계가 이 한
|
||||
파일에 들어 있습니다.
|
||||
|
||||
여덟 모두 `_user_base` 를 상속합니다. **`extends` 없는 독립 레이아웃을 만들면** 그 화면에서는
|
||||
`toast`·`openModal` 이 성공으로 기록되지만 화면에는 아무것도 나타나지 않습니다(호스트
|
||||
컴포넌트가 마운트되지 않아서).
|
||||
|
||||
`401` 레이아웃에서 **로그인 리다이렉트를 직접 구현하지 않습니다** — 코어
|
||||
`TemplateApp.showRouteError` 가드가 처리하므로, 여기서 다시 이동시키면 이중 리다이렉트가 되고
|
||||
돌아올 위치도 어긋납니다.
|
||||
|
||||
레이아웃 JSON 만 고쳤다면 빌드 없이
|
||||
`php artisan template:update gnuboard7-hello_user_template --force` 로 반영합니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 라우트 매핑
|
||||
|
||||
<!-- @generated:layout-map START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 경로 | 레이아웃 | 이름 |
|
||||
|---|---|---|
|
||||
| `/` | `home` | - |
|
||||
<!-- @generated:layout-map END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
하나뿐입니다 — `/` → `home`, 그리고 `auth_required: false`.
|
||||
|
||||
**로그인 없이 접근하는 화면임을 선언**하는 것이 이 플래그의 역할입니다. User 템플릿의 대부분
|
||||
화면이 그렇고, 마이페이지처럼 회원 전용인 화면만 `true` 로 둡니다. 빠뜨리면 방문자가 첫 화면에서
|
||||
로그인으로 튕깁니다.
|
||||
|
||||
오류 레이아웃에는 라우트가 없습니다 — 코어가 상황을 보고 직접 부르므로 경로로 도달하는 화면이
|
||||
아닙니다.
|
||||
|
||||
실제 템플릿에서는 라우트 경로에 표현식을 쓸 수 있습니다(`sirsoft-basic` 의 상점 경로가
|
||||
이커머스 설정을 반영하듯). 이 샘플은 정적 경로 하나로 최소 형태만 보입니다.
|
||||
|
||||
라우트를 바꾼 뒤에는 `template:update --force` 로 반영합니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 확장 오버라이드
|
||||
|
||||
<!-- @generated:template-overrides START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
_오버라이드하는 레이아웃 확장 조각이 없습니다._
|
||||
<!-- @generated:template-overrides END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
없습니다. 이 샘플은 다른 확장이 제공한 조각을 대체하지 않습니다.
|
||||
|
||||
오버라이드는 `extensions/{확장}/` 에 그 확장의 조각과 같은 이름의 파일을 두면 성립합니다 —
|
||||
플러그인이 제공한 조각의 디자인이 이 템플릿과 어긋날 때 **조각을 고치는 대신 템플릿이 자기
|
||||
버전을 얹는** 방향입니다.
|
||||
|
||||
그 대가로 원본이 바뀌어도 사본은 따라가지 않습니다. 오버라이드를 두었다면 그 확장을 업그레이드한
|
||||
뒤 동작을 확인해야 합니다 — 원본이 핸들러 이름이나 필드 계약을 바꾸면 오버라이드만 옛 계약을
|
||||
붙들고 있게 되고, 증상은 "그 자리만 무반응" 으로 나타납니다.
|
||||
<!-- @intent END -->
|
||||
@@ -13,6 +13,7 @@
|
||||
- 코드 편집기를 불러오지 못하면 안내와 함께 일반 입력창으로 전환되어, 레이아웃을 계속 편집하고 저장할 수 있습니다.
|
||||
- 확장(템플릿·모듈·플러그인)을 제거할 때 `custom/` 에 넣어 둔 파일의 사본이 보관되며, 제거 창이 그 보관 경로를 보여 줍니다. 보관된 파일이 없으면 창은 종전대로 곧바로 닫힙니다.
|
||||
- 환경설정 > 고급 에 리버스 프록시 진단이 추가되었습니다. 사이트가 받고 있는 프록시 정보, HTTPS 인식 여부, 방문자 IP 로 인식된 값과 직전 호출 IP 를 나란히 보여 줍니다. 읽기 전용이며 값은 `.env` 에서만 변경합니다.
|
||||
- 개발자와 AI 에이전트를 위한 문서를 추가했습니다. 확장 폴더의 `AGENTS.md`(설계 의도·확장점·수정 시 확인할 것)와 `README.md`(도입·운영 안내), `docs/`(상세 문서)로 구성됩니다.
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
@@ -581,7 +581,7 @@ interface SlotContainerProps {
|
||||
|
||||
- [sirsoft-admin_basic 핸들러](handlers.md)
|
||||
- [sirsoft-admin_basic 레이아웃](layouts.md)
|
||||
- [sirsoft-basic 컴포넌트](../../../../docs/frontend/templates/sirsoft-basic/components.md)
|
||||
- [sirsoft-basic 컴포넌트](../../sirsoft-basic/docs/components.md)
|
||||
- [컴포넌트 개발 규칙](../../../../docs/frontend/components.md)
|
||||
- [컴포넌트 Props 레퍼런스](../../../../docs/frontend/component-props.md)
|
||||
- [컴포넌트 Props 레퍼런스 - Composite](../../../../docs/frontend/component-props-composite.md)
|
||||
|
||||
@@ -581,4 +581,4 @@ params 없이 호출합니다. localStorage에 저장된 테마 설정을 복원
|
||||
- [액션 핸들러 개요](../../../../docs/frontend/actions-handlers.md)
|
||||
- [sirsoft-admin_basic 컴포넌트](components.md)
|
||||
- [sirsoft-admin_basic 레이아웃](layouts.md)
|
||||
- [sirsoft-basic 핸들러](../../../../docs/frontend/templates/sirsoft-basic/handlers.md)
|
||||
- [sirsoft-basic 핸들러](../../sirsoft-basic/docs/handlers.md)
|
||||
|
||||
@@ -618,4 +618,4 @@ _admin_base.json
|
||||
- [sirsoft-admin_basic 핸들러](handlers.md)
|
||||
- [레이아웃 JSON 스키마](../../../../docs/frontend/layout-json.md)
|
||||
- [레이아웃 상속](../../../../docs/frontend/layout-json-inheritance.md)
|
||||
- [sirsoft-basic 레이아웃](../../../../docs/frontend/templates/sirsoft-basic/layouts.md)
|
||||
- [sirsoft-basic 레이아웃](../../sirsoft-basic/docs/layouts.md)
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
# Basic — 에이전트 가이드
|
||||
|
||||
> 이 문서는 이 템플릿을 수정하는 에이전트·확장개발자를 위한 것입니다. 도입 검토·운영 관점은 [README.md](README.md) 를 보세요.
|
||||
|
||||
## TL;DR (5초 요약)
|
||||
|
||||
```text
|
||||
1. 유형: 템플릿 (sirsoft-basic, type=user) — 방문자가 보는 사이트 전체 화면 166개. 게시판·이커머스 모듈은 방문자 화면을 갖지 않으므로 상점·게시판 UI 는 여기가 소유한다. 서버 코드 0줄
|
||||
2. 확장 방식: 훅 0/0 — 확장점은 화면 구조다. 다른 확장이 `layout_extensions` 로 조각을 끼우고, `extensions/{확장}/` 오버라이드로 원본 조각을 대체한다
|
||||
3. 건드리면 안 되는 것: `extends` 없는 독립 레이아웃에서 toast/modal 사용, 상점 경로 하드코딩(표현식 유지), 목록 이동의 `mergeQuery` 누락, 비회원 주문 토큰 진입 정리 제거
|
||||
4. 작업 위치: `templates/_bundled/sirsoft-basic` — 활성 디렉토리 직접 수정 금지
|
||||
5. 반영: `php artisan template:update sirsoft-basic --force`
|
||||
```
|
||||
|
||||
## 1. 이 확장은 무엇인가
|
||||
|
||||
<!-- @intent START -->
|
||||
방문자가 보는 **사이트 전체 화면**을 소유하는 사용자(User) 템플릿입니다. 홈·게시판·상점·
|
||||
마이페이지·인증·오류 화면 166개가 여기 있습니다.
|
||||
|
||||
**이것이 왜 템플릿에 있는가**가 이 확장을 이해하는 열쇠입니다. 게시판 모듈도 이커머스 모듈도
|
||||
레이아웃이 전부 `admin` 그룹이고 방문자 화면을 갖지 않습니다 — 두 모듈은 관리자 CRUD 와 공개
|
||||
API 까지만 소유하고, 그 API 를 소비해 실제로 그리는 것은 이 템플릿입니다. 상점 디자인을 바꾸는
|
||||
작업은 이커머스 모듈이 아니라 **여기**입니다.
|
||||
|
||||
**설계 원칙 넷**:
|
||||
|
||||
1. **모든 화면이 `_user_base` 를 상속한다.** 헤더·푸터·모바일 네비와 토스트·모달 호스트가
|
||||
거기 있습니다. `extends` 없이 독립 레이아웃을 만들면 `toast`·`openModal` 이 성공으로
|
||||
기록되지만 화면에는 아무것도 나타나지 않습니다.
|
||||
2. **화면은 조각으로 나눈다.** 166개 중 124개가 partial 입니다. 조각은 여러 화면이 공유하며,
|
||||
화면 하나를 고칠 때는 그 화면 이름의 partials 디렉토리를 함께 엽니다.
|
||||
3. **모듈 설정이 라우트에 스며든다.** 상점 경로 9개가 표현식이라 운영자가 이커머스 설정에서
|
||||
상점 경로를 바꾸거나 루트에 두면 라우트가 그에 맞춰 바뀝니다.
|
||||
4. **다른 확장의 조각을 갈아 끼울 수 있다.** `extensions/{확장}/` 오버라이드가 그 장치이며,
|
||||
지금은 주소 검색 조각 하나가 있습니다.
|
||||
|
||||
**의도적으로 하지 않는 것**: 서버 코드 일체(PHP 0줄 · 모델 0 · 라우트 파일 없음 · 훅 발행 0).
|
||||
데이터는 전부 모듈·코어의 공개 API 에서 옵니다. 그래서 이 템플릿을 다른 것으로 바꿔도 데이터는
|
||||
그대로이고, 반대로 이 템플릿만으로는 아무 기능도 동작하지 않습니다 — manifest 가 게시판·
|
||||
이커머스·페이지 모듈과 주소 검색 플러그인을 의존으로 선언하는 이유입니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 2. 디렉토리 지도
|
||||
|
||||
<!-- @generated:directory-map START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 경로 | 역할 | 수정 시 필요한 절차 |
|
||||
|---|---|---|
|
||||
| `template.json` | manifest (버전 SSoT) | version 변경 시 package.json·package-lock.json 동기화 |
|
||||
| `routes.json` | 라우트 → 레이아웃 매핑 | `php artisan template:update sirsoft-basic --force` |
|
||||
| `layouts/` | 레이아웃 JSON | `php artisan template:update sirsoft-basic --force` (빌드 불필요) |
|
||||
| `extensions/` | 다른 확장 화면에 주입하는 레이아웃 조각 | `php artisan template:update sirsoft-basic --force` (빌드 불필요) |
|
||||
| `seo-config.json` | SEO 렌더 설정 | `php artisan template:update sirsoft-basic --force` |
|
||||
| `src/components/` | React 컴포넌트 | `php artisan template:build` → `php artisan template:update sirsoft-basic --force` |
|
||||
| `src/handlers/` | 템플릿 전용 액션 핸들러 | `php artisan template:build` → `php artisan template:update sirsoft-basic --force` |
|
||||
| `dist/` | 커밋되는 빌드 산출물 | `--production` 으로 재빌드 (sourceMappingURL 잔존 금지) |
|
||||
| `editor-spec.json` | 레이아웃 편집기 스펙 | `php artisan template:update sirsoft-basic --force` |
|
||||
| `editor-spec/` | 분할 편집기 스펙 | `php artisan template:update sirsoft-basic --force` |
|
||||
| `tests/` | 테스트 | 변경 범위만 필터 실행 |
|
||||
| `CHANGELOG.md` | 변경 이력 | 버전 상향 시 항목 추가 (미기재 시 버전 상향 불가) |
|
||||
| `components.json` | 편집기 컴포넌트 선언 (레이아웃 저작자가 읽는 props 계약) | `php artisan template:update sirsoft-basic --force` |
|
||||
| `docs/` | 개발자 문서 | 표면 변경 시 `php artisan ext:docgen` 재실행 |
|
||||
| `lang/` | 다국어 | 키 추가 시 ko·en 동시 반영 + 번들 ja 팩 동기화 |
|
||||
<!-- @generated:directory-map END -->
|
||||
|
||||
## 3. 핵심 흐름
|
||||
|
||||
<!-- @intent START -->
|
||||
서버 코드가 없으므로 흐름은 전부 **라우트 → 레이아웃 → 데이터소스 → 컴포넌트**입니다.
|
||||
|
||||
**화면 렌더**: 브라우저가 경로 진입 → `routes.json` 이 그 경로에 대응하는 레이아웃을 지목 →
|
||||
레이아웃이 `_user_base` 를 상속해 헤더·푸터를 얻고 콘텐츠 슬롯을 채움 → `data_sources` 가
|
||||
모듈·코어의 공개 API 를 호출 → 응답을 컴포넌트에 바인딩. 상점 경로는 이 첫 단계에서
|
||||
**표현식이 평가**되어 운영자가 지정한 경로가 됩니다.
|
||||
|
||||
**장바구니(비회원 포함)**: 진입 시 `initCartKey` 가 localStorage 의 장바구니 키를 확인하고
|
||||
없으면 API 로 발급받습니다 → 담기·수량 변경·옵션 변경은 `setCartOption` ·
|
||||
`recalculateCart` 등이 이커머스 API 를 호출하고 결과로 상태를 갱신 → 로그인하면 코어
|
||||
`auth.after_login` 을 구독하는 이커머스 리스너가 비회원 장바구니를 회원 장바구니로 병합합니다
|
||||
(그 병합은 서버 쪽 일이며 이 템플릿은 키만 넘깁니다).
|
||||
|
||||
**비회원 주문 조회**: 주문 완료 시 서버가 발급한 토큰을 `saveGuestOrderToken` 이 보관 →
|
||||
조회 화면이 그 토큰으로 API 를 호출하면 서버의 `VerifyGuestOrderToken` 미들웨어가 신원을
|
||||
확인합니다. **토큰이 곧 신원**이므로 `clearGuestTokenOnEntry` 가 진입 시점에 남은 토큰을
|
||||
정리합니다 — 공용 PC 에서 다음 사용자가 남의 주문을 열지 못하게 하는 방어입니다.
|
||||
|
||||
**본인인증(IDV)**: 어떤 API 가 428 을 돌려주면 코어 인터셉터가 그것을 잡아, 부트스트랩에서
|
||||
등록한 launcher 로 인증 화면을 엽니다. 인증을 마치면 원래 액션이 재개됩니다 — 각 화면이
|
||||
428 을 개별 처리하지 않습니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 4. 확장점
|
||||
|
||||
<!-- @generated:extension-points-summary START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 확장점 | 수 | 상세 |
|
||||
|---|---|---|
|
||||
| 제공 컴포넌트 | 79개 | [제공 컴포넌트](docs/components.md#제공-컴포넌트) |
|
||||
| 레이아웃 | 166개 | [레이아웃 목록](docs/layouts.md#레이아웃-목록) |
|
||||
| 전용 핸들러 | 32개 | [템플릿 전용 핸들러](docs/handlers.md#템플릿-전용-핸들러) |
|
||||
| 확장 오버라이드 | 1개 | [확장 오버라이드](docs/layouts.md#확장-오버라이드) |
|
||||
<!-- @generated:extension-points-summary END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
이 템플릿은 훅을 **발행하지도 구독하지도 않습니다**(0/0). 템플릿의 확장점은 훅이 아니라
|
||||
**화면 구조** 자체입니다.
|
||||
|
||||
| 확장점 | 어떻게 쓰는가 |
|
||||
|---|---|
|
||||
| 레이아웃 166개 | 다른 확장이 `layout_extensions` 로 자기 조각을 끼워 넣습니다 — 이커머스의 헤더 통화 선택기·마이페이지 마일리지 카드, 마케팅의 회원가입 동의 항목이 그 예입니다 |
|
||||
| 제공 컴포넌트 79개 | 조각을 만드는 확장이 이 컴포넌트로 화면을 구성합니다. **여기 없는 컴포넌트를 쓰면 그 조각은 이 템플릿에서 렌더되지 않습니다** |
|
||||
| 전용 핸들러 32개 | 조각의 액션에서 부를 수 있습니다. 네임스페이스가 붙은 10개(`sirsoft-basic.*`)는 전체 이름으로, 나머지는 이름만으로 부릅니다 |
|
||||
| 확장 오버라이드 | `extensions/{확장}/` 에 같은 이름의 조각을 두면 그 확장이 제공한 원본을 대체합니다 |
|
||||
|
||||
**다른 확장이 이 템플릿에 화면을 얹는 방향이 정상**입니다. 반대로 이 템플릿이 모듈의 관리자
|
||||
화면에 개입하지는 않습니다.
|
||||
|
||||
`seo-config.json` 도 확장점입니다 — 봇 요청에 서버가 화면을 렌더할 때 어떤 속성을 HTML 로
|
||||
내보낼지 이 파일이 정합니다. 새 컴포넌트가 텍스트를 담는 새 prop 을 쓰면 `text_props` 에
|
||||
추가해야 봇 화면에 그 글자가 나타납니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 5. 수정 시 동반 의무
|
||||
|
||||
- [ ] `_bundled` 에서만 수정하고 `php artisan template:update sirsoft-basic --force` 로 반영
|
||||
- [ ] manifest version 상향 시 `package.json` · `package-lock.json` 동기화 + CHANGELOG 기재
|
||||
- [ ] 레이아웃 JSON 변경 시 빌드 없이 update 만 — 신규 Tailwind 클래스는 빌드된 CSS 에 존재하는지 확인
|
||||
- [ ] TSX/TS 변경 시 `--production` 재빌드 후 `dist/` 커밋 (sourceMappingURL 잔존 금지)
|
||||
- [ ] 다국어 키 추가 시 ko·en 동시 반영 + 번들 ja 언어팩 증분 동기화
|
||||
- [ ] 새 화면을 추가했다면 `_user_base` 를 상속하는지, `routes.json` 에 경로가 등록됐는지 확인
|
||||
- [ ] 상점 관련 경로·링크는 표현식(`route_path` · `no_route`)을 유지 — 문자열로 굳히지 않는다
|
||||
- [ ] 목록 클러스터(목록 → 상세 → 형제 상세 → 폼 → 복귀) 이동 전 leg 에 `mergeQuery: true`
|
||||
- [ ] 텍스트를 담는 새 컴포넌트 prop 을 도입했다면 `seo-config.json` 의 `text_props` 에 추가 (봇 화면에서만 글자가 사라진다)
|
||||
- [ ] 의존 모듈(게시판·이커머스·페이지)의 공개 API 응답 형태가 바뀌면 이 템플릿의 화면이 조용히 빈다 — 그 모듈을 올릴 때 함께 확인
|
||||
- [ ] `extensions/sirsoft-daum_postcode/` 오버라이드는 원본이 바뀌어도 따라가지 않는다 — 그 플러그인 업그레이드 후 확인
|
||||
- [ ] TSX/TS 를 고쳤다면 `template:build --production` 후 `dist/` 동반 커밋 (`sourceMappingURL` 잔존 금지)
|
||||
- [ ] 프론트엔드 변경은 Playwright spec 동반 — 단위 테스트만으로는 화면 회귀가 드러나지 않는다
|
||||
|
||||
## 6. 금지 패턴
|
||||
|
||||
<!-- @intent START -->
|
||||
| 금지 | 올바른 사용 | 이유 |
|
||||
|---|---|---|
|
||||
| `extends` 없는 독립 레이아웃에서 `toast`·`openModal` 사용 | `_user_base` 를 상속하거나, 독립 레이아웃이라면 `Toast`·모달 호스트 컴포넌트를 직접 마운트 | 호스트가 없으면 핸들러는 성공으로 기록되는데 화면에는 아무것도 나타나지 않는다 |
|
||||
| 상점 경로를 `/shop/...` 로 하드코딩 | `routes.json` 의 표현식 유지 (`route_path` · `no_route` 반영) | 운영자가 경로를 바꾸거나 루트에 두면 하드코딩한 링크만 조용히 깨진다 |
|
||||
| 목록 → 상세 → 목록 이동에서 `mergeQuery` 누락 | 목록 클러스터 내 모든 이동에 `"mergeQuery": true` | 검색어·페이지·필터가 사라져 사용자가 처음부터 다시 찾아야 한다 |
|
||||
| 비회원 주문 토큰을 진입 시점에 정리하지 않음 | `clearGuestTokenOnEntry` 유지 | 토큰이 곧 신원이다 — 공용 PC 에서 다음 사용자가 남의 주문을 연다 |
|
||||
| `401` 오류 레이아웃에서 로그인으로 직접 리다이렉트 | 코어 `TemplateApp.showRouteError` 가드에 위임 | 이중 리다이렉트가 되고, 돌아올 위치를 코어가 이미 관리한다 |
|
||||
| 모듈·플러그인에서 `AuthManager.updateConfig()` 호출 | 템플릿 부트스트랩에서만 | 여러 확장이 로그인 경로를 다투면 어느 값이 이기는지 설치 순서에 좌우된다 |
|
||||
| 새 컴포넌트가 텍스트를 담는 prop 을 추가하면서 `seo-config.json` 을 그대로 두기 | `text_props` 에 그 prop 추가 | 봇 화면에서만 그 글자가 사라진다 — 사람 눈에는 정상이라 검색 노출이 줄어든 뒤에야 드러난다 |
|
||||
| 레이아웃 JSON 에 빌드된 CSS 에 없는 Tailwind 클래스 사용 | 기존 레이아웃에 쓰인 클래스이거나 빌드 산출물에 존재하는지 확인 | 그 스타일만 조용히 빠져 화면이 어긋난다 |
|
||||
| `dist/` 재빌드 없이 `src/` 만 고치고 커밋 | `template:build --production` 후 `dist/` 동반 커밋 | 브라우저가 받는 것은 커밋된 `dist/` 다 — 소스 수정이 사문화된다 |
|
||||
<!-- @intent END -->
|
||||
|
||||
## 7. 테스트 실행
|
||||
|
||||
<!-- @generated:test-commands START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 종류 | 개수 | 위치 |
|
||||
|---|---|---|
|
||||
| PHPUnit | 0개 | — |
|
||||
| Vitest | 141개 | `vitest.config.ts` |
|
||||
| Playwright | 8개 | `tests/Playwright` |
|
||||
| 시나리오 매니페스트 | 3개 | `tests/scenarios` |
|
||||
|
||||
```bash
|
||||
# Vitest (확장 디렉토리에서) (PowerShell)
|
||||
cd templates/_bundled/sirsoft-basic && powershell -Command "npm run test:run -- <대상>"
|
||||
|
||||
# Playwright E2E (Bash)
|
||||
npx playwright test templates/_bundled/sirsoft-basic/tests/Playwright/specs/<대상>.spec.ts
|
||||
|
||||
```
|
||||
|
||||
무필터 전체 실행은 금지되어 있습니다 — 변경 범위에 걸리는 대상만 지정해 실행합니다.
|
||||
<!-- @generated:test-commands END -->
|
||||
|
||||
## 8. 문서 목차
|
||||
|
||||
<!-- @generated:docs-index START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 문서 | 내용 | 상태 |
|
||||
|---|---|---|
|
||||
| [docs/README.md](docs/README.md) | 문서 통합 목차와 실측 집계 | ✅ |
|
||||
| [docs/architecture.md](docs/architecture.md) | 설계 의도·계층 지도·디렉토리 맵 | ✅ |
|
||||
| [docs/components.md](docs/components.md) | 템플릿이 제공하는 컴포넌트 | ✅ |
|
||||
| [docs/layouts.md](docs/layouts.md) | 레이아웃 목록과 라우트 매핑 | ✅ |
|
||||
| [docs/handlers.md](docs/handlers.md) | 템플릿 전용 핸들러와 부트스트랩 | ✅ |
|
||||
| [CHANGELOG.md](CHANGELOG.md) | 변경 이력 | ✅ |
|
||||
<!-- @generated:docs-index END -->
|
||||
@@ -11,6 +11,7 @@
|
||||
- 아이콘(Font Awesome)과 본문 글꼴(Pretendard)을 템플릿에 함께 담았습니다. 이제 외부 CDN 에 연결하지 않고 사이트 자신의 서버에서 불러오므로, 폐쇄망이나 외부 접속이 제한된 환경에서도 아이콘과 글꼴이 정상 표시됩니다.
|
||||
- 이미지 업로드 시 쓰는 압축 라이브러리도 함께 담아, 업로드할 때마다 외부로 나가던 요청이 없어졌습니다.
|
||||
- 검색엔진 크롤러가 보는 페이지도 같은 아이콘 파일을 사이트 자신의 주소에서 불러옵니다.
|
||||
- 개발자와 AI 에이전트를 위한 문서를 추가했습니다. 확장 폴더의 `AGENTS.md`(설계 의도·확장점·수정 시 확인할 것)와 `README.md`(도입·운영 안내), `docs/`(상세 문서)로 구성됩니다.
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
# Basic
|
||||
|
||||
**G7 템플릿 · sirsoft-basic**
|
||||
그누보드7 기본 사용자 템플릿
|
||||
|
||||
<!-- @generated:badges START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/version-1.1.3-0066FF?style=flat-square" alt="version 1.1.3">
|
||||
<img src="https://img.shields.io/badge/type-%ED%85%9C%ED%94%8C%EB%A6%BF-555555?style=flat-square" alt="type 템플릿">
|
||||
<img src="https://img.shields.io/badge/G7-%3E%3D7.0.10-1F883D?style=flat-square" alt="G7 >=7.0.10">
|
||||
<img src="https://img.shields.io/badge/license-MIT-8250DF?style=flat-square" alt="license MIT">
|
||||
<img src="https://img.shields.io/badge/requires-sirsoft--board-BF8700?style=flat-square" alt="requires sirsoft-board">
|
||||
<img src="https://img.shields.io/badge/requires-sirsoft--ecommerce-BF8700?style=flat-square" alt="requires sirsoft-ecommerce">
|
||||
<img src="https://img.shields.io/badge/requires-sirsoft--page-BF8700?style=flat-square" alt="requires sirsoft-page">
|
||||
<img src="https://img.shields.io/badge/requires-sirsoft--daum__postcode-BF8700?style=flat-square" alt="requires sirsoft-daum_postcode">
|
||||
</p>
|
||||
<!-- @generated:badges END -->
|
||||
|
||||
---
|
||||
|
||||
[소개](#소개) · [주요 기능](#주요-기능) · [동작 방식](#동작-방식) · [요구 사항](#요구-사항) · [설치](#설치) · [제공 컴포넌트](#제공-컴포넌트) · [사용 방법](#사용-방법) · [다른 확장과의 연동](#다른-확장과의-연동) · [문서](#문서) · [트러블슈팅](#트러블슈팅) · [변경 이력](#변경-이력) · [라이선스](#라이선스)
|
||||
|
||||
---
|
||||
|
||||
## 소개
|
||||
|
||||
<!-- @intent START -->
|
||||
방문자가 보는 **사이트 전체 화면**을 담당하는 기본 사용자 템플릿입니다. 홈·게시판·상점·
|
||||
마이페이지·로그인·오류 화면이 모두 여기 들어 있습니다.
|
||||
|
||||
그누보드7 에서 게시판이나 쇼핑몰 모듈은 데이터와 관리자 화면을 담당하고, **방문자에게 보이는
|
||||
모습은 템플릿이 정합니다.** 그래서 상점이나 게시판의 디자인을 바꾸고 싶다면 그 모듈이 아니라
|
||||
이 템플릿(또는 다른 사용자 템플릿)을 손봅니다.
|
||||
|
||||
다크 모드·반응형·다국어·다중 통화를 기본으로 지원하며, 상점 경로처럼 운영자가 환경설정에서
|
||||
바꾸는 값은 화면과 주소에 자동으로 반영됩니다.
|
||||
|
||||
이 템플릿만으로는 동작하지 않습니다 — 게시판·이커머스·페이지 모듈과 주소 검색 플러그인이
|
||||
함께 설치·활성화되어 있어야 합니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 주요 기능
|
||||
|
||||
<!-- @intent START -->
|
||||
| 영역 | 설명 |
|
||||
|---|---|
|
||||
| 홈·공통 | 헤더·푸터·모바일 네비게이션, 통합 검색, 알림 센터, 다크/라이트 전환 |
|
||||
| 인증 | 로그인·회원가입·비밀번호 찾기/재설정·본인인증 화면, 소셜 로그인 버튼 |
|
||||
| 게시판 | 게시판 목록·글 목록·글 보기·글쓰기, 인기글, 게시판 유형별 표시 |
|
||||
| 상점 | 상품 목록·카테고리·상품 상세·장바구니·주문서·주문 완료, 비회원 주문 조회와 재주문 |
|
||||
| 마이페이지 | 프로필·비밀번호 변경·주문 내역·마일리지·찜·배송지·알림·내 게시글·문의 |
|
||||
| 단일 문서 | 회사소개·약관 같은 페이지 표시 |
|
||||
| 오류 화면 | 401·403·404·500·503·점검 중 |
|
||||
| 다국어·다통화 | 언어 전환, 표시 통화 선택과 통화별 가격 표시 |
|
||||
| 반응형·다크 모드 | 모바일/데스크톱 레이아웃 분기, 시스템 설정 연동 테마 |
|
||||
<!-- @intent END -->
|
||||
|
||||
## 동작 방식
|
||||
|
||||
<!-- @intent START -->
|
||||
```mermaid
|
||||
flowchart TD
|
||||
B[_user_base<br/>헤더 · 푸터 · 모바일 네비 · 토스트/모달] --> A[auth<br/>로그인·가입·본인인증]
|
||||
B --> BD[board<br/>게시판 목록·글·작성]
|
||||
B --> S[shop<br/>상품·장바구니·주문]
|
||||
B --> M[mypage<br/>프로필·주문·마일리지]
|
||||
B --> P[page<br/>단일 문서]
|
||||
B --> E[errors<br/>401·403·404·500·503]
|
||||
```
|
||||
|
||||
모든 화면이 하나의 베이스를 물려받습니다. 헤더·푸터·모바일 메뉴와 알림 표시가 그 베이스에
|
||||
있어, 사이트 전체의 공통 요소를 한 곳에서 바꿀 수 있습니다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
V[방문자] --> T[템플릿 화면]
|
||||
T -->|공개 API 호출| MOD[게시판·이커머스·페이지 모듈]
|
||||
MOD --> DB[(데이터)]
|
||||
```
|
||||
|
||||
화면은 이 템플릿이 그리고 데이터는 모듈이 제공합니다. 그래서 템플릿을 바꿔도 게시글이나
|
||||
주문 데이터는 그대로 남습니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 요구 사항
|
||||
|
||||
<!-- @generated:requirements START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 항목 | 값 |
|
||||
|---|---|
|
||||
| G7 코어 | `>=7.0.10` |
|
||||
| PHP | `^8.2` |
|
||||
| 의존 모듈 | `sirsoft-board` `>=1.0.0` |
|
||||
| 의존 모듈 | `sirsoft-ecommerce` `>=1.1.0` |
|
||||
| 의존 모듈 | `sirsoft-page` `>=1.1.0` |
|
||||
| 의존 플러그인 | `sirsoft-daum_postcode` `>=1.0.0` |
|
||||
<!-- @generated:requirements END -->
|
||||
|
||||
## 설치
|
||||
|
||||
<!-- @generated:install START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
```bash
|
||||
# 번들 설치 (코어에 동봉된 소스에서 설치)
|
||||
php artisan template:install sirsoft-basic
|
||||
|
||||
# 활성화
|
||||
php artisan template:activate sirsoft-basic
|
||||
|
||||
# 업데이트 (번들 소스 기준 강제 반영)
|
||||
php artisan template:update sirsoft-basic --force
|
||||
```
|
||||
|
||||
저장소: https://github.com/gnuboard/g7-template-sirsoft-basic
|
||||
<!-- @generated:install END -->
|
||||
|
||||
## 제공 컴포넌트
|
||||
|
||||
<!-- @generated:settings-summary START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
컴포넌트 79개 (루트: `src/components`).
|
||||
|
||||
| 분류 | 개수 |
|
||||
|---|---|
|
||||
| `basic` | 38개 |
|
||||
| `composite` | 36개 |
|
||||
| `layout` | 5개 |
|
||||
<!-- @generated:settings-summary END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
위 개수는 이 템플릿이 화면을 그리는 데 쓰는 **부품**의 수입니다. 운영자가 직접 다룰 일은
|
||||
없지만, 화면을 직접 손보거나 확장을 붙일 때는 **여기 있는 부품만 쓸 수 있습니다** — 목록에 없는
|
||||
부품을 쓴 화면 조각은 이 템플릿에서 렌더되지 않습니다.
|
||||
|
||||
관리자 템플릿(`sirsoft-admin_basic`)과는 구성이 다릅니다. 표·필터·다국어 입력 같은 관리 도구가
|
||||
없고, 대신 상품 카드·이미지 뷰어·수량 선택기·게시글 반응·모바일 메뉴·소셜 로그인처럼 **방문자
|
||||
화면에 필요한 것들**이 있습니다.
|
||||
|
||||
전체 목록과 각 부품의 사용법은 [docs/components.md](docs/components.md) 에 있습니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 사용 방법
|
||||
|
||||
<!-- @intent START -->
|
||||
**도입**: 템플릿을 설치·활성화하면 사이트의 방문자 화면이 이 템플릿으로 바뀝니다. 게시판·
|
||||
이커머스·페이지 모듈과 주소 검색 플러그인이 함께 활성화되어 있어야 모든 화면이 정상 동작합니다 —
|
||||
예를 들어 이커머스가 없으면 상점 메뉴로 들어갔을 때 데이터를 받지 못합니다.
|
||||
|
||||
**상점 주소 바꾸기**: 이커머스 환경설정에서 상점 경로를 바꾸면(`shop` → `store`) 이 템플릿의
|
||||
상점 화면 주소도 함께 바뀝니다. 상점을 사이트 첫 화면으로 쓰려면 같은 설정에서 "경로 없음"
|
||||
으로 두면 `/products` 처럼 최상위 주소가 됩니다. 템플릿을 고칠 필요가 없습니다.
|
||||
|
||||
**색상·문구 손보기**: 화면 구성은 레이아웃 파일(JSON)이 정하고 부품 모양은 컴포넌트가
|
||||
정합니다. 레이아웃만 고치는 변경(문구·배치·표시 항목)은 다시 빌드할 필요 없이
|
||||
`php artisan template:update sirsoft-basic --force` 로 반영됩니다.
|
||||
|
||||
**다른 템플릿으로 교체**: 이 템플릿은 화면만 담당하므로, 다른 사용자 템플릿으로 바꿔도
|
||||
게시글·주문·회원 데이터는 그대로입니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 다른 확장과의 연동
|
||||
|
||||
<!-- @generated:integrations START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
**이 확장이 의존하는 확장**
|
||||
|
||||
| 확장 | 유형 | 버전 제약 | 번들 |
|
||||
|---|---|---|---|
|
||||
| `sirsoft-board` | 모듈 | `>=1.0.0` | ✅ |
|
||||
| `sirsoft-ecommerce` | 모듈 | `>=1.1.0` | ✅ |
|
||||
| `sirsoft-page` | 모듈 | `>=1.1.0` | ✅ |
|
||||
| `sirsoft-daum_postcode` | 플러그인 | `>=1.0.0` | ✅ |
|
||||
|
||||
**이 확장에 의존하는 확장** (이 확장을 비활성화하면 함께 영향을 받습니다)
|
||||
|
||||
없음.
|
||||
<!-- @generated:integrations END -->
|
||||
|
||||
## 문서
|
||||
|
||||
<!-- @generated:docs-index START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 문서 | 내용 | 상태 |
|
||||
|---|---|---|
|
||||
| [docs/README.md](docs/README.md) | 문서 통합 목차와 실측 집계 | ✅ |
|
||||
| [docs/architecture.md](docs/architecture.md) | 설계 의도·계층 지도·디렉토리 맵 | ✅ |
|
||||
| [docs/components.md](docs/components.md) | 템플릿이 제공하는 컴포넌트 | ✅ |
|
||||
| [docs/layouts.md](docs/layouts.md) | 레이아웃 목록과 라우트 매핑 | ✅ |
|
||||
| [docs/handlers.md](docs/handlers.md) | 템플릿 전용 핸들러와 부트스트랩 | ✅ |
|
||||
| [CHANGELOG.md](CHANGELOG.md) | 변경 이력 | ✅ |
|
||||
<!-- @generated:docs-index END -->
|
||||
|
||||
## 트러블슈팅
|
||||
|
||||
<!-- @intent START -->
|
||||
| 증상 | 원인 | 조치 |
|
||||
|---|---|---|
|
||||
| 상점 메뉴로 들어가면 화면이 비어 있음 | 이커머스 모듈이 비활성이거나 상품이 없음 | 모듈 활성화 여부와 상품 진열 상태를 확인합니다 |
|
||||
| 상점 주소가 예전 경로 그대로 | 브라우저에 저장된 예전 링크 | 이 템플릿의 상점 주소는 이커머스 설정을 따릅니다. 메뉴를 통해 다시 들어가면 새 주소가 적용됩니다 |
|
||||
| 게시판 메뉴는 보이는데 글 목록이 안 나옴 | 게시판 모듈이 비활성이거나 그 게시판의 접근 권한이 제한됨 | 모듈 활성화와 게시판별 권한 설정을 확인합니다 |
|
||||
| 주소 검색 버튼이 없거나 눌러도 반응이 없음 | 주소 검색 플러그인이 비활성이거나 외부 접속이 차단됨 | 플러그인 활성화를 확인합니다. 검색을 불러오지 못하면 주소를 직접 입력할 수 있습니다 |
|
||||
| 비회원으로 주문 조회를 했는데 이전 주문이 열림 | 브라우저에 남아 있던 조회 정보 | 이 템플릿은 조회 화면에 들어갈 때마다 남은 정보를 정리합니다. 계속 발생하면 브라우저 데이터를 지우고 다시 시도합니다 |
|
||||
| 알림 메시지나 팝업이 뜨지 않음 | 그 화면이 공통 베이스를 쓰지 않음 | 직접 추가한 화면이라면 공통 베이스를 상속하도록 고칩니다 |
|
||||
| 검색엔진 노출 화면에 일부 글자가 빠짐 | 새 부품이 쓰는 항목이 검색엔진용 렌더 설정에 없음 | `seo-config.json` 의 텍스트 항목 목록을 확인합니다 |
|
||||
| 화면 일부의 여백·색이 어긋남 | 새로 쓴 스타일 클래스가 빌드된 CSS 에 없음 | 기존 화면에서 쓰이던 클래스인지 확인하고, 필요하면 템플릿을 다시 빌드합니다 |
|
||||
<!-- @intent END -->
|
||||
|
||||
## 변경 이력
|
||||
|
||||
[CHANGELOG.md](CHANGELOG.md)
|
||||
|
||||
## 라이선스
|
||||
|
||||
MIT
|
||||
@@ -0,0 +1,20 @@
|
||||
# Basic 개발자 문서
|
||||
|
||||
> templates/_bundled/sirsoft-basic · 템플릿
|
||||
|
||||
<!-- @generated:stats START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
**훅 수**: 0 · **구독 훅 수**: 0 · **라우트 수**: 40 · **모델 수**: 0 · **테이블 수**: 0 · **마이그레이션 수**: 0 · **레이아웃 수**: 166 · **핸들러 수**: 32
|
||||
<!-- @generated:stats END -->
|
||||
|
||||
## 문서 목차
|
||||
|
||||
<!-- @generated:doc-toc START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 문서 | 내용 |
|
||||
|---|---|
|
||||
| [architecture.md](architecture.md) | 설계 의도·계층 지도·디렉토리 맵 |
|
||||
| [components.md](components.md) | 템플릿이 제공하는 컴포넌트 |
|
||||
| [layouts.md](layouts.md) | 레이아웃 목록과 라우트 매핑 |
|
||||
| [handlers.md](handlers.md) | 템플릿 전용 핸들러와 부트스트랩 |
|
||||
| [../AGENTS.md](../AGENTS.md) | 에이전트·확장개발자 진입점 |
|
||||
| [../README.md](../README.md) | 사람(도입검토자·운영자) 진입점 |
|
||||
<!-- @generated:doc-toc END -->
|
||||
@@ -0,0 +1,85 @@
|
||||
# Basic — 아키텍처
|
||||
|
||||
> 설계 의도와 계층 구조 · 진입점: [AGENTS.md](../AGENTS.md)
|
||||
|
||||
## 설계 의도
|
||||
|
||||
<!-- @intent START -->
|
||||
"모듈은 데이터, 템플릿은 화면" 이라는 경계가 이 템플릿의 존재 이유입니다.
|
||||
|
||||
게시판 모듈도 이커머스 모듈도 레이아웃이 전부 `admin` 그룹이고 방문자 화면을 갖지 않습니다.
|
||||
두 모듈이 방문자 화면을 소유하면 템플릿마다 다른 디자인을 그 모듈이 전부 알아야 하는데, 공개
|
||||
API 로만 노출하면 템플릿이 자유롭게 구성할 수 있습니다. 그 대가로 **방문자가 보는 커머스·
|
||||
게시판 UI 는 사실상 이 템플릿이 전부**이며, 상점 화면을 고치는 작업은 이커머스 모듈이 아니라
|
||||
여기입니다.
|
||||
|
||||
그 위에 화면 조직 원칙 셋이 있습니다.
|
||||
|
||||
- **단일 베이스.** 166개 전부 `_user_base` 를 상속합니다. 헤더·푸터·모바일 네비와 토스트·모달
|
||||
호스트가 거기 있어, 상속하지 않은 화면에서는 전역 UI 가 통째로 없습니다.
|
||||
- **조각 중심.** 124개가 partial 입니다. 화면이 커서가 아니라, 조각을 여러 화면이 공유하기
|
||||
때문입니다 — 상품 카드·주소 폼·탭 구조가 그 예입니다.
|
||||
- **설정이 라우트에 스며든다.** 상점 경로는 정적 문자열이 아니라 표현식입니다. 운영자가 상점을
|
||||
`/store` 로 옮기거나 루트에 두면 `routes.json` 의 그 표현식이 따라갑니다.
|
||||
|
||||
**서버 코드가 없습니다** — PHP 0줄 · 모델 0 · 라우트 파일 없음 · 훅 발행/구독 0. 데이터는 전부
|
||||
모듈·코어의 공개 API 에서 오며, 그래서 이 템플릿만으로는 아무 기능도 동작하지 않습니다.
|
||||
manifest 가 게시판·이커머스·페이지 모듈과 주소 검색 플러그인을 **의존으로 선언**하는 이유이고,
|
||||
반대로 템플릿을 갈아 끼워도 데이터는 그대로인 이유이기도 합니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 계층 지도
|
||||
|
||||
<!-- @intent START -->
|
||||
```
|
||||
routes.json (경로 → 레이아웃. 상점 9개는 표현식 — 운영자 설정이 평가되어 들어온다)
|
||||
│
|
||||
▼
|
||||
layouts/_user_base.json (헤더 · 푸터 · 모바일 네비 · 토스트/모달 호스트 · 콘텐츠 슬롯)
|
||||
│ extends
|
||||
▼
|
||||
layouts/{auth,board,shop,mypage,page,search,users,errors}/*.json 화면 42
|
||||
│ partial 참조
|
||||
▼
|
||||
layouts/partials/** 조각 124
|
||||
│ data_sources → 모듈·코어 공개 API
|
||||
│ actions → 코어 빌트인 핸들러 + 이 템플릿의 전용 핸들러 32
|
||||
▼
|
||||
src/components/{basic,composite,layout}/ 컴포넌트 79 → dist/ (커밋되는 빌드 산출물)
|
||||
|
||||
extensions/{확장}/*.json 다른 확장이 제공한 조각을 대체하는 오버라이드
|
||||
seo-config.json 봇 화면 렌더 규칙 (어떤 prop 을 HTML 로 내보낼지)
|
||||
src/index.ts initTemplate() — 핸들러 등록 · IDV launcher · iOS 판정 보정
|
||||
```
|
||||
|
||||
**두 방향의 주입이 이 템플릿에서 만납니다.** 다른 확장이 `layout_extensions` 로 이 템플릿의
|
||||
화면에 조각을 끼워 넣고(이커머스 통화 선택기·마케팅 동의 항목), 이 템플릿은
|
||||
`extensions/{확장}/` 으로 그 확장이 제공한 조각을 자기 것으로 대체합니다. 앞은 확장이 화면을
|
||||
넓히는 통로이고, 뒤는 템플릿이 디자인 주도권을 되찾는 통로입니다.
|
||||
|
||||
`seo-config.json` 은 계층 밖에 있지만 화면과 짝을 이룹니다 — 봇 요청에는 React 가 아니라 서버
|
||||
렌더러가 화면을 그리므로, 컴포넌트가 새 prop 에 텍스트를 담기 시작하면 이 파일의 `text_props`
|
||||
에 그 prop 을 더해야 봇 화면에도 글자가 실립니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 디렉토리
|
||||
|
||||
<!-- @generated:directory-map START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 경로 | 역할 | 수정 시 필요한 절차 |
|
||||
|---|---|---|
|
||||
| `template.json` | manifest (버전 SSoT) | version 변경 시 package.json·package-lock.json 동기화 |
|
||||
| `routes.json` | 라우트 → 레이아웃 매핑 | `php artisan template:update sirsoft-basic --force` |
|
||||
| `layouts/` | 레이아웃 JSON | `php artisan template:update sirsoft-basic --force` (빌드 불필요) |
|
||||
| `extensions/` | 다른 확장 화면에 주입하는 레이아웃 조각 | `php artisan template:update sirsoft-basic --force` (빌드 불필요) |
|
||||
| `seo-config.json` | SEO 렌더 설정 | `php artisan template:update sirsoft-basic --force` |
|
||||
| `src/components/` | React 컴포넌트 | `php artisan template:build` → `php artisan template:update sirsoft-basic --force` |
|
||||
| `src/handlers/` | 템플릿 전용 액션 핸들러 | `php artisan template:build` → `php artisan template:update sirsoft-basic --force` |
|
||||
| `dist/` | 커밋되는 빌드 산출물 | `--production` 으로 재빌드 (sourceMappingURL 잔존 금지) |
|
||||
| `editor-spec.json` | 레이아웃 편집기 스펙 | `php artisan template:update sirsoft-basic --force` |
|
||||
| `editor-spec/` | 분할 편집기 스펙 | `php artisan template:update sirsoft-basic --force` |
|
||||
| `tests/` | 테스트 | 변경 범위만 필터 실행 |
|
||||
| `CHANGELOG.md` | 변경 이력 | 버전 상향 시 항목 추가 (미기재 시 버전 상향 불가) |
|
||||
| `components.json` | 편집기 컴포넌트 선언 (레이아웃 저작자가 읽는 props 계약) | `php artisan template:update sirsoft-basic --force` |
|
||||
| `docs/` | 개발자 문서 | 표면 변경 시 `php artisan ext:docgen` 재실행 |
|
||||
| `lang/` | 다국어 | 키 추가 시 ko·en 동시 반영 + 번들 ja 팩 동기화 |
|
||||
<!-- @generated:directory-map END -->
|
||||
@@ -0,0 +1,305 @@
|
||||
# Basic — 컴포넌트
|
||||
|
||||
> 템플릿이 제공하는 컴포넌트 · 진입점: [AGENTS.md](../AGENTS.md)
|
||||
|
||||
## 제공 컴포넌트
|
||||
|
||||
<!-- @generated:components START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
컴포넌트 79개 (루트: `src/components`).
|
||||
|
||||
| 분류 | 개수 |
|
||||
|---|---|
|
||||
| `basic` | 38개 |
|
||||
| `composite` | 36개 |
|
||||
| `layout` | 5개 |
|
||||
<!-- @generated:components END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
세 분류는 "얼마나 조합됐는가" 로 나뉩니다 — `basic` 은 HTML 태그를 그대로 래핑한 최소 단위
|
||||
(`Div`→`<div>`), `composite` 는 basic 을 조합해 UI 패턴을 캡슐화한 것(`Header` 가 로고·네비·
|
||||
검색·사용자 메뉴를 감싸는 식), `layout` 은 페이지 구조(Container/Grid/Flex)입니다. 새 컴포넌트는
|
||||
이 구분에 맞는 디렉토리(`src/components/{basic,composite,layout}/`)에 넣어야 `components.json`
|
||||
카탈로그와 `editor-spec.json` 팔레트 분류가 어긋나지 않습니다.
|
||||
|
||||
**이 템플릿은 방문자 화면 전용**이라 관리자 템플릿(`sirsoft-admin_basic`)과 컴포넌트 구성이
|
||||
크게 다릅니다. `DataGrid` · `AdminSidebar` · `MultilingualInput` 같은 관리 도구가 없고, 대신
|
||||
`ProductCard` · `ProductImageViewer` · `QuantitySelector` · `PostReactions` · `MobileNav` ·
|
||||
`SocialLoginButtons` 처럼 상점·게시판·인증 화면에 필요한 것들이 있습니다. **모듈이 방문자
|
||||
화면을 그리지 않고 API 만 제공하는 구조**(게시판·이커머스 모두 레이아웃이 전부 `admin` 그룹)
|
||||
이므로, 방문자가 보는 커머스·게시판 UI 는 사실상 이 템플릿의 컴포넌트가 전부입니다.
|
||||
|
||||
위 개수는 코드에서 실측되므로 시간이 지나면 달라집니다 — 이 문서에 구체적 개수를 하드코딩하지
|
||||
않습니다. 정확한 전체 목록·Props 는 코어의 컴포넌트 Props 레퍼런스를 따르며, 이 문서는 "이
|
||||
템플릿에서만" 유효한 것을 다룹니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 이관 원문 상세
|
||||
|
||||
> 아래는 코어 `docs/frontend/templates/sirsoft-basic/components.md` 에 있던 원문을 이 문서로
|
||||
> 옮긴 것입니다(#601). 이관 시점 그대로 보존하되, **코드가 SSoT 인 값과 어긋나는 부분에는
|
||||
> 정정 주석**을 달았습니다 — 실측 총계는 위 「제공 컴포넌트」 블록이 SSoT 입니다.
|
||||
|
||||
### sirsoft-basic 컴포넌트
|
||||
|
||||
> **템플릿 식별자**: `sirsoft-basic` (type: user, v0.4.16)
|
||||
> **관련 문서**: [핸들러](handlers.md) | [레이아웃](layouts.md) | [컴포넌트 Props 레퍼런스](../../../../docs/frontend/component-props.md)
|
||||
|
||||
> **정정(#601)**: 위 버전(v0.4.16)은 이관 시점 문서 값입니다. 현재 버전은 `template.json` 이
|
||||
> SSoT 입니다.
|
||||
|
||||
---
|
||||
|
||||
### TL;DR (5초 요약)
|
||||
|
||||
```text
|
||||
1. Basic 26개: HTML 래핑 (Div, Button, Input, Select, Form, A, H1~H4, PasswordInput 등)
|
||||
2. Composite 27개: UI 패턴 캡슐화 (Header, Footer, Modal, ProductCard, Pagination 등)
|
||||
3. Layout 5개: 페이지 구조 (Container, Grid, Flex, SectionLayout, ThreeColumnLayout)
|
||||
(정정(#601): 개수는 이관 시점 값 — 실측은 위 「제공 컴포넌트」 블록이 SSoT)
|
||||
4. 사용자(User) 템플릿 전용 — 모듈 레이아웃(user/ 하위)에서 사용
|
||||
5. features: dark_mode, responsive, multi_language, multi_currency 지원
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 목차
|
||||
|
||||
1. [컴포넌트 개요](#컴포넌트-개요)
|
||||
2. [Basic Components (26개)](#basic-components-26개)
|
||||
3. [Composite Components (27개)](#composite-components-27개)
|
||||
4. [Layout Components (5개)](#layout-components-5개)
|
||||
5. [sirsoft-admin_basic과의 차이](#sirsoft-admin_basic과의-차이)
|
||||
|
||||
---
|
||||
|
||||
### 컴포넌트 개요
|
||||
|
||||
> **정정(#601)**: 아래 개수는 이관 시점 문서 값입니다. 코드 실측은 basic 38 · composite 36 ·
|
||||
> layout 5 = **79종**이며 위 「제공 컴포넌트」 블록이 SSoT 입니다. 아래 목록에 없는 컴포넌트가
|
||||
> 있습니다(basic 의 `Code` · `FileInput` · `I` · `Ol` · `Optgroup` · `Section` · `Svg` ·
|
||||
> `Tbody` · `Td` · `Th` · `Thead` · `Tr`, composite 의 `BrandMark` · `NotificationCenter` ·
|
||||
> `PageLoading` · `SlotContainer` 등).
|
||||
|
||||
| 타입 | 개수 | 설명 |
|
||||
|------|------|------|
|
||||
| Basic | 26 | HTML 태그 래핑 — 최소 단위 컴포넌트 |
|
||||
| Composite | 27 | 기본 컴포넌트 조합 — UI 패턴 캡슐화 |
|
||||
| Layout | 5 | 페이지 구조 정의 — 컨테이너/그리드/플렉스 |
|
||||
| **합계** | **58** | |
|
||||
|
||||
**소스**: `templates/_bundled/sirsoft-basic/components.json`
|
||||
**컴포넌트 소스**: `templates/_bundled/sirsoft-basic/src/components/{basic,composite,layout}/*.tsx`
|
||||
|
||||
---
|
||||
|
||||
### Basic Components (26개)
|
||||
|
||||
> **정정(#601)**: 제목의 개수는 이관 시점 문서 값입니다(코드 실측 38종). 목록 자체는 원문 그대로입니다.
|
||||
|
||||
HTML 태그를 래핑하는 최소 단위 컴포넌트입니다.
|
||||
|
||||
#### 텍스트/링크
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props | 바인딩 |
|
||||
|----------|------|-----------|--------|
|
||||
| `A` | 앵커/링크 | href | - |
|
||||
| `H1` | 제목 (h1) | - | - |
|
||||
| `H2` | 제목 (h2) | - | - |
|
||||
| `H3` | 제목 (h3) | - | - |
|
||||
| `H4` | 제목 (h4) | - | - |
|
||||
| `P` | 문단 | - | - |
|
||||
| `Span` | 인라인 텍스트 | - | - |
|
||||
| `Label` | 라벨 | - | - |
|
||||
|
||||
#### 컨테이너
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props | 바인딩 |
|
||||
|----------|------|-----------|--------|
|
||||
| `Div` | 범용 컨테이너 | - | - |
|
||||
| `Nav` | 네비게이션 래퍼 | - | - |
|
||||
| `Form` | 폼 컨테이너 | - | - |
|
||||
| `Header` | HTML header 래퍼 | - | - |
|
||||
| `Footer` | HTML footer 래퍼 | - | - |
|
||||
| `Hr` | 수평선 | - | - |
|
||||
|
||||
#### 폼 입력
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props | 바인딩 |
|
||||
|----------|------|-----------|--------|
|
||||
| `Input` | 텍스트 입력 | label, error | checkable |
|
||||
| `Select` | 선택 박스 | label, error | - |
|
||||
| `Option` | Select 내부 옵션 | value, disabled | - |
|
||||
| `Textarea` | 텍스트 영역 | label, error | - |
|
||||
| `Checkbox` | 체크박스 | label | checked |
|
||||
| `PasswordInput` | 비밀번호 입력 (보기/숨기기, 조건 검증, 확인 일치) | label, error, showToggle, showValidation, isConfirmField, confirmTarget, showRules, ... | - |
|
||||
| `Button` | 버튼 | variant, size | - |
|
||||
|
||||
#### 미디어
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props | 바인딩 |
|
||||
|----------|------|-----------|--------|
|
||||
| `Icon` | FontAwesome 아이콘 | name | - |
|
||||
| `Img` | 이미지 | src, alt | - |
|
||||
|
||||
#### 기타
|
||||
|
||||
| 컴포넌트 | 설명 |
|
||||
|----------|------|
|
||||
| `Table` | HTML table wrapper |
|
||||
| `Ul` | 순서 없는 리스트 |
|
||||
| `Li` | 리스트 아이템 |
|
||||
|
||||
---
|
||||
|
||||
### Composite Components (27개)
|
||||
|
||||
> **정정(#601)**: 제목의 개수는 이관 시점 문서 값입니다(코드 실측 36종). 목록 자체는 원문 그대로입니다.
|
||||
|
||||
기본 컴포넌트를 조합하여 UI 패턴을 캡슐화한 복합 컴포넌트입니다.
|
||||
|
||||
#### 사이트 레이아웃
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props |
|
||||
|----------|------|-----------|
|
||||
| `Header` | 사이트 헤더 (로고, 네비게이션, 검색, 사용자 메뉴) | siteName |
|
||||
| `Footer` | 사이트 푸터 (저작권, 링크, 소셜) | siteName |
|
||||
| `MobileNav` | 모바일 네비게이션 드로어 | - |
|
||||
|
||||
#### 쇼핑몰
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props |
|
||||
|----------|------|-----------|
|
||||
| `ProductCard` | 상품 카드 (이미지, 제목, 가격) | product, showDiscount |
|
||||
| `ProductImageViewer` | 상품 이미지 뷰어 (메인 + 썸네일 + 라이트박스) | images |
|
||||
| `QuantitySelector` | 수량 선택기 (+/- 버튼) | value, min, max |
|
||||
|
||||
#### 게시판
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props |
|
||||
|----------|------|-----------|
|
||||
| `PostReactions` | 게시글 리액션 버튼 그룹 | postId, reactions |
|
||||
| `ExpandableContent` | 콘텐츠 펼치기/접기 (높이 초과 시 그라데이션) | maxHeight, expandText, collapseText |
|
||||
|
||||
#### 인증
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props |
|
||||
|----------|------|-----------|
|
||||
| `SocialLoginButtons` | 소셜 로그인 버튼 그룹 | providers |
|
||||
|
||||
#### 사용자
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props |
|
||||
|----------|------|-----------|
|
||||
| `Avatar` | 사용자 아바타 (이미지 또는 이름 첫 글자) | name, avatar, size, text |
|
||||
| `AvatarUploader` | 아바타 이미지 업로드 (원형 UI, 즉시 업로드) | src, fallbackText, size, uploadEndpoint, deleteEndpoint, showDeleteButton, ... |
|
||||
| `UserInfo` | 사용자 정보 표시 및 드롭다운 | name, userId, subText, isGuest, showDropdown, clickable, ... |
|
||||
|
||||
#### 에디터/콘텐츠
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props |
|
||||
|----------|------|-----------|
|
||||
| `HtmlContent` | HTML 안전 렌더링 (DOMPurify XSS 방지) | content, text, isHtml, purifyConfig |
|
||||
| `HtmlEditor` | HTML/텍스트 에디터 (WYSIWYG, 미리보기) | name, value, placeholder, isHtml, showPreview, minHeight |
|
||||
| `RichTextEditor` | 리치 텍스트 에디터 | value, placeholder |
|
||||
| `ImageGallery` | 이미지 갤러리 (썸네일 + 메인 이미지) | images |
|
||||
|
||||
#### 파일/미디어
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props |
|
||||
|----------|------|-----------|
|
||||
| `FileUploader` | 파일 업로드 | accept, maxSize, multiple |
|
||||
|
||||
#### 네비게이션/피드백
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props |
|
||||
|----------|------|-----------|
|
||||
| `TabNavigation` | 탭 네비게이션 (underline/pills/enclosed) | tabs, activeTabId, variant, onTabChange, hiddenTabIds |
|
||||
| `Pagination` | 페이지네이션 | currentPage, totalPages, maxVisiblePages, showFirstLast, prevText, nextText |
|
||||
|
||||
> **TabNavigation `hiddenTabIds`**: 특정 조건에서 탭을 동적으로 숨길 때 사용합니다. 탭 아이템의 `id` 배열을 전달하면 해당 탭이 렌더링에서 제외됩니다.
|
||||
> ```json
|
||||
> {
|
||||
> "name": "TabNavigation",
|
||||
> "props": {
|
||||
> "tabs": [...],
|
||||
> "hiddenTabIds": "{{_global.modules?.['sirsoft-ecommerce']?.inquiry?.board_slug ? [] : ['qna']}}"
|
||||
> }
|
||||
> }
|
||||
> ```
|
||||
| `SearchBar` | 검색 바 (자동완성 지원) | name, placeholder, value, showButton, suggestions, showSuggestions |
|
||||
| `Modal` | 모달 다이얼로그 (ESC, 포커스 트랩) | isOpen, onClose, title, width |
|
||||
| `ConfirmDialog` | 확인/취소 다이얼로그 | isOpen, onClose, title, message, confirmText, cancelText, ... |
|
||||
| `Toast` | 토스트 알림 | toasts, position, duration |
|
||||
| `ThemeToggle` | 테마 전환 토글 (auto/light/dark) | autoText, lightText, darkText |
|
||||
|
||||
#### 페이지 전환
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props |
|
||||
|----------|------|-----------|
|
||||
| `PageTransitionIndicator` | 페이지 전환 로딩 인디케이터 | - |
|
||||
| `PageTransitionBlur` | 페이지 전환 시 콘텐츠 블러 오버레이 | - |
|
||||
| `PageSkeleton` | 동적 스켈레톤 UI 렌더러 | components, options |
|
||||
|
||||
---
|
||||
|
||||
### Layout Components (5개)
|
||||
|
||||
페이지 구조를 정의하는 레이아웃 컴포넌트입니다.
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props |
|
||||
|----------|------|-----------|
|
||||
| `Container` | 레이아웃 컨테이너 | maxWidth |
|
||||
| `Flex` | Flexbox 레이아웃 | direction, gap |
|
||||
| `Grid` | Grid 레이아웃 | cols, gap |
|
||||
| `SectionLayout` | 섹션 레이아웃 (제목, 부제목, 패딩) | title, subtitle, padding, background |
|
||||
| `ThreeColumnLayout` | 3단 레이아웃 (좌/중/우) | leftWidth, centerWidth, rightWidth, gap |
|
||||
|
||||
---
|
||||
|
||||
### sirsoft-admin_basic과의 차이
|
||||
|
||||
#### 공통 컴포넌트
|
||||
|
||||
두 템플릿 모두에 존재하는 컴포넌트:
|
||||
|
||||
| 타입 | 공통 컴포넌트 |
|
||||
|------|-------------|
|
||||
| Basic | A, Button, Checkbox, Div, Form, H1~H4, Icon, Img, Input, Label, Li, Nav, P, Select, Span, Table, Textarea, Ul |
|
||||
| Composite | ConfirmDialog, FileUploader, HtmlContent, HtmlEditor, ImageGallery, Modal, Pagination, ProductCard, SearchBar, TabNavigation, ThemeToggle, Toast |
|
||||
| Layout | Container, Flex, Grid, SectionLayout, ThreeColumnLayout |
|
||||
|
||||
#### sirsoft-basic 전용 컴포넌트
|
||||
|
||||
| 타입 | 전용 컴포넌트 | 설명 |
|
||||
|------|-------------|------|
|
||||
| Basic | `PasswordInput` | 비밀번호 입력 (보기/숨기기, 검증) |
|
||||
| Basic | `Header`, `Footer`, `Hr` | HTML5 시맨틱 태그 |
|
||||
| Composite | `Header`, `Footer` | 사이트 헤더/푸터 (composite) |
|
||||
| Composite | `MobileNav` | 모바일 드로어 네비게이션 |
|
||||
| Composite | `ProductImageViewer` | 상품 이미지 뷰어 |
|
||||
| Composite | `QuantitySelector` | 수량 선택기 |
|
||||
| Composite | `PostReactions` | 게시글 리액션 |
|
||||
| Composite | `SocialLoginButtons` | 소셜 로그인 |
|
||||
| Composite | `Avatar`, `AvatarUploader` | 아바타 관련 |
|
||||
| Composite | `UserInfo` | 사용자 정보 드롭다운 |
|
||||
| Composite | `RichTextEditor` | 리치 텍스트 에디터 |
|
||||
| Composite | `ExpandableContent` | 콘텐츠 접기/펼치기 |
|
||||
| Composite | `PageTransitionBlur` | 전환 블러 효과 |
|
||||
| Composite | `PageSkeleton` | 스켈레톤 UI |
|
||||
|
||||
#### sirsoft-admin_basic 전용 컴포넌트 (이 템플릿에 없음)
|
||||
|
||||
AdminSidebar, AdminHeader, AdminFooter, PageHeader, DataGrid, CodeEditor, DynamicFieldList, MultilingualInput, TagInput, Toggle, RadioGroup, FormField, SlotContainer, FilterGroup 등 관리자 전용 컴포넌트 39개+
|
||||
|
||||
> **정정(#601)**: `SlotContainer` 는 이 템플릿에도 있습니다(`src/components/composite/SlotContainer.tsx`).
|
||||
> 나머지 항목은 이관 시점 그대로입니다.
|
||||
|
||||
---
|
||||
|
||||
### 관련 문서
|
||||
|
||||
- [sirsoft-basic 핸들러](handlers.md)
|
||||
- [sirsoft-basic 레이아웃](layouts.md)
|
||||
- [sirsoft-admin_basic 컴포넌트](../../sirsoft-admin_basic/docs/components.md)
|
||||
- [컴포넌트 개발 규칙](../../../../docs/frontend/components.md)
|
||||
- [컴포넌트 Props 레퍼런스](../../../../docs/frontend/component-props.md)
|
||||
@@ -0,0 +1,551 @@
|
||||
# Basic — 핸들러
|
||||
|
||||
> 템플릿 전용 핸들러와 부트스트랩 · 진입점: [AGENTS.md](../AGENTS.md)
|
||||
|
||||
## 템플릿 전용 핸들러
|
||||
|
||||
<!-- @generated:handlers START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
핸들러 32개 (정의: `src/handlers/index.ts`).
|
||||
|
||||
| 핸들러 | 레이아웃에서 부르는 이름 |
|
||||
|---|---|
|
||||
| `addSelectedItemIfComplete` | `sirsoft-basic.addSelectedItemIfComplete` |
|
||||
| `updateSelectedItemQuantity` | `sirsoft-basic.updateSelectedItemQuantity` |
|
||||
| `removeSelectedItem` | `sirsoft-basic.removeSelectedItem` |
|
||||
| `updateNoOptionQuantity` | `sirsoft-basic.updateNoOptionQuantity` |
|
||||
| `setBlockAdditionalOption` | `sirsoft-basic.setBlockAdditionalOption` |
|
||||
| `getDisplayPrice` | `sirsoft-basic.getDisplayPrice` |
|
||||
| `formatCurrency` | `sirsoft-basic.formatCurrency` |
|
||||
| `getCurrencySymbol` | `sirsoft-basic.getCurrencySymbol` |
|
||||
| `loadPreferredCurrency` | `sirsoft-basic.loadPreferredCurrency` |
|
||||
| `savePreferredCurrency` | `sirsoft-basic.savePreferredCurrency` |
|
||||
| `setTheme` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `initTheme` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `redirectToLoginWithReturn` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `downloadAttachment` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `toggleCartItemSelection` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `selectAllCartItems` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `setCartOption` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `openCartDeleteModal` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `openCartOptionModal` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `recalculateCart` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `findMatchingOption` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `initCartOptionSelection` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `initCartKey` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `getCartKey` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `clearCartKey` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `regenerateCartKey` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `saveToStorage` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `loadFromStorage` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `initGuestOrderToken` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `saveGuestOrderToken` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `clearGuestOrderToken` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `clearGuestTokenOnEntry` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
<!-- @generated:handlers END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
32개가 **두 종류로 갈립니다** — 네임스페이스를 붙여 등록한 10개(`sirsoft-basic.*`)와 붙이지
|
||||
않은 22개. 레이아웃에서 부를 때 전자는 전체 이름을 그대로 써야 하고, 후자는 이름만 씁니다.
|
||||
|
||||
| 무리 | 예 | 무엇을 하는가 |
|
||||
|---|---|---|
|
||||
| 상품 옵션 (`sirsoft-basic.*` 5) | `addSelectedItemIfComplete` · `updateSelectedItemQuantity` · `removeSelectedItem` · `updateNoOptionQuantity` · `setBlockAdditionalOption` | 상품 상세에서 옵션 조합이 완성될 때마다 선택 목록을 갱신 |
|
||||
| 통화 (`sirsoft-basic.*` 5) | `getDisplayPrice` · `formatCurrency` · `getCurrencySymbol` · `loadPreferredCurrency` · `savePreferredCurrency` | 표시 통화 전환과 금액 포맷 |
|
||||
| 테마 2 | `setTheme` · `initTheme` | 다크/라이트 전환 (관리자 템플릿과 **같은 localStorage 키**를 공유) |
|
||||
| 장바구니 8 | `toggleCartItemSelection` · `setCartOption` · `recalculateCart` · `findMatchingOption` 등 | 선택·옵션 변경·재계산 |
|
||||
| 저장소 6 | `initCartKey` · `getCartKey` · `saveToStorage` 등 | 비회원 장바구니 키 관리(localStorage + API 발급) |
|
||||
| 비회원 주문 4 | `initGuestOrderToken` · `saveGuestOrderToken` · `clearGuestOrderToken` · `clearGuestTokenOnEntry` | 비회원 주문 조회 토큰 보관·폐기 |
|
||||
| 기타 2 | `redirectToLoginWithReturn` · `downloadAttachment` | 로그인 후 원래 자리 복귀, 첨부 내려받기 |
|
||||
|
||||
**비회원 토큰 4종이 이 템플릿에서 가장 조심스러운 자리입니다.** 그 토큰이 곧 신원이므로
|
||||
(서버측 `VerifyGuestOrderToken` 이 그것만 보고 주문을 엽니다), 브라우저에 남아 있으면 다음
|
||||
사용자가 남의 주문을 열 수 있습니다. `clearGuestTokenOnEntry` 가 진입 시점에 정리하는 것이
|
||||
그 방어입니다.
|
||||
|
||||
`setLocale` 은 이 템플릿의 핸들러가 아닙니다 — 엔진(`ActionDispatcher`) 빌트인이라 등록이
|
||||
필요 없습니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 부트스트랩
|
||||
|
||||
<!-- @generated:frontend-entry START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 항목 | 값 |
|
||||
|---|---|
|
||||
| 엔트리 파일 | `src/index.ts` |
|
||||
| 전역 객체 | **미노출** |
|
||||
| 재등록 진입점 | `initTemplate()` |
|
||||
|
||||
재등록 진입점이 전역에 고정 이름으로 노출되지 않으면 로케일 전환 후 이 확장의 액션이 전부 무반응이 됩니다 (오류·토스트 없음).
|
||||
<!-- @generated:frontend-entry END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
전역 객체가 **미노출**입니다. 모듈·플러그인은 `window.__[Name].initModule/initPlugin` 을 고정
|
||||
이름으로 노출해야 하지만(로케일 전환 후 코어가 그것을 다시 부릅니다), 템플릿은 코어가 부트스트랩
|
||||
경로를 직접 알고 있어 전역 노출이 필요하지 않습니다.
|
||||
|
||||
`initTemplate()` 이 그 진입점이며 모듈 로드 시점에 스스로 실행됩니다. 하는 일이 셋입니다:
|
||||
|
||||
1. **핸들러 등록** — `handlerMap` 전량을 `ActionDispatcher` 에 올립니다. 개별 등록이 아니라
|
||||
맵을 순회하므로, 핸들러를 추가할 때 등록 코드를 함께 고칠 필요가 없습니다.
|
||||
2. **IDV launcher 등록** — `window.G7Core.identity.setLauncher()` 로 본인인증 화면을 여는
|
||||
방법을 코어에 알립니다. 이것이 없으면 428 응답을 받은 화면이 인증 창을 띄우지 못합니다.
|
||||
3. **iOS 판정 보정** — 서버 UA 판정이 놓치는 iPadOS(데스크탑 UA)를 클라이언트 신호로 바로잡아
|
||||
`appConfig.isIos` 에 반영합니다. 체크아웃의 애플페이 노출이 이 값을 봅니다.
|
||||
|
||||
**`ActionDispatcher` 가용을 기다리는 재시도 루프**(100ms × 최대 50회)가 들어 있습니다.
|
||||
`window.load` 이후에 시작하며, 그 안에서 세 작업이 함께 일어납니다.
|
||||
|
||||
레이아웃 편집기 위젯 등록만은 **이 함수 밖**에서 모듈 로드 즉시 실행됩니다. 편집기 URL 을 직접
|
||||
하드로드한 경로에서는 `window.load` 게이트를 기다리면 등록이 편집기 셸 마운트보다 늦어 위젯이
|
||||
누락되기 때문입니다("Unsupported control"). 진입 경로와 무관하게 결정적이어야 하는 등록은 이
|
||||
자리에 둡니다.
|
||||
|
||||
이 템플릿은 `AuthManager.updateConfig()` 를 부르지 않습니다 — 코어 기본값을 그대로 씁니다.
|
||||
호출이 필요해지면 **템플릿 부트스트랩에서만** 하고(모듈·플러그인에서 부르면 안 됩니다),
|
||||
`loginPath` 는 `/` 로 시작하는 동일 origin 경로여야 합니다. `//` 로 시작하거나 외부 origin 을
|
||||
주면 open redirect 가 됩니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 이관 원문 상세
|
||||
|
||||
> 아래는 코어 `docs/frontend/templates/sirsoft-basic/handlers.md` 에 있던 원문을 이 문서로 옮긴 것입니다(#601). 이관 시점 그대로
|
||||
> 보존하되, **코드가 SSoT 인 값과 어긋나는 부분에는 정정 주석**을 달았습니다.
|
||||
|
||||
### sirsoft-basic 핸들러
|
||||
|
||||
> **템플릿 식별자**: `sirsoft-basic` (type: user)
|
||||
> **관련 문서**: [액션 핸들러 개요](../../../../docs/frontend/actions-handlers.md) | [컴포넌트](components.md) | [레이아웃](layouts.md)
|
||||
|
||||
---
|
||||
|
||||
### TL;DR (5초 요약)
|
||||
|
||||
```text
|
||||
1. setTheme/initTheme: 다크/라이트 모드 전환 (admin과 동일 키 공유)
|
||||
2. 장바구니 6종: 선택/옵션/삭제/재계산 핸들러
|
||||
3. 상품 옵션 2종: 옵션 완료 시 자동 추가/수량 변경
|
||||
4. 다중 통화 5종: 가격 표시/포맷/통화 기호/선호 통화 로드/저장
|
||||
5. 스토리지 6종: 비회원 장바구니 키 관리 (localStorage + API 발급)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 목차
|
||||
|
||||
1. [테마 핸들러](#테마-핸들러)
|
||||
2. [장바구니 핸들러](#장바구니-핸들러)
|
||||
3. [장바구니 옵션 변경 핸들러](#장바구니-옵션-변경-핸들러)
|
||||
4. [상품 옵션 핸들러](#상품-옵션-핸들러)
|
||||
5. [다중 통화 핸들러](#다중-통화-핸들러)
|
||||
6. [스토리지 핸들러](#스토리지-핸들러)
|
||||
7. [핸들러 등록 맵](#핸들러-등록-맵)
|
||||
|
||||
---
|
||||
|
||||
### 테마 핸들러
|
||||
|
||||
**소스**: `src/handlers/setThemeHandler.ts`
|
||||
|
||||
sirsoft-admin_basic과 동일한 localStorage 키(`g7_color_scheme`)를 사용하여 테마 설정을 공유합니다.
|
||||
|
||||
#### setTheme
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "click",
|
||||
"handler": "setTheme",
|
||||
"params": {
|
||||
"theme": "dark"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 필드 | 타입 | 필수 | 설명 |
|
||||
|------|------|------|------|
|
||||
| `theme` | string | ✅ | `"light"`, `"dark"`, `"auto"` (시스템 설정 따름) |
|
||||
|
||||
#### initTheme
|
||||
|
||||
앱 시작 시 `init_actions`에서 호출. params 없음.
|
||||
|
||||
```json
|
||||
{
|
||||
"init_actions": [
|
||||
{ "handler": "initTheme" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 장바구니 핸들러
|
||||
|
||||
**소스**: `src/handlers/cartHandlers.ts`
|
||||
|
||||
장바구니 페이지에서 상품 선택, 옵션 변경, 삭제 등을 처리합니다.
|
||||
|
||||
#### toggleCartItemSelection
|
||||
|
||||
장바구니 아이템 선택/해제를 토글합니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"handler": "toggleCartItemSelection",
|
||||
"params": {
|
||||
"itemId": "{{item.id}}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### selectAllCartItems
|
||||
|
||||
모든 장바구니 아이템을 선택/해제합니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"handler": "selectAllCartItems",
|
||||
"params": {
|
||||
"selected": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### setCartOption
|
||||
|
||||
장바구니 아이템의 옵션을 변경합니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"handler": "setCartOption",
|
||||
"params": {
|
||||
"itemId": "{{item.id}}",
|
||||
"optionId": "{{selectedOption.id}}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### openCartDeleteModal
|
||||
|
||||
장바구니 삭제 확인 모달을 엽니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"handler": "openCartDeleteModal",
|
||||
"params": {
|
||||
"itemId": "{{item.id}}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### openCartOptionModal
|
||||
|
||||
장바구니 옵션 변경 모달을 엽니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"handler": "openCartOptionModal",
|
||||
"params": {
|
||||
"itemId": "{{item.id}}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### recalculateCart
|
||||
|
||||
장바구니 합계를 재계산합니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"handler": "recalculateCart"
|
||||
}
|
||||
```
|
||||
|
||||
#### 장바구니 핸들러 요약
|
||||
|
||||
| 핸들러 | params | 설명 |
|
||||
|--------|--------|------|
|
||||
| `toggleCartItemSelection` | `{ itemId }` | 아이템 선택 토글 |
|
||||
| `selectAllCartItems` | `{ selected }` | 전체 선택/해제 |
|
||||
| `setCartOption` | `{ itemId, optionId }` | 옵션 변경 |
|
||||
| `openCartDeleteModal` | `{ itemId }` | 삭제 모달 열기 |
|
||||
| `openCartOptionModal` | `{ itemId }` | 옵션 변경 모달 열기 |
|
||||
| `recalculateCart` | 없음 | 합계 재계산 |
|
||||
|
||||
---
|
||||
|
||||
### 장바구니 옵션 변경 핸들러
|
||||
|
||||
**소스**: `src/handlers/cartOptionChange.ts`
|
||||
|
||||
장바구니 옵션 변경 모달에서 옵션 선택 및 매칭을 처리합니다.
|
||||
|
||||
#### findMatchingOption
|
||||
|
||||
선택한 옵션 값들로 매칭되는 상품 옵션을 찾습니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"handler": "findMatchingOption",
|
||||
"params": {
|
||||
"options": "{{_local.options}}",
|
||||
"selection": "{{_local.optionSelection}}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### initCartOptionSelection
|
||||
|
||||
옵션 변경 모달 초기화 시 현재 선택된 옵션을 설정합니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"handler": "initCartOptionSelection",
|
||||
"params": {
|
||||
"currentOption": "{{item.option}}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 상품 옵션 핸들러
|
||||
|
||||
**소스**: `src/handlers/productOptions.ts`
|
||||
|
||||
상품 상세 페이지에서 옵션 선택 및 수량 변경을 처리합니다.
|
||||
|
||||
#### addSelectedItemIfComplete (sirsoft-basic.addSelectedItemIfComplete)
|
||||
|
||||
모든 옵션 그룹 선택 완료 시 선택 아이템 목록에 자동 추가합니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"handler": "sirsoft-basic.addSelectedItemIfComplete",
|
||||
"params": {
|
||||
"newGroupName": "{{groupName}}",
|
||||
"newValue": "{{selectedValue}}",
|
||||
"optionGroups": "{{product?.data?.option_groups}}",
|
||||
"options": "{{product?.data?.options}}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### updateSelectedItemQuantity (sirsoft-basic.updateSelectedItemQuantity)
|
||||
|
||||
선택된 아이템의 수량을 변경합니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"handler": "sirsoft-basic.updateSelectedItemQuantity",
|
||||
"params": {
|
||||
"optionId": "{{option.id}}",
|
||||
"quantity": "{{newQuantity}}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 다중 통화 핸들러
|
||||
|
||||
#### getDisplayPrice (sirsoft-basic.getDisplayPrice)
|
||||
|
||||
**소스**: `src/handlers/getDisplayPrice.ts`
|
||||
|
||||
사용자 선호 통화에 맞는 가격을 반환합니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"handler": "sirsoft-basic.getDisplayPrice",
|
||||
"params": {
|
||||
"product": "{{product.data}}",
|
||||
"priceField": "selling_price"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 필드 | 타입 | 필수 | 설명 |
|
||||
|------|------|------|------|
|
||||
| `product` | object | ✅ | 상품 객체 |
|
||||
| `priceField` | string | ✅ | `"selling_price"` 또는 `"list_price"` |
|
||||
| `currencyCode` | string | ❌ | 통화 코드 (미지정 시 전역 설정 사용) |
|
||||
|
||||
#### formatCurrency (sirsoft-basic.formatCurrency)
|
||||
|
||||
**소스**: `src/handlers/formatCurrency.ts`
|
||||
|
||||
숫자 값을 통화 형식 문자열로 변환합니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"handler": "sirsoft-basic.formatCurrency",
|
||||
"params": {
|
||||
"value": 10000,
|
||||
"currencyCode": "KRW"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 필드 | 타입 | 필수 | 설명 |
|
||||
|------|------|------|------|
|
||||
| `value` | number | ✅ | 포맷팅할 숫자 값 |
|
||||
| `currencyCode` | string | ❌ | 통화 코드 (KRW, USD, JPY, CNY, EUR) |
|
||||
| `locale` | string | ❌ | 로케일 (미지정 시 통화 기본 로케일 사용) |
|
||||
|
||||
지원 통화: KRW (₩), USD ($), JPY (¥), CNY (¥), EUR (€)
|
||||
|
||||
#### getCurrencySymbol (sirsoft-basic.getCurrencySymbol)
|
||||
|
||||
통화 기호를 반환합니다.
|
||||
|
||||
#### loadPreferredCurrency (sirsoft-basic.loadPreferredCurrency)
|
||||
|
||||
**소스**: `src/handlers/loadPreferredCurrency.ts`
|
||||
|
||||
localStorage에서 선호 통화를 로드하여 전역 상태(`_global.preferredCurrency`)에 설정합니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"init_actions": [
|
||||
{
|
||||
"handler": "sirsoft-basic.loadPreferredCurrency",
|
||||
"params": { "defaultCurrency": "KRW" }
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### savePreferredCurrency (sirsoft-basic.savePreferredCurrency)
|
||||
|
||||
선호 통화를 localStorage에 저장합니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"handler": "sirsoft-basic.savePreferredCurrency",
|
||||
"params": {
|
||||
"currencyCode": "{{selectedCurrency}}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 스토리지 핸들러
|
||||
|
||||
**소스**: `src/handlers/storageHandlers.ts`
|
||||
|
||||
비로그인 사용자의 장바구니 키 등 클라이언트 스토리지를 관리합니다.
|
||||
|
||||
#### initCartKey
|
||||
|
||||
장바구니 키를 초기화합니다. localStorage에 있으면 로드, 없으면 API를 통해 발급합니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"init_actions": [
|
||||
{ "handler": "initCartKey" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### getCartKey / clearCartKey / regenerateCartKey
|
||||
|
||||
```json
|
||||
{ "handler": "getCartKey" }
|
||||
{ "handler": "clearCartKey" }
|
||||
{ "handler": "regenerateCartKey" }
|
||||
```
|
||||
|
||||
#### saveToStorage / loadFromStorage
|
||||
|
||||
범용 localStorage 저장/로드 핸들러입니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"handler": "saveToStorage",
|
||||
"params": {
|
||||
"key": "g7_some_setting",
|
||||
"value": "{{_local.settingValue}}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"handler": "loadFromStorage",
|
||||
"params": {
|
||||
"key": "g7_some_setting",
|
||||
"stateKey": "savedSetting"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 스토리지 핸들러 요약
|
||||
|
||||
| 핸들러 | params | 설명 |
|
||||
|--------|--------|------|
|
||||
| `initCartKey` | 없음 | 장바구니 키 초기화 (localStorage + API) |
|
||||
| `getCartKey` | 없음 | 현재 장바구니 키 반환 |
|
||||
| `clearCartKey` | 없음 | 장바구니 키 삭제 |
|
||||
| `regenerateCartKey` | 없음 | 장바구니 키 재발급 |
|
||||
| `saveToStorage` | `{ key, value }` | localStorage 저장 |
|
||||
| `loadFromStorage` | `{ key, stateKey }` | localStorage 로드 → 상태에 설정 |
|
||||
|
||||
---
|
||||
|
||||
### 핸들러 등록 맵
|
||||
|
||||
> **정정(#601)**: 아래 표는 이관 시점 값(23개)입니다. 코드 실측은 **32개**이며 위 「템플릿 전용
|
||||
> 핸들러」 블록이 SSoT 입니다. 아래 표에 없는 9개는
|
||||
> `sirsoft-basic.removeSelectedItem` · `sirsoft-basic.updateNoOptionQuantity` ·
|
||||
> `sirsoft-basic.setBlockAdditionalOption` · `redirectToLoginWithReturn` ·
|
||||
> `downloadAttachment` · `initGuestOrderToken` · `saveGuestOrderToken` ·
|
||||
> `clearGuestOrderToken` · `clearGuestTokenOnEntry` 입니다.
|
||||
|
||||
**소스**: `src/handlers/index.ts`
|
||||
|
||||
| 등록 키 | 소스 파일 | 설명 |
|
||||
|---------|----------|------|
|
||||
| `setTheme` | setThemeHandler.ts | 테마 변경 |
|
||||
| `initTheme` | setThemeHandler.ts | 테마 초기화 |
|
||||
| `toggleCartItemSelection` | cartHandlers.ts | 장바구니 아이템 선택 |
|
||||
| `selectAllCartItems` | cartHandlers.ts | 장바구니 전체 선택 |
|
||||
| `setCartOption` | cartHandlers.ts | 장바구니 옵션 변경 |
|
||||
| `openCartDeleteModal` | cartHandlers.ts | 삭제 모달 열기 |
|
||||
| `openCartOptionModal` | cartHandlers.ts | 옵션 모달 열기 |
|
||||
| `recalculateCart` | cartHandlers.ts | 장바구니 재계산 |
|
||||
| `findMatchingOption` | cartOptionChange.ts | 매칭 옵션 검색 |
|
||||
| `initCartOptionSelection` | cartOptionChange.ts | 옵션 선택 초기화 |
|
||||
| `sirsoft-basic.addSelectedItemIfComplete` | productOptions.ts | 옵션 완료 시 자동 추가 |
|
||||
| `sirsoft-basic.updateSelectedItemQuantity` | productOptions.ts | 수량 변경 |
|
||||
| `sirsoft-basic.getDisplayPrice` | getDisplayPrice.ts | 통화별 가격 표시 |
|
||||
| `sirsoft-basic.formatCurrency` | formatCurrency.ts | 통화 포맷팅 |
|
||||
| `sirsoft-basic.getCurrencySymbol` | formatCurrency.ts | 통화 기호 |
|
||||
| `sirsoft-basic.loadPreferredCurrency` | loadPreferredCurrency.ts | 선호 통화 로드 |
|
||||
| `sirsoft-basic.savePreferredCurrency` | loadPreferredCurrency.ts | 선호 통화 저장 |
|
||||
| `initCartKey` | storageHandlers.ts | 장바구니 키 초기화 |
|
||||
| `getCartKey` | storageHandlers.ts | 장바구니 키 조회 |
|
||||
| `clearCartKey` | storageHandlers.ts | 장바구니 키 삭제 |
|
||||
| `regenerateCartKey` | storageHandlers.ts | 장바구니 키 재발급 |
|
||||
| `saveToStorage` | storageHandlers.ts | localStorage 저장 |
|
||||
| `loadFromStorage` | storageHandlers.ts | localStorage 로드 |
|
||||
|
||||
---
|
||||
|
||||
### 주의사항
|
||||
|
||||
```text
|
||||
이 핸들러들은 sirsoft-basic 템플릿에서만 등록됨
|
||||
sirsoft-basic. 접두사 핸들러는 풀네임으로 호출해야 함
|
||||
setLocale은 엔진 레벨(ActionDispatcher) 빌트인 — 별도 등록 불필요
|
||||
✅ 범용 핸들러(navigate, apiCall, setState 등)는 actions-handlers.md 참조
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 관련 문서
|
||||
|
||||
- [액션 핸들러 개요](../../../../docs/frontend/actions-handlers.md)
|
||||
- [sirsoft-basic 컴포넌트](components.md)
|
||||
- [sirsoft-basic 레이아웃](layouts.md)
|
||||
- [sirsoft-admin_basic 핸들러](../../sirsoft-admin_basic/docs/handlers.md)
|
||||
@@ -0,0 +1,951 @@
|
||||
# Basic — 레이아웃
|
||||
|
||||
> 레이아웃 목록과 라우트 매핑 · 진입점: [AGENTS.md](../AGENTS.md)
|
||||
|
||||
## 레이아웃 목록
|
||||
|
||||
<!-- @generated:layouts START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
레이아웃 166개 (루트: `layouts`).
|
||||
|
||||
| 그룹 | 개수 |
|
||||
|---|---|
|
||||
| `(root)` | 2개 |
|
||||
| `auth` | 5개 |
|
||||
| `board` | 5개 |
|
||||
| `errors` | 6개 |
|
||||
| `mypage` | 11개 |
|
||||
| `page` | 1개 |
|
||||
| `partials` | 124개 |
|
||||
| `search` | 1개 |
|
||||
| `shop` | 9개 |
|
||||
| `users` | 2개 |
|
||||
|
||||
| 레이아웃 | 그룹 | 종류 | extends |
|
||||
|---|---|---|---|
|
||||
| `_user_base` | `(root)` | partial | - |
|
||||
| `forgot_password` | `auth` | 화면 | `_user_base` |
|
||||
| `identity_challenge` | `auth` | 화면 | `_user_base` |
|
||||
| `login` | `auth` | 화면 | `_user_base` |
|
||||
| `register` | `auth` | 화면 | `_user_base` |
|
||||
| `reset_password` | `auth` | 화면 | `_user_base` |
|
||||
| `boards` | `board` | 화면 | `_user_base` |
|
||||
| `form` | `board` | 화면 | `_user_base` |
|
||||
| `index` | `board` | 화면 | `_user_base` |
|
||||
| `popular` | `board` | 화면 | `_user_base` |
|
||||
| `show` | `board` | 화면 | `_user_base` |
|
||||
| `401` | `errors` | 화면 | `_user_base` |
|
||||
| `403` | `errors` | 화면 | `_user_base` |
|
||||
| `404` | `errors` | 화면 | `_user_base` |
|
||||
| `500` | `errors` | 화면 | `_user_base` |
|
||||
| `503` | `errors` | 화면 | `_user_base` |
|
||||
| `maintenance` | `errors` | 화면 | `_user_base` |
|
||||
| `home` | `(root)` | 화면 | `_user_base` |
|
||||
| `addresses` | `mypage` | 화면 | `_user_base` |
|
||||
| `board` | `mypage` | 화면 | `_user_base` |
|
||||
| `change-password` | `mypage` | 화면 | `_user_base` |
|
||||
| `inquiries` | `mypage` | 화면 | `_user_base` |
|
||||
| `mileage` | `mypage` | 화면 | `_user_base` |
|
||||
| `notifications` | `mypage` | 화면 | `_user_base` |
|
||||
| `orders` | `mypage` | 화면 | `_user_base` |
|
||||
| `show` | `mypage` | 화면 | `_user_base` |
|
||||
| `profile-edit` | `mypage` | 화면 | `_user_base` |
|
||||
| `profile` | `mypage` | 화면 | `_user_base` |
|
||||
| `wishlist` | `mypage` | 화면 | `_user_base` |
|
||||
| `show` | `page` | 화면 | `_user_base` |
|
||||
| `_identity_challenge_modal` | `partials` | partial | - |
|
||||
| `_modal_notification_delete_all_confirm` | `partials` | partial | - |
|
||||
| `_login_form` | `partials` | partial | - |
|
||||
| `_modal_privacy` | `partials` | partial | - |
|
||||
| `_modal_terms` | `partials` | partial | - |
|
||||
| `_redirect_if_logged_in` | `partials` | partial | - |
|
||||
| `_register_form` | `partials` | partial | - |
|
||||
| `_board_card` | `partials` | partial | - |
|
||||
| `_parent_post` | `partials` | partial | - |
|
||||
| `_password_verify_modal` | `partials` | partial | - |
|
||||
| `_post_form` | `partials` | partial | - |
|
||||
| `_type_renderer` | `partials` | partial | - |
|
||||
| `_admin_links` | `partials` | partial | - |
|
||||
| `_empty_states` | `partials` | partial | - |
|
||||
| `_loading_error` | `partials` | partial | - |
|
||||
| `_type_renderer` | `partials` | partial | - |
|
||||
| `_write_button` | `partials` | partial | - |
|
||||
| `_popular_item` | `partials` | partial | - |
|
||||
| `_popular_list` | `partials` | partial | - |
|
||||
| `_comment_input` | `partials` | partial | - |
|
||||
| `_comment_item` | `partials` | partial | - |
|
||||
| `_comment_section` | `partials` | partial | - |
|
||||
| `_navigation` | `partials` | partial | - |
|
||||
| `_post_attachments` | `partials` | partial | - |
|
||||
| `_reply_section` | `partials` | partial | - |
|
||||
| `_type_renderer` | `partials` | partial | - |
|
||||
| `_modal_delete` | `partials` | partial | - |
|
||||
| `_modal_report` | `partials` | partial | - |
|
||||
| `_password_verify_modal` | `partials` | partial | - |
|
||||
| `form` | `partials` | 화면 | - |
|
||||
| `index` | `partials` | 화면 | - |
|
||||
| `show` | `partials` | 화면 | - |
|
||||
| `index` | `partials` | 화면 | - |
|
||||
| `index` | `partials` | 화면 | - |
|
||||
| `_currency_selector` | `partials` | partial | - |
|
||||
| `_board_summary` | `partials` | partial | - |
|
||||
| `_community_guide` | `partials` | partial | - |
|
||||
| `_popular_boards` | `partials` | partial | - |
|
||||
| `_recent_posts` | `partials` | partial | - |
|
||||
| `_shop_promo` | `partials` | partial | - |
|
||||
| `_stat_card_boards` | `partials` | partial | - |
|
||||
| `_stat_card_comments` | `partials` | partial | - |
|
||||
| `_stat_card_posts` | `partials` | partial | - |
|
||||
| `_stat_card_users` | `partials` | partial | - |
|
||||
| `_welcome_card` | `partials` | partial | - |
|
||||
| `_tab_navigation` | `partials` | partial | - |
|
||||
| `_list` | `partials` | partial | - |
|
||||
| `_modal_address` | `partials` | partial | - |
|
||||
| `_modal_confirm_delete` | `partials` | partial | - |
|
||||
| `_modal_confirm_overwrite` | `partials` | partial | - |
|
||||
| `_list` | `partials` | partial | - |
|
||||
| `_my_comments` | `partials` | partial | - |
|
||||
| `_my_posts` | `partials` | partial | - |
|
||||
| `_list` | `partials` | partial | - |
|
||||
| `_list` | `partials` | partial | - |
|
||||
| `_list` | `partials` | partial | - |
|
||||
| `_history` | `partials` | partial | - |
|
||||
| `_items` | `partials` | partial | - |
|
||||
| `_list` | `partials` | partial | - |
|
||||
| `_modal_cancel` | `partials` | partial | - |
|
||||
| `_modal_change_address` | `partials` | partial | - |
|
||||
| `_modal_confirm_purchase` | `partials` | partial | - |
|
||||
| `_modal_write_review` | `partials` | partial | - |
|
||||
| `_orderer` | `partials` | partial | - |
|
||||
| `_payment` | `partials` | partial | - |
|
||||
| `_shipping` | `partials` | partial | - |
|
||||
| `_status_header` | `partials` | partial | - |
|
||||
| `_edit` | `partials` | partial | - |
|
||||
| `_modal_withdraw` | `partials` | partial | - |
|
||||
| `_password_verify_section` | `partials` | partial | - |
|
||||
| `_view` | `partials` | partial | - |
|
||||
| `_list` | `partials` | partial | - |
|
||||
| `_search_filters` | `partials` | partial | - |
|
||||
| `_search_input` | `partials` | partial | - |
|
||||
| `_search_results` | `partials` | partial | - |
|
||||
| `_search_states` | `partials` | partial | - |
|
||||
| `_search_tabs` | `partials` | partial | - |
|
||||
| `_section` | `partials` | partial | - |
|
||||
| `_list` | `partials` | partial | - |
|
||||
| `_section` | `partials` | partial | - |
|
||||
| `_item_card` | `partials` | partial | - |
|
||||
| `_item_list` | `partials` | partial | - |
|
||||
| `_section` | `partials` | partial | - |
|
||||
| `_cart_item` | `partials` | partial | - |
|
||||
| `_cart_list` | `partials` | partial | - |
|
||||
| `_cart_summary` | `partials` | partial | - |
|
||||
| `_checkout_discount` | `partials` | partial | - |
|
||||
| `_checkout_items` | `partials` | partial | - |
|
||||
| `_checkout_mileage` | `partials` | partial | - |
|
||||
| `_checkout_orderer` | `partials` | partial | - |
|
||||
| `_checkout_payment` | `partials` | partial | - |
|
||||
| `_checkout_shipping` | `partials` | partial | - |
|
||||
| `_checkout_summary` | `partials` | partial | - |
|
||||
| `_modal_address_manage` | `partials` | partial | - |
|
||||
| `_modal_cart_delete_confirm` | `partials` | partial | - |
|
||||
| `_modal_cart_option_change` | `partials` | partial | - |
|
||||
| `_modal_cart_unavailable` | `partials` | partial | - |
|
||||
| `_modal_coupon_download` | `partials` | partial | - |
|
||||
| `_modal_exclusive_coupon_confirm` | `partials` | partial | - |
|
||||
| `_modal_temp_order_not_found` | `partials` | partial | - |
|
||||
| `_product_purchase_card` | `partials` | partial | - |
|
||||
| `_admin_edit_link` | `partials` | partial | - |
|
||||
| `_header` | `partials` | partial | - |
|
||||
| `_info_summary` | `partials` | partial | - |
|
||||
| `_modal_cart_added` | `partials` | partial | - |
|
||||
| `_modal_coupon_download_confirm` | `partials` | partial | - |
|
||||
| `_modal_inquiry_delete` | `partials` | partial | - |
|
||||
| `_modal_login_required` | `partials` | partial | - |
|
||||
| `_modal_qna_reply` | `partials` | partial | - |
|
||||
| `_modal_qna_write` | `partials` | partial | - |
|
||||
| `_modal_review_image` | `partials` | partial | - |
|
||||
| `_price_mobile` | `partials` | partial | - |
|
||||
| `_purchase_card` | `partials` | partial | - |
|
||||
| `_review_avatar` | `partials` | partial | - |
|
||||
| `_tab_detail` | `partials` | partial | - |
|
||||
| `_tab_qna` | `partials` | partial | - |
|
||||
| `_tab_reviews` | `partials` | partial | - |
|
||||
| `_category_breadcrumb` | `partials` | partial | - |
|
||||
| `_category_filter` | `partials` | partial | - |
|
||||
| `_new_products` | `partials` | partial | - |
|
||||
| `_popular_products` | `partials` | partial | - |
|
||||
| `_product_grid` | `partials` | partial | - |
|
||||
| `_recent_products` | `partials` | partial | - |
|
||||
| `_search_filter_bar` | `partials` | partial | - |
|
||||
| `index` | `search` | 화면 | `_user_base` |
|
||||
| `cart` | `shop` | 화면 | `_user_base` |
|
||||
| `category` | `shop` | 화면 | `_user_base` |
|
||||
| `checkout` | `shop` | 화면 | `_user_base` |
|
||||
| `guest_order_form` | `shop` | 화면 | `_user_base` |
|
||||
| `guest_order_show` | `shop` | 화면 | `_user_base` |
|
||||
| `index` | `shop` | 화면 | `_user_base` |
|
||||
| `order_complete` | `shop` | 화면 | `_user_base` |
|
||||
| `reorder` | `shop` | 화면 | `_user_base` |
|
||||
| `show` | `shop` | 화면 | `_user_base` |
|
||||
| `posts` | `users` | 화면 | `_user_base` |
|
||||
| `show` | `users` | 화면 | `_user_base` |
|
||||
<!-- @generated:layouts END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
166개 중 **124개가 partial** 입니다(화면 42). 이 비율이 이 템플릿의 구조를 그대로 보여줍니다 —
|
||||
화면 하나가 여러 조각으로 나뉘어 있고, 조각은 여러 화면이 공유합니다. 화면 하나를 고칠 때는
|
||||
그 화면 이름의 partials 디렉토리를 함께 열어야 전체가 보입니다.
|
||||
|
||||
그룹은 방문자 여정과 1:1 입니다 — `auth`(로그인·가입·비밀번호·본인인증) · `board`(게시판) ·
|
||||
`shop`(상점) · `mypage`(마이페이지) · `page`(단일 문서) · `search` · `users` · `errors`.
|
||||
`(root)` 둘은 `_user_base`(모든 화면의 베이스)와 `home` 입니다.
|
||||
|
||||
**모든 화면이 `_user_base` 를 상속합니다.** 헤더·푸터·모바일 네비·토스트·모달 호스트가 거기
|
||||
있으므로, `extends` 없이 독립 레이아웃을 새로 만들면 그 화면에서는 `toast` 나 `openModal` 이
|
||||
성공으로 기록되지만 **화면에는 아무것도 나타나지 않습니다**(호스트 컴포넌트가 마운트되지 않아서).
|
||||
새 화면은 특별한 이유가 없는 한 `_user_base` 를 상속합니다.
|
||||
|
||||
`errors/` 6종(401·403·404·500·503·maintenance)은 코어가 오류 상황에서 직접 부릅니다. 401 은
|
||||
**로그인 리다이렉트를 여기서 구현하지 않습니다** — 코어 `TemplateApp.showRouteError` 가드가
|
||||
처리하므로, 이 레이아웃에서 다시 이동시키면 이중 리다이렉트가 됩니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 라우트 매핑
|
||||
|
||||
<!-- @generated:layout-map START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 경로 | 레이아웃 | 이름 |
|
||||
|---|---|---|
|
||||
| `/` | `home` | - |
|
||||
| `/login` | `auth/login` | - |
|
||||
| `/register` | `auth/register` | - |
|
||||
| `/forgot-password` | `auth/forgot_password` | - |
|
||||
| `/reset-password` | `auth/reset_password` | - |
|
||||
| `/identity/challenge` | `auth/identity_challenge` | - |
|
||||
| `/boards` | `board/boards` | - |
|
||||
| `/boards/popular` | `board/popular` | - |
|
||||
| `/board/:slug` | `board/index` | - |
|
||||
| `/board/:slug/write` | `board/form` | - |
|
||||
| `/board/:slug/:id` | `board/show` | - |
|
||||
| `/board/:slug/:id/edit` | `board/form` | - |
|
||||
| `/{{_global.modules?.['sirsoft-ecommerce']?.basic_info?.no_route ? '' : (_global.modules?.['sirsoft-ecommerce']?.basic_info?.route_path ?? 'shop')}}/products` | `shop/index` | - |
|
||||
| `/{{_global.modules?.['sirsoft-ecommerce']?.basic_info?.no_route ? '' : (_global.modules?.['sirsoft-ecommerce']?.basic_info?.route_path ?? 'shop')}}/category/:slug` | `shop/category` | - |
|
||||
| `/{{_global.modules?.['sirsoft-ecommerce']?.basic_info?.no_route ? '' : (_global.modules?.['sirsoft-ecommerce']?.basic_info?.route_path ?? 'shop')}}/products/:product_code` | `shop/show` | - |
|
||||
| `/{{_global.modules?.['sirsoft-ecommerce']?.basic_info?.no_route ? '' : (_global.modules?.['sirsoft-ecommerce']?.basic_info?.route_path ?? 'shop')}}/cart` | `shop/cart` | - |
|
||||
| `/{{_global.modules?.['sirsoft-ecommerce']?.basic_info?.no_route ? '' : (_global.modules?.['sirsoft-ecommerce']?.basic_info?.route_path ?? 'shop')}}/checkout` | `shop/checkout` | - |
|
||||
| `/{{_global.modules?.['sirsoft-ecommerce']?.basic_info?.no_route ? '' : (_global.modules?.['sirsoft-ecommerce']?.basic_info?.route_path ?? 'shop')}}/orders/:id/complete` | `shop/order_complete` | - |
|
||||
| `/{{_global.modules?.['sirsoft-ecommerce']?.basic_info?.no_route ? '' : (_global.modules?.['sirsoft-ecommerce']?.basic_info?.route_path ?? 'shop')}}/guest/orders` | `shop/guest_order_form` | - |
|
||||
| `/{{_global.modules?.['sirsoft-ecommerce']?.basic_info?.no_route ? '' : (_global.modules?.['sirsoft-ecommerce']?.basic_info?.route_path ?? 'shop')}}/guest/orders/:order_number` | `shop/guest_order_show` | - |
|
||||
| `/{{_global.modules?.['sirsoft-ecommerce']?.basic_info?.no_route ? '' : (_global.modules?.['sirsoft-ecommerce']?.basic_info?.route_path ?? 'shop')}}/reorder/:id` | `shop/reorder` | - |
|
||||
| `/mypage` | `-` | - |
|
||||
| `/mypage/profile` | `mypage/profile` | - |
|
||||
| `/mypage/profile/edit` | `mypage/profile-edit` | - |
|
||||
| `/mypage/change-password` | `mypage/change-password` | - |
|
||||
| `/mypage/orders` | `mypage/orders` | - |
|
||||
| `/mypage/orders/:order_number` | `mypage/orders/show` | - |
|
||||
| `/mypage/mileage` | `mypage/mileage` | - |
|
||||
| `/mypage/wishlist` | `mypage/wishlist` | - |
|
||||
| `/mypage/addresses` | `mypage/addresses` | - |
|
||||
| `/mypage/notifications` | `mypage/notifications` | - |
|
||||
| `/mypage/board` | `mypage/board` | - |
|
||||
| `/mypage/inquiries` | `mypage/inquiries` | - |
|
||||
| `/users/:userId` | `users/show` | - |
|
||||
| `/users/:userId/posts` | `users/posts` | - |
|
||||
| `/page/:slug` | `page/show` | - |
|
||||
| `/search` | `search/index` | - |
|
||||
| `/404` | `errors/404` | - |
|
||||
| `/403` | `errors/403` | - |
|
||||
| `/500` | `errors/500` | - |
|
||||
<!-- @generated:layout-map END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
상점 경로 9개만 **표현식**입니다. 운영자가 이커머스 환경설정에서 상점 경로(`route_path`,
|
||||
기본 `shop`)를 바꾸거나 아예 루트로 두면(`no_route`), 그 설정이 라우트 문자열에 그대로
|
||||
반영됩니다 — `routes.json` 이 정적 파일이 아니라 **표현식을 담을 수 있다**는 것이 이 템플릿의
|
||||
전제입니다.
|
||||
|
||||
그래서 상점 라우트를 손볼 때는 세 경우를 함께 생각해야 합니다: 기본(`/shop/...`) · 운영자
|
||||
지정 경로(`/store/...`) · 루트 배치(`/products`). 문자열을 하드코딩하면 뒤의 둘이 조용히
|
||||
깨집니다.
|
||||
|
||||
`/mypage` 자체는 레이아웃이 `-` 입니다 — 진입하면 하위 탭 중 하나로 넘기는 자리이며 자기
|
||||
화면을 갖지 않습니다.
|
||||
|
||||
라우트를 바꾸면 `php artisan template:update sirsoft-basic --force` 로 반영합니다. 빌드는
|
||||
필요 없습니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 확장 오버라이드
|
||||
|
||||
<!-- @generated:template-overrides START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 대상 | 설명 |
|
||||
|---|---|
|
||||
| `extensions/sirsoft-daum_postcode/user-address-search.json` | 모듈/플러그인 확장 조각을 대체하는 오버라이드 |
|
||||
<!-- @generated:template-overrides END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
오버라이드는 **확장이 제공한 조각을 이 템플릿의 것으로 갈아 끼우는** 장치입니다. 지금은 하나
|
||||
있습니다 — `sirsoft-daum_postcode` 의 주소 검색 조각.
|
||||
|
||||
플러그인이 제공하는 원본 조각은 이커머스 관리자 화면에 맞춰져 있어, 방문자 화면의 배송지 입력
|
||||
디자인과 어긋납니다. 조각을 고치는 대신 **템플릿이 자기 버전을 얹는** 것이 이 방향입니다 —
|
||||
플러그인을 업데이트해도 이 오버라이드는 남고, 다른 템플릿은 원본을 그대로 씁니다.
|
||||
|
||||
그 대가로 **원본이 바뀌면 이 사본은 따라가지 않습니다.** 플러그인이 조각의 동작(핸들러 이름·
|
||||
필드 계약)을 바꾸면 오버라이드만 옛 계약을 붙들고 있게 되고, 증상은 "주소 검색 버튼이
|
||||
무반응" 으로만 나타납니다. 해당 플러그인을 업그레이드한 뒤에는 이 오버라이드를 함께 확인합니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 이관 원문 상세
|
||||
|
||||
> 아래는 코어 `docs/frontend/templates/sirsoft-basic/layouts.md` 에 있던 원문을 이 문서로 옮긴 것입니다(#601). 이관 시점 그대로
|
||||
> 보존하되, **코드가 SSoT 인 값과 어긋나는 부분에는 정정 주석**을 달았습니다.
|
||||
|
||||
### sirsoft-basic 레이아웃
|
||||
|
||||
> **템플릿 식별자**: `sirsoft-basic` (type: user)
|
||||
> **관련 문서**: [컴포넌트](components.md) | [핸들러](handlers.md) | [레이아웃 JSON 스키마](../../../../docs/frontend/layout-json.md)
|
||||
|
||||
> **정정(#601)**: 아래 페이지 맵·개수는 이관 시점 값입니다. 실측 총계와 그룹별 분포는 위
|
||||
> 「레이아웃 목록」 블록이 SSoT 이며, 이관 이후 추가·삭제된 레이아웃이 있을 수 있습니다.
|
||||
|
||||
---
|
||||
|
||||
### TL;DR (5초 요약)
|
||||
|
||||
```text
|
||||
1. 베이스: _user_base.json (헤더 + 푸터 + 모바일 네비 + 콘텐츠 슬롯)
|
||||
2. 코어 페이지 30개+: 인증, 게시판, 쇼핑몰, 마이페이지, 검색, 프로필
|
||||
3. Partial 100개+: 폼, 모달, 탭, 카드, 목록, 필터, 타입별 렌더러
|
||||
4. 에러 페이지 6개: 401, 403, 404, 500, 503, maintenance
|
||||
5. 패턴: 게시판(타입별 렌더러), 쇼핑몰(장바구니/결제 플로우), 마이페이지(탭 네비게이션)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 목차
|
||||
|
||||
1. [페이지 맵 (트리 구조)](#페이지-맵-트리-구조)
|
||||
2. [카테고리별 가이드](#카테고리별-가이드)
|
||||
- [인증 페이지 패턴](#인증-페이지-패턴)
|
||||
- [게시판 패턴](#게시판-패턴)
|
||||
- [쇼핑몰 패턴](#쇼핑몰-패턴)
|
||||
- [마이페이지 패턴](#마이페이지-패턴)
|
||||
- [검색 패턴](#검색-패턴)
|
||||
- [기타 페이지 패턴](#기타-페이지-패턴)
|
||||
- [에러 페이지 패턴](#에러-페이지-패턴)
|
||||
|
||||
---
|
||||
|
||||
### 페이지 맵 (트리 구조)
|
||||
|
||||
```text
|
||||
_user_base.json (베이스 레이아웃)
|
||||
│
|
||||
├── home.json (홈/대시보드)
|
||||
│ └── partials/home/
|
||||
│ ├── _welcome_card.json (환영 카드)
|
||||
│ ├── _stat_card_users.json (통계: 사용자)
|
||||
│ ├── _stat_card_posts.json (통계: 게시글)
|
||||
│ ├── _stat_card_comments.json (통계: 댓글)
|
||||
│ ├── _stat_card_boards.json (통계: 게시판)
|
||||
│ ├── _recent_posts.json (최근 게시글)
|
||||
│ ├── _popular_boards.json (인기 게시판)
|
||||
│ ├── _board_summary.json (게시판 요약)
|
||||
│ ├── _community_guide.json (커뮤니티 가이드)
|
||||
│ └── _shop_promo.json (쇼핑 프로모션)
|
||||
│
|
||||
├── 인증 (auth/)
|
||||
│ ├── login.json (로그인)
|
||||
│ ├── register.json (회원가입)
|
||||
│ ├── forgot_password.json (비밀번호 찾기)
|
||||
│ ├── reset_password.json (비밀번호 재설정)
|
||||
│ └── partials/auth/
|
||||
│ ├── _login_form.json (로그인 폼)
|
||||
│ ├── _register_form.json (회원가입 폼)
|
||||
│ ├── _redirect_if_logged_in.json (로그인 상태 리다이렉트)
|
||||
│ ├── _modal_terms.json (이용약관 모달)
|
||||
│ └── _modal_privacy.json (개인정보 처리방침 모달)
|
||||
│
|
||||
├── 게시판 (board/)
|
||||
│ ├── boards.json (게시판 목록)
|
||||
│ │ └── partials/board/boards/
|
||||
│ │ └── _board_card.json (게시판 카드)
|
||||
│ ├── index.json (게시글 목록)
|
||||
│ │ └── partials/board/index/
|
||||
│ │ ├── _type_renderer.json (타입별 렌더러 분기)
|
||||
│ │ ├── _empty_states.json (빈 상태)
|
||||
│ │ ├── _loading_error.json (로딩/에러)
|
||||
│ │ └── _write_button.json (글쓰기 버튼)
|
||||
│ ├── show.json (게시글 상세)
|
||||
│ │ └── partials/board/show/
|
||||
│ │ ├── _type_renderer.json (타입별 렌더러 분기)
|
||||
│ │ ├── _comment_section.json (댓글 섹션)
|
||||
│ │ ├── _comment_item.json (댓글 아이템)
|
||||
│ │ ├── _comment_input.json (댓글 입력)
|
||||
│ │ ├── _reply_section.json (답글 섹션)
|
||||
│ │ ├── _navigation.json (이전글/다음글)
|
||||
│ │ ├── _post_attachments.json (첨부파일)
|
||||
│ │ └── modals/
|
||||
│ │ ├── _modal_delete.json (삭제 확인)
|
||||
│ │ ├── _modal_report.json (신고)
|
||||
│ │ └── _password_verify_modal.json (비밀번호 확인)
|
||||
│ ├── form.json (게시글 작성/수정)
|
||||
│ │ └── partials/board/form/
|
||||
│ │ ├── _post_form.json (게시글 폼)
|
||||
│ │ ├── _parent_post.json (원글 표시)
|
||||
│ │ ├── _type_renderer.json (타입별 렌더러 분기)
|
||||
│ │ └── _password_verify_modal.json (비밀번호 확인)
|
||||
│ ├── popular.json (인기글)
|
||||
│ │ └── partials/board/popular/
|
||||
│ │ ├── _popular_list.json (인기글 리스트)
|
||||
│ │ └── _popular_item.json (인기글 아이템)
|
||||
│ └── partials/board/types/ (게시판 타입별 부분 레이아웃)
|
||||
│ ├── basic/
|
||||
│ │ ├── index.json (기본 목록)
|
||||
│ │ ├── show.json (기본 상세)
|
||||
│ │ └── form.json (기본 폼)
|
||||
│ ├── card/
|
||||
│ │ └── index.json (카드형 목록)
|
||||
│ └── gallery/
|
||||
│ └── index.json (갤러리형 목록)
|
||||
│
|
||||
├── 쇼핑몰 (shop/)
|
||||
│ ├── index.json (상품 목록/메인)
|
||||
│ │ └── partials/shop/list/
|
||||
│ │ ├── _product_grid.json (상품 그리드)
|
||||
│ │ ├── _popular_products.json (인기 상품)
|
||||
│ │ ├── _new_products.json (신상품)
|
||||
│ │ ├── _recent_products.json (최근 본 상품)
|
||||
│ │ ├── _category_filter.json (카테고리 필터)
|
||||
│ │ ├── _category_breadcrumb.json (카테고리 경로)
|
||||
│ │ └── _search_filter_bar.json (검색/필터 바)
|
||||
│ ├── category.json (카테고리 상품)
|
||||
│ ├── show.json (상품 상세)
|
||||
│ │ └── partials/shop/detail/
|
||||
│ │ ├── _header.json (상품 헤더)
|
||||
│ │ ├── _info_summary.json (상품 정보 요약)
|
||||
│ │ ├── _purchase_card.json (구매 카드)
|
||||
│ │ ├── _price_mobile.json (모바일 가격)
|
||||
│ │ ├── _tab_detail.json (상세 탭)
|
||||
│ │ ├── _tab_reviews.json (리뷰 탭)
|
||||
│ │ ├── _tab_qna.json (Q&A 탭)
|
||||
│ │ ├── _review_avatar.json (리뷰 아바타)
|
||||
│ │ ├── _modal_cart_added.json (장바구니 추가 확인)
|
||||
│ │ ├── _modal_coupon_download_confirm.json (쿠폰 다운로드 확인)
|
||||
│ │ ├── _modal_login_required.json (로그인 필요)
|
||||
│ │ └── _modal_review_image.json (리뷰 이미지 확대)
|
||||
│ ├── cart.json (장바구니)
|
||||
│ │ └── partials/shop/
|
||||
│ │ ├── _cart_list.json (장바구니 목록)
|
||||
│ │ ├── _cart_item.json (장바구니 아이템)
|
||||
│ │ ├── _cart_summary.json (장바구니 요약)
|
||||
│ │ ├── _product_purchase_card.json (구매 카드)
|
||||
│ │ ├── _modal_cart_delete_confirm.json (삭제 확인)
|
||||
│ │ ├── _modal_cart_option_change.json (옵션 변경)
|
||||
│ │ ├── _modal_cart_unavailable.json (품절 안내)
|
||||
│ │ └── _modal_temp_order_not_found.json (임시주문 없음)
|
||||
│ ├── checkout.json (결제)
|
||||
│ │ └── partials/shop/
|
||||
│ │ ├── _checkout_orderer.json (주문자 정보)
|
||||
│ │ ├── _checkout_shipping.json (배송 정보)
|
||||
│ │ ├── _checkout_items.json (주문 상품)
|
||||
│ │ ├── _checkout_discount.json (할인/쿠폰)
|
||||
│ │ ├── _checkout_mileage.json (마일리지)
|
||||
│ │ ├── _checkout_payment.json (결제 수단)
|
||||
│ │ ├── _checkout_summary.json (주문 요약)
|
||||
│ │ ├── _modal_address_manage.json (배송지 관리)
|
||||
│ │ ├── _modal_coupon_download.json (쿠폰 다운로드)
|
||||
│ │ └── _modal_exclusive_coupon_confirm.json (전용 쿠폰 확인)
|
||||
│ └── order_complete.json (주문 완료)
|
||||
│
|
||||
├── 마이페이지 (mypage/)
|
||||
│ ├── profile.json (프로필 보기)
|
||||
│ ├── profile-edit.json (프로필 수정)
|
||||
│ │ └── partials/mypage/profile/
|
||||
│ │ ├── _view.json (프로필 보기 섹션)
|
||||
│ │ ├── _edit.json (프로필 수정 섹션)
|
||||
│ │ ├── _password_verify_section.json (비밀번호 확인)
|
||||
│ │ └── _modal_withdraw.json (회원 탈퇴)
|
||||
│ ├── orders.json (주문 내역)
|
||||
│ │ └── partials/mypage/orders/
|
||||
│ │ └── _list.json (주문 목록)
|
||||
│ ├── orders/show.json (주문 상세)
|
||||
│ │ └── partials/mypage/orders/
|
||||
│ │ ├── _status_header.json (주문 상태 헤더)
|
||||
│ │ ├── _items.json (주문 상품)
|
||||
│ │ ├── _shipping.json (배송 정보)
|
||||
│ │ ├── _payment.json (결제 정보)
|
||||
│ │ ├── _history.json (주문 이력)
|
||||
│ │ ├── _modal_cancel.json (주문 취소)
|
||||
│ │ └── _modal_change_address.json (배송지 변경)
|
||||
│ ├── board.json (내 게시글/댓글)
|
||||
│ │ └── partials/mypage/board/
|
||||
│ │ ├── _list.json (탭 목록)
|
||||
│ │ ├── _my_posts.json (내 게시글)
|
||||
│ │ └── _my_comments.json (내 댓글)
|
||||
│ ├── addresses.json (배송지 관리)
|
||||
│ │ └── partials/mypage/addresses/
|
||||
│ │ ├── _list.json (배송지 목록)
|
||||
│ │ ├── _modal_address.json (배송지 추가/수정)
|
||||
│ │ ├── _modal_confirm_delete.json (삭제 확인)
|
||||
│ │ └── _modal_confirm_overwrite.json (덮어쓰기 확인)
|
||||
│ ├── notifications.json (알림)
|
||||
│ │ └── partials/mypage/notifications/
|
||||
│ │ └── _list.json (알림 목록)
|
||||
│ ├── change-password.json (비밀번호 변경)
|
||||
│ ├── wishlist.json (위시리스트)
|
||||
│ │ └── partials/mypage/wishlist/
|
||||
│ │ └── _list.json (위시리스트 목록)
|
||||
│ ├── inquiries.json (상품 1:1 문의내역)
|
||||
│ │ └── partials/mypage/inquiries/
|
||||
│ │ └── _list.json (문의 목록)
|
||||
│ └── partials/mypage/
|
||||
│ └── _tab_navigation.json (마이페이지 공통 탭)
|
||||
│ hiddenTabIds: inquiries 탭은 sirsoft-ecommerce inquiry 설정 시에만 노출
|
||||
│
|
||||
├── 검색 (search/)
|
||||
│ └── index.json (통합 검색)
|
||||
│ └── partials/search/
|
||||
│ ├── _search_input.json (검색 입력)
|
||||
│ ├── _search_tabs.json (검색 탭)
|
||||
│ ├── _search_filters.json (검색 필터)
|
||||
│ ├── _search_results.json (검색 결과)
|
||||
│ ├── _search_states.json (검색 상태)
|
||||
│ ├── posts/
|
||||
│ │ ├── _section.json (게시글 결과 섹션)
|
||||
│ │ └── _list.json (게시글 결과 목록)
|
||||
│ ├── products/
|
||||
│ │ ├── _section.json (상품 결과 섹션)
|
||||
│ │ ├── _item_card.json (상품 카드)
|
||||
│ │ └── _item_list.json (상품 리스트)
|
||||
│ └── pages/
|
||||
│ └── _section.json (페이지 결과 섹션)
|
||||
│
|
||||
├── 사용자 (users/)
|
||||
│ ├── show.json (사용자 프로필)
|
||||
│ └── posts.json (사용자 게시글)
|
||||
│
|
||||
├── 페이지 (page/)
|
||||
│ └── show.json (정적 페이지)
|
||||
│
|
||||
├── 공통 Partial
|
||||
│ └── partials/common/
|
||||
│ └── _currency_selector.json (통화 선택기)
|
||||
│
|
||||
└── 에러 페이지 (errors/)
|
||||
├── 401.json (인증 필요)
|
||||
├── 403.json (접근 거부)
|
||||
├── 404.json (페이지 없음)
|
||||
├── 500.json (서버 오류)
|
||||
├── 503.json (서비스 불가)
|
||||
└── maintenance.json (점검 중)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 카테고리별 가이드
|
||||
|
||||
#### 인증 페이지 패턴
|
||||
|
||||
**대표**: `auth/login.json`, `auth/register.json`
|
||||
|
||||
**구성**:
|
||||
```text
|
||||
extends: _user_base
|
||||
slots.content:
|
||||
└── Flex (중앙 정렬)
|
||||
├── _redirect_if_logged_in.json (로그인 시 리다이렉트)
|
||||
└── Div (카드 스타일 래퍼)
|
||||
├── H2 (제목)
|
||||
├── partial: _login_form.json 또는 _register_form.json
|
||||
└── 하단 링크 (회원가입/로그인 전환, 비밀번호 찾기)
|
||||
```
|
||||
|
||||
**특수사항**:
|
||||
- `_redirect_if_logged_in.json`: 이미 로그인한 사용자를 홈으로 리다이렉트하는 partial
|
||||
- 로그인 폼은 `SocialLoginButtons` 포함 (소셜 로그인 지원)
|
||||
- 회원가입 폼은 `PasswordInput` 사용 (비밀번호 규칙 검증, 확인 일치)
|
||||
- 약관 동의 모달: `_modal_terms.json`, `_modal_privacy.json`
|
||||
- `blur_until_loaded`: 현재 사용자 정보 로드 전까지 폼 블러 처리
|
||||
|
||||
**핸들러 패턴**:
|
||||
- `apiCall` — 로그인 (POST `/api/auth/login`), 회원가입 (POST `/api/auth/register`)
|
||||
- `navigate` — 성공 후 홈/대시보드로 이동
|
||||
- `setState` — 폼 에러, 로딩 상태
|
||||
|
||||
---
|
||||
|
||||
#### 게시판 패턴
|
||||
|
||||
**대표**: `board/index.json`, `board/show.json`, `board/form.json`
|
||||
|
||||
**구성**:
|
||||
```text
|
||||
extends: _user_base
|
||||
data_sources: [posts (게시글 목록/상세)]
|
||||
slots.content:
|
||||
└── Container
|
||||
├── 타입 렌더러 (_type_renderer.json)
|
||||
│ → 게시판 타입에 따라 분기:
|
||||
│ ├── basic/index.json (기본 테이블형)
|
||||
│ ├── card/index.json (카드형)
|
||||
│ └── gallery/index.json (갤러리형)
|
||||
├── Pagination
|
||||
└── 글쓰기 버튼 (_write_button.json)
|
||||
```
|
||||
|
||||
**data_sources**:
|
||||
```json
|
||||
{
|
||||
"id": "posts",
|
||||
"endpoint": "/api/modules/sirsoft-board/boards/{{route.slug}}/posts",
|
||||
"method": "GET",
|
||||
"params": {
|
||||
"page": "{{query.page ?? 1}}",
|
||||
"search": "{{query.search ?? ''}}",
|
||||
"category": "{{query.category ?? ''}}"
|
||||
},
|
||||
"auto_fetch": true,
|
||||
"refetchOnMount": true,
|
||||
"errorHandling": {
|
||||
"403": { "handler": "showErrorPage", "params": { "target": "content" } }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**타입별 렌더러 시스템**:
|
||||
게시판은 `board_type` 필드에 따라 다른 레이아웃을 렌더링합니다:
|
||||
- `basic` — 테이블형 게시글 목록 (기본)
|
||||
- `card` — 카드 그리드형 목록
|
||||
- `gallery` — 이미지 갤러리형 목록
|
||||
|
||||
```text
|
||||
_type_renderer.json (조건 분기):
|
||||
├── if: board_type === 'basic' → partial: types/basic/index.json
|
||||
├── if: board_type === 'card' → partial: types/card/index.json
|
||||
└── if: board_type === 'gallery' → partial: types/gallery/index.json
|
||||
```
|
||||
|
||||
**게시글 상세 구성**:
|
||||
```text
|
||||
show.json:
|
||||
└── _type_renderer.json → types/basic/show.json
|
||||
├── 게시글 본문 (HtmlContent)
|
||||
├── _post_attachments.json (첨부파일)
|
||||
├── PostReactions (리액션)
|
||||
├── _comment_section.json
|
||||
│ ├── _comment_input.json (댓글 입력)
|
||||
│ └── _comment_item.json (댓글 아이템, iteration)
|
||||
│ └── _reply_section.json (답글)
|
||||
├── _navigation.json (이전/다음 글)
|
||||
└── modals: 삭제, 신고, 비밀번호 확인
|
||||
```
|
||||
|
||||
**게시글 작성/수정 구성**:
|
||||
```text
|
||||
form.json:
|
||||
├── _parent_post.json (답글 시 원글 표시)
|
||||
└── _post_form.json
|
||||
├── Input (제목)
|
||||
├── Select (카테고리)
|
||||
├── HtmlEditor (본문)
|
||||
├── FileUploader (첨부파일)
|
||||
├── Checkbox (공지, 비밀글 등)
|
||||
└── Button (저장/취소)
|
||||
```
|
||||
|
||||
**핸들러 패턴**:
|
||||
- `apiCall` — CRUD, 댓글 작성/삭제, 리액션, 신고
|
||||
- `navigate` — 게시판 이동, 글 상세/목록 이동
|
||||
- `setState` — 검색, 필터, 페이지네이션, 댓글 접기/펼치기
|
||||
- `replaceUrl` — 페이지네이션 시 URL만 변경
|
||||
|
||||
---
|
||||
|
||||
#### 쇼핑몰 패턴
|
||||
|
||||
**대표**: `shop/index.json`, `shop/show.json`, `shop/cart.json`, `shop/checkout.json`
|
||||
|
||||
##### 상품 목록 (shop/index.json)
|
||||
|
||||
```text
|
||||
extends: _user_base
|
||||
data_sources: [products (상품 API), categories (카테고리)]
|
||||
slots.content:
|
||||
└── Container
|
||||
├── _category_breadcrumb.json (경로)
|
||||
├── _search_filter_bar.json (검색/정렬)
|
||||
├── _category_filter.json (카테고리 사이드)
|
||||
├── _product_grid.json (상품 그리드, ProductCard iteration)
|
||||
├── _popular_products.json (인기 상품)
|
||||
├── _new_products.json (신상품)
|
||||
├── _recent_products.json (최근 본 상품)
|
||||
└── Pagination
|
||||
```
|
||||
|
||||
##### 상품 상세 (shop/show.json)
|
||||
|
||||
```text
|
||||
extends: _user_base
|
||||
init_actions: [최근 본 상품 localStorage 저장]
|
||||
data_sources: [product, reviews, popularProducts, relatedProducts]
|
||||
computed: [displayPrice, displayListPrice, ...]
|
||||
slots.content:
|
||||
└── ThreeColumnLayout
|
||||
├── left: _header.json (이미지 뷰어 — ProductImageViewer)
|
||||
├── center: _info_summary.json (상품 정보, 옵션 선택)
|
||||
└── right: _purchase_card.json (가격, 수량, 구매 버튼)
|
||||
└── _price_mobile.json (모바일 가격 — responsive portable)
|
||||
└── TabNavigation
|
||||
├── _tab_detail.json (상세 정보 — HtmlContent)
|
||||
├── _tab_reviews.json (리뷰 — iteration)
|
||||
└── _tab_qna.json (Q&A — iteration)
|
||||
└── modals: 장바구니 추가, 쿠폰 다운로드, 로그인 필요, 리뷰 이미지
|
||||
```
|
||||
|
||||
**SEO 특수사항**:
|
||||
- `meta.seo.structured_data`: Product, AggregateRating 스키마
|
||||
- `meta.seo.og`: 상품 이미지, 가격 정보 포함
|
||||
|
||||
**핸들러 패턴**:
|
||||
- `sirsoft-basic.addSelectedItemIfComplete` — 옵션 선택 완료 시 자동 추가
|
||||
- `sirsoft-basic.updateSelectedItemQuantity` — 수량 변경
|
||||
- `sirsoft-basic.getDisplayPrice` — 다중 통화 가격 표시
|
||||
- `apiCall` — 장바구니 추가, 리뷰 작성, 위시리스트 토글
|
||||
- `saveToLocalStorage` — 최근 본 상품 저장
|
||||
|
||||
##### 장바구니 (shop/cart.json)
|
||||
|
||||
```text
|
||||
extends: _user_base
|
||||
state: { selectedItems, allSelected, isCalculating, optionModal, deleteModal, ... }
|
||||
data_sources: [cartItems (POST /cart/query)]
|
||||
slots.content:
|
||||
└── Flex
|
||||
├── _cart_list.json (장바구니 목록)
|
||||
│ └── _cart_item.json (아이템, iteration)
|
||||
│ ├── Checkbox (선택)
|
||||
│ ├── 상품 정보/가격
|
||||
│ ├── QuantitySelector
|
||||
│ └── 삭제/옵션변경 버튼
|
||||
└── _cart_summary.json (주문 요약, 결제 버튼)
|
||||
└── modals: 삭제 확인, 옵션 변경, 품절 안내
|
||||
```
|
||||
|
||||
**특수사항**:
|
||||
- `auth_mode: "optional"`: 비회원도 장바구니 사용 가능 (X-Cart-Key 헤더)
|
||||
- `state` 섹션으로 모달/선택 상태 관리
|
||||
- 장바구니 핸들러: `toggleCartItemSelection`, `selectAllCartItems`, `recalculateCart`
|
||||
|
||||
##### 결제 (shop/checkout.json)
|
||||
|
||||
```text
|
||||
extends: _user_base
|
||||
data_sources: [tempOrder (임시 주문)]
|
||||
slots.content:
|
||||
└── Container
|
||||
├── _checkout_orderer.json (주문자 정보)
|
||||
├── _checkout_shipping.json (배송 정보)
|
||||
├── _checkout_items.json (주문 상품 목록)
|
||||
├── _checkout_discount.json (할인/쿠폰)
|
||||
├── _checkout_mileage.json (마일리지)
|
||||
├── _checkout_payment.json (결제 수단 선택)
|
||||
└── _checkout_summary.json (최종 요약 + 결제 버튼)
|
||||
└── modals: 배송지 관리, 쿠폰 다운로드, 전용 쿠폰 확인
|
||||
```
|
||||
|
||||
**핸들러 패턴**:
|
||||
- `apiCall` — 주문 생성, 배송지 조회/저장, 쿠폰 적용
|
||||
- `setState` — 배송지 선택, 결제 수단, 쿠폰/마일리지 계산
|
||||
|
||||
---
|
||||
|
||||
#### 마이페이지 패턴
|
||||
|
||||
**대표**: `mypage/profile.json`, `mypage/orders.json`
|
||||
|
||||
**구성**:
|
||||
```text
|
||||
extends: _user_base
|
||||
computed: { currentTab: "profile" }
|
||||
data_sources: [user (인증 사용자 정보)]
|
||||
transition_overlay: { target: "mypage_tab_content" }
|
||||
slots.content:
|
||||
└── Div
|
||||
├── _tab_navigation.json (공통 마이페이지 탭)
|
||||
│ → 프로필, 주문내역, 게시글, 배송지, 알림, 위시리스트
|
||||
└── 탭별 콘텐츠 영역
|
||||
```
|
||||
|
||||
**탭 구조**:
|
||||
각 마이페이지 하위 페이지는 동일한 `_tab_navigation.json`을 공유하되, `computed.currentTab`으로 활성 탭을 구분합니다.
|
||||
|
||||
| 탭 | 레이아웃 | Partial |
|
||||
|---|---------|---------|
|
||||
| 프로필 | `profile.json` | `_view.json` |
|
||||
| 프로필 수정 | `profile-edit.json` | `_edit.json`, `_password_verify_section.json`, `_modal_withdraw.json` |
|
||||
| 주문 내역 | `orders.json` | `_list.json` |
|
||||
| 주문 상세 | `orders/show.json` | `_status_header.json`, `_items.json`, `_shipping.json`, `_payment.json`, `_history.json` |
|
||||
| 내 게시글 | `board.json` | `_my_posts.json`, `_my_comments.json` |
|
||||
| 배송지 | `addresses.json` | `_list.json`, `_modal_address.json`, `_modal_confirm_delete.json` |
|
||||
| 알림 | `notifications.json` | `_list.json` |
|
||||
| 비밀번호 변경 | `change-password.json` | (인라인) |
|
||||
| 위시리스트 | `wishlist.json` | `_list.json` |
|
||||
| 상품 1:1 문의내역 | `inquiries.json` | `_list.json` (sirsoft-ecommerce 모듈 연동 시에만 탭 표시) |
|
||||
|
||||
**핸들러 패턴**:
|
||||
- `apiCall` — 프로필 수정, 주문 취소, 배송지 CRUD, 알림 읽음 처리
|
||||
- `navigate` — 탭 간 이동 (별도 페이지)
|
||||
- `setState` — 폼 상태, 모달 제어
|
||||
- `openModal`/`closeModal` — 탈퇴, 주소 편집, 삭제 확인
|
||||
|
||||
---
|
||||
|
||||
#### 검색 패턴
|
||||
|
||||
**대표**: `search/index.json`
|
||||
|
||||
**구성**:
|
||||
```text
|
||||
extends: _user_base
|
||||
state: { q: "" }
|
||||
init_actions: [setState — query 파라미터에서 검색어 추출]
|
||||
global_state: { searchActiveTab, searchBoardFilter, searchSortBy, searchPage }
|
||||
data_sources: [searchPosts, searchProducts, searchPages]
|
||||
slots.content:
|
||||
└── Container
|
||||
├── _search_input.json (검색 입력 — SearchBar)
|
||||
├── _search_tabs.json (탭: 전체/게시글/상품/페이지)
|
||||
├── _search_filters.json (정렬, 게시판 필터)
|
||||
├── _search_states.json (검색 전/로딩/결과없음 상태)
|
||||
└── _search_results.json (결과 표시)
|
||||
├── posts/_section.json → posts/_list.json
|
||||
├── products/_section.json → products/_item_card.json / _item_list.json
|
||||
└── pages/_section.json
|
||||
```
|
||||
|
||||
**특수사항**:
|
||||
- 통합 검색: 게시글 + 상품 + 페이지 동시 검색
|
||||
- `searchActiveTab`으로 탭 전환 시 해당 카테고리만 표시
|
||||
- 상품 결과는 카드/리스트 뷰 전환 지원
|
||||
- `global_state`로 검색 상태 유지 (탭, 필터, 정렬, 페이지)
|
||||
|
||||
**핸들러 패턴**:
|
||||
- `apiCall` — 검색 API (게시글, 상품, 페이지 별도)
|
||||
- `setState` — 탭 전환, 필터, 정렬
|
||||
- `replaceUrl` — 검색어/필터 변경 시 URL 갱신
|
||||
|
||||
---
|
||||
|
||||
#### 기타 페이지 패턴
|
||||
|
||||
##### 사용자 프로필 (users/show.json, users/posts.json)
|
||||
|
||||
```text
|
||||
extends: _user_base
|
||||
data_sources: [user (공개 프로필)]
|
||||
slots.content:
|
||||
└── Container
|
||||
├── Avatar + 사용자 정보
|
||||
└── 게시글 목록 (users/posts.json)
|
||||
```
|
||||
|
||||
##### 정적 페이지 (page/show.json)
|
||||
|
||||
```text
|
||||
extends: _user_base
|
||||
data_sources: [page (페이지 콘텐츠)]
|
||||
slots.content:
|
||||
└── Container
|
||||
└── HtmlContent (본문 렌더링)
|
||||
```
|
||||
|
||||
##### 홈 (home.json)
|
||||
|
||||
```text
|
||||
extends: _user_base
|
||||
data_sources: [stats, recent_posts, popular_boards]
|
||||
meta.seo: { structured_data: WebSite }
|
||||
slots.content:
|
||||
└── Container
|
||||
├── _welcome_card.json (환영 배너)
|
||||
├── Grid (통계 카드 4개)
|
||||
│ ├── _stat_card_users.json
|
||||
│ ├── _stat_card_posts.json
|
||||
│ ├── _stat_card_comments.json
|
||||
│ └── _stat_card_boards.json
|
||||
├── _recent_posts.json (최근 게시글)
|
||||
├── _popular_boards.json (인기 게시판)
|
||||
├── _board_summary.json (게시판 요약)
|
||||
├── _community_guide.json (가이드)
|
||||
└── _shop_promo.json (쇼핑 프로모션)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 에러 페이지 패턴
|
||||
|
||||
**대표**: `errors/404.json`
|
||||
|
||||
**구성**:
|
||||
```text
|
||||
(extends 없음 — 독립 레이아웃)
|
||||
components:
|
||||
└── Div (전체 화면 중앙 정렬)
|
||||
├── Icon (에러 아이콘)
|
||||
├── H1 (에러 코드)
|
||||
├── P (에러 메시지)
|
||||
└── Button (홈으로 이동)
|
||||
```
|
||||
|
||||
**핸들러 패턴**:
|
||||
- `navigate` — 홈으로 이동
|
||||
|
||||
---
|
||||
|
||||
### 베이스 레이아웃 구조
|
||||
|
||||
#### _user_base.json
|
||||
|
||||
모든 사용자 페이지의 공통 구조를 정의합니다.
|
||||
|
||||
```text
|
||||
_user_base.json
|
||||
├── globalHeaders: [X-Cart-Key 헤더 (이커머스, 인증 API)]
|
||||
├── transition_overlay: { style: "skeleton", target: "main_content_area" }
|
||||
├── init_actions: [initTheme, initCartKey, loadPreferredCurrency, setState(shopBase)]
|
||||
├── data_sources:
|
||||
│ ├── boards (게시판 메뉴 — progressive, fallback)
|
||||
│ ├── current_user (인증 사용자 — auth_required, suppress 401)
|
||||
│ └── cart (장바구니 카운트 — optional auth)
|
||||
├── components:
|
||||
│ ├── Toast (전역 알림)
|
||||
│ ├── PageTransitionIndicator
|
||||
│ ├── Div (min-h-screen flex flex-col)
|
||||
│ │ ├── mobile_overlay (모바일 메뉴 오버레이 — responsive portable)
|
||||
│ │ ├── MobileNav (모바일 네비게이션 드로어)
|
||||
│ │ ├── Header (사이트 헤더 — 로고, 메뉴, 검색, 사용자)
|
||||
│ │ ├── Div#main_content_area (콘텐츠 영역)
|
||||
│ │ │ └── slot: "content" (← 하위 레이아웃이 채움)
|
||||
│ │ └── Footer (사이트 푸터)
|
||||
│ └── PageTransitionBlur (전환 블러)
|
||||
```
|
||||
|
||||
**슬롯**:
|
||||
- `content` — 각 페이지의 메인 콘텐츠가 삽입되는 위치
|
||||
|
||||
**특수사항**:
|
||||
- `globalHeaders`: 이커머스 API에 `X-Cart-Key` 자동 첨부
|
||||
- `transition_overlay.style: "skeleton"`: 페이지 전환 시 PageSkeleton 표시
|
||||
- `responsive`: 모바일 오버레이/네비게이션은 `portable` breakpoint에서만 표시
|
||||
- `auth_mode: "optional"`: 비회원도 장바구니 카운트 조회 가능
|
||||
- `errorHandling.401.handler: "suppress"`: 비회원의 인증 API 401은 에러 전파 방지
|
||||
|
||||
---
|
||||
|
||||
### 관련 문서
|
||||
|
||||
- [sirsoft-basic 컴포넌트](components.md)
|
||||
- [sirsoft-basic 핸들러](handlers.md)
|
||||
- [sirsoft-admin_basic 레이아웃](../../sirsoft-admin_basic/docs/layouts.md)
|
||||
- [레이아웃 JSON 스키마](../../../../docs/frontend/layout-json.md)
|
||||
- [레이아웃 상속](../../../../docs/frontend/layout-json-inheritance.md)
|
||||
Reference in New Issue
Block a user