-
Notifications
You must be signed in to change notification settings - Fork 0
docs(frontend): 자가학습 온보딩 슬라이드 추가 #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
온보딩 자가학습 슬라이드를 위한 기본 컴포넌트: - Slide: 개별 슬라이드 래퍼 (StepCard 패턴 적용) - SlideContainer: 네비게이션 컨테이너 (키보드 좌/우 지원) - ProgressIndicator: 진행률 표시 (프로그레스 바 + 도트) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
온보딩 슬라이드에서 코드 예제 표시를 위한 CodeBlock 컴포넌트. 모노스페이스 폰트와 neutral 배경색으로 가독성 확보. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
/[lang]/onboarding 경로에 자가학습 슬라이드 페이지 추가. 5개 슬라이드 구조 (콘텐츠는 별도 커밋에서 추가 예정). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
5개 슬라이드 콘텐츠 추가: 1. 왜 이 구조인가 - Refine Data Provider 요구사항 설명 2. 프로젝트 계층 구조 - repositories, services, schemas, mocks 3. aioia-core 패턴 - BaseCrudRepository 사용 예제 4. 백엔드 응답 형식 - SingleItemResponse, PaginatedResponse 5. 체크리스트 - 새 파일 생성 전 확인사항 출처 표기: Repository 패턴(Martin Fowler, PoEAA) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
onboarding 네비게이션 UI 번역 키 추가: - title, subtitle: 페이지 제목 - previous, next, done: 네비게이션 버튼 - keyboardHint: 키보드 단축키 안내 지원 언어: en, es, id, ja, ko, th, tl, vi, zh Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- rounded → rounded-sm (eslint 규칙) - h-2 w-2 → size-2 (eslint 규칙) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
개선 사항: - 카드 가로 폭 확장 (max-w-[600px] → max-w-3xl) - 모든 출처 클릭 가능하게 (Refine 공식 문서, Martin Fowler PoEAA) - 프론트엔드/백엔드 계층 명시 + Frontend 배지 추가 - mocks/ 설명 수정: "Storybook/테스트용 Mock 데이터. 타입별 fixture 제공" - aioia-core 패키지 및 소스 코드 링크 추가 (npm, GitHub) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 온보딩 전용 layout.tsx 추가 (fixed 레이아웃으로 부모 제약 우회) - 부모 layout의 max-w-mobile(393px) 제약을 무시하고 전체 너비 사용 - page.tsx에 w-full 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
6개 슬라이드로 재구성:
1. 프로젝트 계층 구조 (프론트엔드/백엔드 공통)
2. aioia-core (프론트엔드) - @aioia/core, BaseCrudRepository
3. aioia-core (백엔드) - aioia-core (PyPI), BaseCrudRouter
4. Factory 패턴 - BaseRepositoryFactory, DI
5. 응답 형식 - { data: ... } 래퍼 (Admin 도구 Refine 호환)
6. 체크리스트
추가된 링크:
- aioia-core PyPI
- BaseRepositoryFactory 소스
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
신규 팀원을 위한 자가학습 온보딩 슬라이드 추가는 매우 좋은 아이디어입니다. 프로젝트의 아키텍처를 이해하는 데 큰 도움이 될 것입니다. 전반적으로 컴포넌트 구조와 구현이 잘 되어 있지만, 몇 가지 개선점을 제안합니다.
주요 피드백은 다음과 같습니다:
- 다국어(i18n) 미적용: 새로 추가된 컴포넌트들(
OnboardingPage,SlideContainer,ProgressIndicator)의 사용자 표시 텍스트가 대부분 하드코딩되어 있습니다. 이미 추가된 다국어 리소스를 활용하여react-i18next를 적용해야 합니다. - 스타일 가이드 및 규칙 준수: 일부 코드에서 AIdol 개발 가이드의 타이포그래피 규칙(77행)과 조건부 클래스 관리 규칙(64행)을 위반한 부분이 발견되었으며, 스타일 일관성 관련 규칙에 따라 디자인 드래프트에 맞춰 통일해야 합니다.
- 리팩토링 기회:
OnboardingPage의 슬라이드 렌더링 로직은 데이터 기반 접근 방식으로 리팩토링하여 유지보수성을 높일 수 있습니다.
자세한 내용은 각 파일에 대한 개별 코멘트를 참고해 주세요.
| <h1 className="text-headline-s text-base-content"> | ||
| 프로젝트 아키텍처 가이드 | ||
| </h1> | ||
| <p className="text-body-m text-base-content/60"> | ||
| 자가학습 온보딩 슬라이드 | ||
| </p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
페이지 제목과 부제목이 한국어로 하드코딩되어 있습니다. 이 컴포넌트는 "use client"로 선언되어 있으므로 react-i18next의 useTranslation 훅을 사용하여 다국어를 지원해야 합니다. ko/aidol.json에 onboarding.title과 onboarding.subtitle 키가 이미 추가되어 있습니다.
슬라이드 내부의 모든 텍스트(제목, 본문 등)도 동일하게 다국어 처리가 필요합니다. 이는 AIdol 개발 가이드의 다국어 지원 원칙(89행)에 해당합니다.
import { useTranslation } from "react-i18next";
// ...
export default function OnboardingPage(...) {
const { t } = useTranslation("aidol");
// ...
<h1 className="text-headline-s text-base-content">
{t("onboarding.title")}
</h1>
<p className="text-body-m text-base-content/60">
{t("onboarding.subtitle")}
</p>
| Previous | ||
| </button> | ||
| <button | ||
| type="button" | ||
| onClick={goToNext} | ||
| disabled={isLastSlide} | ||
| className="btn btn-primary btn-sm flex-1" | ||
| > | ||
| {isLastSlide ? "Done" : "Next"} | ||
| </button> | ||
| </div> | ||
|
|
||
| {/* Keyboard hint */} | ||
| <p className="text-body-s text-base-content/40 text-center"> | ||
| Use ← → arrow keys to navigate |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
네비게이션 버튼의 텍스트("Previous", "Next", "Done")와 키보드 사용 안내 문구가 영어로 하드코딩되어 있습니다. 이 문자열들은 이미 aidol.json 파일에 onboarding 키로 추가되었으므로, useTranslation 훅을 사용하여 다국어를 지원해야 합니다. 이는 AIdol 개발 가이드의 다국어 지원 원칙(89행)에 위배됩니다.
| Previous | |
| </button> | |
| <button | |
| type="button" | |
| onClick={goToNext} | |
| disabled={isLastSlide} | |
| className="btn btn-primary btn-sm flex-1" | |
| > | |
| {isLastSlide ? "Done" : "Next"} | |
| </button> | |
| </div> | |
| {/* Keyboard hint */} | |
| <p className="text-body-s text-base-content/40 text-center"> | |
| Use ← → arrow keys to navigate | |
| {t("onboarding.previous")} | |
| </button> | |
| <button | |
| type="button" | |
| onClick={goToNext} | |
| disabled={isLastSlide} | |
| className="btn btn-primary btn-sm flex-1" | |
| > | |
| {isLastSlide ? t("onboarding.done") : t("onboarding.next")} | |
| </button> | |
| </div> | |
| {/* Keyboard hint */} | |
| <p className="text-body-s text-base-content/40 text-center"> | |
| {t("onboarding.keyboardHint")} |
| const renderSlide = (index: number) => { | ||
| switch (index) { | ||
| case 0: | ||
| return ( | ||
| <Slide slideNumber={1} title="프로젝트 계층 구조"> | ||
| <p className="text-body-m text-base-content mb-4"> | ||
| 프론트엔드와 백엔드가 동일한 계층 구조를 공유합니다. | ||
| </p> | ||
| <div className="grid grid-cols-2 gap-4"> | ||
| <div> | ||
| <h3 className="text-title-s text-primary mb-2">Frontend</h3> | ||
| <ul className="text-body-s text-base-content/80 space-y-1"> | ||
| <li> | ||
| <strong>repositories/</strong> - 데이터 접근 | ||
| </li> | ||
| <li> | ||
| <strong>services/</strong> - 비즈니스 로직 | ||
| </li> | ||
| <li> | ||
| <strong>schemas/</strong> - Zod 스키마 | ||
| </li> | ||
| <li> | ||
| <strong>mocks/</strong> - 테스트 fixture | ||
| </li> | ||
| </ul> | ||
| </div> | ||
| <div> | ||
| <h3 className="text-title-s text-primary mb-2">Backend</h3> | ||
| <ul className="text-body-s text-base-content/80 space-y-1"> | ||
| <li> | ||
| <strong>api/</strong> - FastAPI 라우터 | ||
| </li> | ||
| <li> | ||
| <strong>models/</strong> - SQLAlchemy 모델 | ||
| </li> | ||
| <li> | ||
| <strong>repositories/</strong> - 데이터 접근 | ||
| </li> | ||
| <li> | ||
| <strong>schemas/</strong> - Pydantic 스키마 | ||
| </li> | ||
| <li> | ||
| <strong>services/</strong> - 비즈니스 로직 | ||
| </li> | ||
| <li> | ||
| <strong>factories.py</strong> - DI 팩토리 | ||
| </li> | ||
| </ul> | ||
| </div> | ||
| </div> | ||
| <p className="text-body-s text-base-content/60 mt-4"> | ||
| 출처:{" "} | ||
| <a | ||
| href={LINKS.martinFowlerRepository} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="link" | ||
| > | ||
| Repository 패턴 - Martin Fowler, PoEAA | ||
| </a> | ||
| </p> | ||
| </Slide> | ||
| ); | ||
| case 1: | ||
| return ( | ||
| <Slide slideNumber={2} title="aioia-core (프론트엔드)"> | ||
| <p className="text-body-m text-base-content"> | ||
| <a | ||
| href={LINKS.aioiaCoreNpm} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="link link-primary" | ||
| > | ||
| @aioia/core | ||
| </a> | ||
| 에서 제공하는{" "} | ||
| <a | ||
| href={LINKS.baseCrudRepositorySource} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="link link-primary" | ||
| > | ||
| BaseCrudRepository | ||
| </a> | ||
| 를 상속합니다. | ||
| </p> | ||
| <CodeBlock | ||
| code={FRONTEND_REPOSITORY_CODE} | ||
| language="TypeScript" | ||
| title="repositories/CompanionRepository.ts" | ||
| /> | ||
| <div className="text-body-s text-base-content/80 space-y-1"> | ||
| <p> | ||
| <strong>resource:</strong> API 엔드포인트 경로 | ||
| </p> | ||
| <p> | ||
| <strong>getDataSchema():</strong> Zod 스키마로 응답 유효성 검증 | ||
| </p> | ||
| </div> | ||
| </Slide> | ||
| ); | ||
| case 2: | ||
| return ( | ||
| <Slide slideNumber={3} title="aioia-core (백엔드)"> | ||
| <p className="text-body-m text-base-content"> | ||
| <a | ||
| href={LINKS.aioiaCorePyPI} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="link link-primary" | ||
| > | ||
| aioia-core | ||
| </a>{" "} | ||
| (PyPI)에서 제공하는{" "} | ||
| <a | ||
| href={LINKS.baseCrudRouterSource} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="link link-primary" | ||
| > | ||
| BaseCrudRouter | ||
| </a> | ||
| 를 상속합니다. | ||
| </p> | ||
| <CodeBlock | ||
| code={BACKEND_ROUTER_CODE} | ||
| language="Python" | ||
| title="api/companion.py" | ||
| /> | ||
| <div className="text-body-s text-base-content/80 space-y-1"> | ||
| <p> | ||
| <strong>prefix:</strong> URL 경로 접두사 | ||
| </p> | ||
| <p> | ||
| <strong>tags:</strong> OpenAPI 문서 태그 | ||
| </p> | ||
| </div> | ||
| </Slide> | ||
| ); | ||
| case 3: | ||
| return ( | ||
| <Slide slideNumber={4} title="Factory 패턴 (백엔드)"> | ||
| <p className="text-body-m text-base-content"> | ||
| <a | ||
| href={LINKS.baseRepositoryFactorySource} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="link link-primary" | ||
| > | ||
| BaseRepositoryFactory | ||
| </a> | ||
| 를 상속하여 의존성 주입(DI)을 구현합니다. | ||
| </p> | ||
| <CodeBlock | ||
| code={FACTORY_CODE} | ||
| language="Python" | ||
| title="factories.py" | ||
| /> | ||
| <div className="text-body-s text-base-content/80 space-y-1"> | ||
| <p> | ||
| <strong>db_session:</strong> SQLAlchemy 세션 (자동 주입) | ||
| </p> | ||
| <p> | ||
| <strong>@property:</strong> 지연 초기화로 필요할 때만 생성 | ||
| </p> | ||
| </div> | ||
| </Slide> | ||
| ); | ||
| case 4: | ||
| return ( | ||
| <Slide slideNumber={5} title="응답 형식"> | ||
| <p className="text-body-m text-base-content"> | ||
| 모든 API 응답은{" "} | ||
| <code className="bg-base-300 rounded-sm px-1"> | ||
| {"{ data: ... }"} | ||
| </code>{" "} | ||
| 형식으로 래핑됩니다. | ||
| </p> | ||
| <CodeBlock | ||
| code={RESPONSE_CODE} | ||
| language="TypeScript" | ||
| title="응답 타입" | ||
| /> | ||
| <div className="bg-info/10 border-info rounded-lg border-l-4 p-4"> | ||
| <p className="text-body-s text-info-content"> | ||
| <strong>설계 이유:</strong> Admin 도구에서{" "} | ||
| <a | ||
| href={LINKS.refineDataProvider} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="link" | ||
| > | ||
| Refine 프레임워크 | ||
| </a> | ||
| 를 사용합니다. aioia-core가 Refine Data Provider 구조와 호환되어 | ||
| 여러 프로젝트를 통합 관리할 수 있습니다. | ||
| </p> | ||
| </div> | ||
| </Slide> | ||
| ); | ||
| case 5: | ||
| return ( | ||
| <Slide slideNumber={6} title="체크리스트"> | ||
| <p className="text-body-m text-base-content mb-4"> | ||
| 새 파일 생성 전 확인사항 | ||
| </p> | ||
| <div className="text-body-m text-base-content space-y-3"> | ||
| <label className="flex items-start gap-3"> | ||
| <input | ||
| type="checkbox" | ||
| className="checkbox checkbox-primary mt-1" | ||
| readOnly | ||
| /> | ||
| <span> | ||
| <code className="bg-base-300 rounded-sm px-1"> | ||
| repositories/ | ||
| </code> | ||
| 에 기존 Repository가 있는지 확인 | ||
| </span> | ||
| </label> | ||
| <label className="flex items-start gap-3"> | ||
| <input | ||
| type="checkbox" | ||
| className="checkbox checkbox-primary mt-1" | ||
| readOnly | ||
| /> | ||
| <span> | ||
| <code className="bg-base-300 rounded-sm px-1">schemas/</code> | ||
| 에 관련 스키마가 있는지 확인 | ||
| </span> | ||
| </label> | ||
| <label className="flex items-start gap-3"> | ||
| <input | ||
| type="checkbox" | ||
| className="checkbox checkbox-primary mt-1" | ||
| readOnly | ||
| /> | ||
| <span> | ||
| aioia-core 베이스 클래스를 상속하고 있는지 확인 | ||
| (BaseCrudRepository, BaseCrudRouter) | ||
| </span> | ||
| </label> | ||
| <label className="flex items-start gap-3"> | ||
| <input | ||
| type="checkbox" | ||
| className="checkbox checkbox-primary mt-1" | ||
| readOnly | ||
| /> | ||
| <span> | ||
| API 응답이{" "} | ||
| <code className="bg-base-300 rounded-sm px-1"> | ||
| {"{ data: ... }"} | ||
| </code>{" "} | ||
| 형식인지 확인 | ||
| </span> | ||
| </label> | ||
| </div> | ||
| </Slide> | ||
| ); | ||
| default: | ||
| return null; | ||
| } | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| <div className="bg-base-300 text-body-s text-base-content/70 px-4 py-2"> | ||
| {title} | ||
| {language && ( | ||
| <span className="text-primary ml-2 text-xs">{language}</span> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
text-xs 클래스는 Tailwind의 기본 유틸리티로, AIdol 개발 가이드의 타이포그래피 규칙(77행)에 위배됩니다. tailwind.config.ts에 정의된 MD3 타입 스케일(text-label-m 등)을 사용해야 합니다. text-xs는 0.75rem이므로 text-label-m으로 대체하는 것이 적절해 보입니다.
| <span className="text-primary ml-2 text-xs">{language}</span> | |
| <span className="text-primary ml-2 text-label-m">{language}</span> |
References
- AIdol development guide's typography rules (line 77) are violated. MD3 type scale defined in tailwind.config.ts should be used instead of basic Tailwind utilities like text-xs. (link)
- When there is a style inconsistency, the style should be unified to match the design draft, rather than just making them consistent with each other.
| className={`size-2 rounded-full transition-colors ${ | ||
| index === currentIndex | ||
| ? "bg-primary" | ||
| : "bg-base-300 hover:bg-base-content/30" | ||
| }`} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
조건부 클래스를 적용하기 위해 템플릿 리터럴을 사용하고 있습니다. AIdol 개발 가이드(64행)에 따라 clsx 라이브러리를 사용하여 조건부 클래스를 관리해야 합니다. 파일 상단에 import clsx from "clsx";를 추가하는 것을 잊지 마세요.
| className={`size-2 rounded-full transition-colors ${ | |
| index === currentIndex | |
| ? "bg-primary" | |
| : "bg-base-300 hover:bg-base-content/30" | |
| }`} | |
| className={clsx( | |
| "size-2 rounded-full transition-colors", | |
| index === currentIndex | |
| ? "bg-primary" | |
| : "bg-base-300 hover:bg-base-content/30", | |
| )} |
References
- AIdol development guide (line 64) requires using the
clsxlibrary for managing conditional classes instead of template literals. (link)
| ? "bg-primary" | ||
| : "bg-base-300 hover:bg-base-content/30" | ||
| }`} | ||
| aria-label={`Go to slide ${index + 1}`} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Factory 패턴 코드 예제를 실제 패턴으로 수정 (Repository별 개별 Factory) - "범용 REST API 표준이 아닌" 설명 복원 - 쿼리 파라미터 슬라이드 추가 (Slide 6) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
팀원 학습 흐름 개선: - 전체 구조 → 공통 규약 → 구현 순서로 재배치 - FE/BE 미숙 팀원이 맥락 이해 후 구현 코드 학습 순서 변경: 1. 계층 구조 (유지) 2. 응답 형식 (기존 5번) 3. 쿼리 파라미터 (기존 6번) 4. FE Repository (기존 2번) 5. BE Router (기존 3번) 6. Factory 패턴 (기존 4번) 7. 체크리스트 (유지) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Zod 스키마: 런타임 검증 목적 설명 (TS 타입 vs Zod, Sentry 보고) - 쿼리 파라미터: Refine Data Provider 호환 형식임을 명시 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add providers/ pattern (LLMProvider Protocol) - Add services/ pattern (ResponseGenerationService) - Reference chatroom domain implementation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
요약
신규 팀원을 위한 자가학습 온보딩 슬라이드 페이지를 추가합니다.
목적
프로젝트 아키텍처 패턴(Repository, Data Provider)을 7개 슬라이드로 설명하여 신규 팀원의 온보딩을 지원합니다.
주요 개선 사항
📚 슬라이드 콘텐츠 (7개)
학습 흐름 최적화: 전체 구조 → 공통 규약 → 구현
{ data: ... }래퍼와 Refine 호환성🔗 외부 리소스 연결
🎨 UI 컴포넌트
테스트 체크리스트
/ko/onboarding페이지 접근🤖 Generated with Claude Code