diff --git a/claude/frontend-design.md b/claude/frontend-design.md new file mode 100644 index 0000000..7c08c05 --- /dev/null +++ b/claude/frontend-design.md @@ -0,0 +1,77 @@ +# 프론트엔드설계가이드 + +[요청사항] +- <설계원칙>을 준용하여 설계 +- <설계순서>에 따라 설계 +- [결과파일] 안내에 따라 파일 작성 + +[가이드] +<설계원칙> +- 기술스택: TypeScript 5.5 + React 18.3 + Vite 5.4 +- 프로토타입과 동일하게 설계 +- 각 백엔드서비스 API명세서와 반드시 일치 +- 모바일, 태블릿, 웹 화면 크기에 맞게 반응형으로 디자인 + +<설계순서> +- 준비: + - 프로토타입 분석: '../{시스템}/design/uiux/prototype'디렉토리의 모든 파일을 'design/prototype' 디렉토리로 복사하고 분석 및 이해 + - API 분석: "[백엔드시스템]"섹션의 정보를 이용하여 API명세서를 'design/api'에 다운로드하여 분석 및 이해 + - 화면요구사항 분석: "[요구사항]" 섹션을 읽어 화면 요구사항 이해 + +- 설계: + - 1. **UI/UX 설계** + - 1.1 UI프레임워크 선택: MUI, Ant Design, Chakra UI, Mantine, React Bootstrap 등 + - 1.2 화면목록 정의 + - 1.3 화면 간 사용자 플로우 정의 + - 1.4 화면별 상세 설계: + - 1.4.1 상세기능 + - 1.4.2 UI 구성요소 + - 1.4.3 인터랙션 + - 1.5 화면간 전환 및 네비게이션 + - 1.6 반응형 설계 전략 + - 1.7 접근성 보장 방안 + - 1.8 성능 최적화 방안 + + - 2. **스타일가이드 작성**: + API명세서 분석 결과와 선택한 UI프레임워크 특성을 반영 + - 2.1 브랜드 아이덴티티: 디자인 컨셉 등 + - 2.2 디자인 원칙 + - 2.3 컬러 시스템 + - 2.4 타이포그래피 + - 2.5 간격 시스템 + - 2.6 컴포넌트 스타일 + - 2.7 반응형 브레이크포인트 + - 2.8 대상 서비스 특화 컴포넌트 + - 2.9 인터랙션 패턴 + + - 3. **정보 아키텍처 설계** + - 3.1 사이트맵: 페이지 구조 및 네비게이션 흐름 + - 3.2 프로젝트 구조 설계: 패키지와 파일까지 설계 + + - 4. **API매핑설계서** + - 4.1 API경로 매핑 + public/runtime-env.js파일을 읽어 API그룹과 '"[백엔드시스템]"섹션에 정의된 각 서비스별 HOST를 지정 + 예시) + ``` + window.__runtime_config__ = { + API_GROUP: "/api/${version:v1}", + USER_HOST: "http://localhost:8081", + ORDER_HOST: "http://localhost:8082" + } + ``` + + - 4.2 **API와 화면 상세기능 매칭**: '1.4.1 상세기능'과 API 매핑 + - 화면, 기능, 백엔드 서비스, API경로, 요청데이터 구조, 응답데이터 구조 명시 + - API 요청데이타와 API 응답데이터 예시 + +[참고자료] +- 프로토타입: design/prototype/* +- API명세서: design/api/*.json + +[결과파일] +- UI/UX설계서: design/frontend/uiux-design.md +- 스타일가이드: design/frontend/style-guide.md +- 정보아키텍처: design/frontend/ia.md +- API매핑설계서: design/frontend/api-mapping.md + + diff --git a/design/frontend/api-mapping.md b/design/frontend/api-mapping.md new file mode 100644 index 0000000..3fd7015 --- /dev/null +++ b/design/frontend/api-mapping.md @@ -0,0 +1,1158 @@ +# API 매핑 설계서 + +## 목차 +1. [API 경로 매핑](#1-api-경로-매핑) +2. [화면별 API 매핑](#2-화면별-api-매핑) +3. [API 호출 예시](#3-api-호출-예시) + +--- + +## 1. API 경로 매핑 + +### 1.1 Runtime 환경 변수 설정 + +**public/runtime-env.js** +```javascript +/** + * 런타임 환경 변수 설정 + * - 빌드 시점이 아닌 실행 시점에 환경별 API 호스트 설정 + * - 도커 컨테이너 환경에서 환경변수 주입 가능 + */ +window.__runtime_config__ = { + // API 그룹 경로 (버전 포함) + API_GROUP: "/api/v1", + + // 7개 마이크로서비스 호스트 + USER_HOST: process.env.NEXT_PUBLIC_USER_HOST || "http://localhost:8081", + EVENT_HOST: process.env.NEXT_PUBLIC_EVENT_HOST || "http://localhost:8080", + CONTENT_HOST: process.env.NEXT_PUBLIC_CONTENT_HOST || "http://localhost:8082", + AI_HOST: process.env.NEXT_PUBLIC_AI_HOST || "http://localhost:8083", + PARTICIPATION_HOST: process.env.NEXT_PUBLIC_PARTICIPATION_HOST || "http://localhost:8084", + DISTRIBUTION_HOST: process.env.NEXT_PUBLIC_DISTRIBUTION_HOST || "http://localhost:8085", + ANALYTICS_HOST: process.env.NEXT_PUBLIC_ANALYTICS_HOST || "http://localhost:8086", +}; +``` + +### 1.2 API 클라이언트 초기화 + +**src/lib/api/client.ts** +```typescript +import axios, { AxiosInstance } from 'axios'; + +// 런타임 환경 변수 타입 정의 +declare global { + interface Window { + __runtime_config__: { + API_GROUP: string; + USER_HOST: string; + EVENT_HOST: string; + CONTENT_HOST: string; + AI_HOST: string; + PARTICIPATION_HOST: string; + DISTRIBUTION_HOST: string; + ANALYTICS_HOST: string; + }; + } +} + +const config = typeof window !== 'undefined' ? window.__runtime_config__ : { + API_GROUP: '/api/v1', + USER_HOST: process.env.NEXT_PUBLIC_USER_HOST || 'http://localhost:8081', + EVENT_HOST: process.env.NEXT_PUBLIC_EVENT_HOST || 'http://localhost:8080', + CONTENT_HOST: process.env.NEXT_PUBLIC_CONTENT_HOST || 'http://localhost:8082', + AI_HOST: process.env.NEXT_PUBLIC_AI_HOST || 'http://localhost:8083', + PARTICIPATION_HOST: process.env.NEXT_PUBLIC_PARTICIPATION_HOST || 'http://localhost:8084', + DISTRIBUTION_HOST: process.env.NEXT_PUBLIC_DISTRIBUTION_HOST || 'http://localhost:8085', + ANALYTICS_HOST: process.env.NEXT_PUBLIC_ANALYTICS_HOST || 'http://localhost:8086', +}; + +// JWT 토큰 가져오기 헬퍼 +const getAuthToken = (): string | null => { + if (typeof window === 'undefined') return null; + return localStorage.getItem('accessToken'); +}; + +// Request Interceptor (JWT 토큰 자동 추가) +const authInterceptor = (instance: AxiosInstance) => { + instance.interceptors.request.use( + (config) => { + const token = getAuthToken(); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; + }, + (error) => Promise.reject(error) + ); + + // Response Interceptor (401 처리) + instance.interceptors.response.use( + (response) => response, + (error) => { + if (error.response?.status === 401) { + // 토큰 만료 또는 인증 실패 + localStorage.removeItem('accessToken'); + window.location.href = '/login'; + } + return Promise.reject(error); + } + ); +}; + +// 1. User Service API Client (인증, 프로필 관리) +export const userClient = axios.create({ + baseURL: `${config.USER_HOST}`, + timeout: 10000, + headers: { + 'Content-Type': 'application/json', + }, +}); +authInterceptor(userClient); + +// 2. Event Service API Client (이벤트 생명주기 관리) +export const eventClient = axios.create({ + baseURL: `${config.EVENT_HOST}`, + timeout: 30000, // Job 폴링 고려 + headers: { + 'Content-Type': 'application/json', + }, +}); +authInterceptor(eventClient); + +// 3. Content Service API Client (이미지 생성 및 편집) +export const contentClient = axios.create({ + baseURL: `${config.CONTENT_HOST}`, + timeout: 30000, // 이미지 생성 Job 폴링 + headers: { + 'Content-Type': 'application/json', + }, +}); +authInterceptor(contentClient); + +// 4. AI Service API Client (AI 추천 생성) +export const aiClient = axios.create({ + baseURL: `${config.AI_HOST}`, + timeout: 30000, // AI 생성 Job 폴링 + headers: { + 'Content-Type': 'application/json', + }, +}); +authInterceptor(aiClient); + +// 5. Participation Service API Client (참여자/당첨자 관리) +export const participationClient = axios.create({ + baseURL: `${config.PARTICIPATION_HOST}`, + timeout: 10000, + headers: { + 'Content-Type': 'application/json', + }, +}); +authInterceptor(participationClient); + +// 6. Distribution Service API Client (다중 채널 배포) +export const distributionClient = axios.create({ + baseURL: `${config.DISTRIBUTION_HOST}`, + timeout: 20000, // 다중 채널 배포 시간 고려 + headers: { + 'Content-Type': 'application/json', + }, +}); +authInterceptor(distributionClient); + +// 7. Analytics Service API Client (성과 분석 및 대시보드) +export const analyticsClient = axios.create({ + baseURL: `${config.ANALYTICS_HOST}`, + timeout: 10000, + headers: { + 'Content-Type': 'application/json', + }, +}); +authInterceptor(analyticsClient); +``` + +### 1.3 환경별 설정 + +| 환경 | User Service | Event Service | Content Service | AI Service | Participation Service | Distribution Service | Analytics Service | +|------|-------------|---------------|----------------|-----------|---------------------|---------------------|-------------------| +| **로컬 개발** | http://localhost:8081 | http://localhost:8080 | http://localhost:8082 | http://localhost:8083 | http://localhost:8084 | http://localhost:8085 | http://localhost:8086 | +| **개발 서버** | https://dev-api.kt-event-marketing.com/user/v1 | https://dev-api.kt-event-marketing.com/event/v1 | https://dev-api.kt-event-marketing.com/content/v1 | https://dev-api.kt-event-marketing.com/ai/v1 | https://dev-api.kt-event-marketing.com/participation/v1 | https://dev-api.kt-event-marketing.com/distribution/v1 | https://dev-api.kt-event-marketing.com/analytics/v1 | +| **프로덕션** | https://api.kt-event-marketing.com/user/v1 | https://api.kt-event-marketing.com/event/v1 | https://api.kt-event-marketing.com/content/v1 | https://api.kt-event-marketing.com/ai/v1 | https://api.kt-event-marketing.com/participation/v1 | https://api.kt-event-marketing.com/distribution/v1 | https://api.kt-event-marketing.com/analytics/v1 | + +--- + +## 2. 화면별 API 매핑 + +### 2.1 인증 영역 + +#### AUTH-01: 로그인 화면 + +| 기능 | 백엔드 서비스 | API 경로 | HTTP 메서드 | 인증 필요 | +|-----|------------|---------|-----------|---------| +| 로그인 | User Service | `/users/login` | POST | ❌ | + +**요청 데이터** +```typescript +interface LoginRequest { + phoneNumber: string; // "010XXXXXXXX" 형식 + password: string; // 최소 8자 +} +``` + +**응답 데이터** +```typescript +interface LoginResponse { + token: string; // JWT 토큰 (7일 만료) + userId: number; + userName: string; + role: 'OWNER' | 'ADMIN'; + email: string; +} +``` + +#### AUTH-02: 회원가입 화면 + +| 기능 | 백엔드 서비스 | API 경로 | HTTP 메서드 | 인증 필요 | +|-----|------------|---------|-----------|---------| +| 회원가입 | User Service | `/users/register` | POST | ❌ | + +**요청 데이터** +```typescript +interface RegisterRequest { + name: string; // 2-50자 + phoneNumber: string; // "010XXXXXXXX" + email: string; // 이메일 형식 + password: string; // 8자 이상, 영문/숫자/특수문자 + storeName: string; // 2-100자 + industry: string; // 업종 (예: 음식점, 카페) + address: string; // 5-200자 + businessHours?: string; // 영업시간 (선택) +} +``` + +**응답 데이터** +```typescript +interface RegisterResponse { + token: string; // JWT 토큰 (자동 로그인) + userId: number; + userName: string; + storeId: number; + storeName: string; +} +``` + +#### AUTH-03: 프로필 관리 화면 + +| 기능 | 백엔드 서비스 | API 경로 | HTTP 메서드 | 인증 필요 | +|-----|------------|---------|-----------|---------| +| 프로필 조회 | User Service | `/users/profile` | GET | ✅ | +| 프로필 수정 | User Service | `/users/profile` | PUT | ✅ | +| 비밀번호 변경 | User Service | `/users/password` | PUT | ✅ | + +**프로필 조회 응답** +```typescript +interface ProfileResponse { + userId: number; + userName: string; + phoneNumber: string; + email: string; + role: 'OWNER' | 'ADMIN'; + storeId: number; + storeName: string; + industry: string; + address: string; + businessHours?: string; + createdAt: string; // ISO 8601 + lastLoginAt: string; // ISO 8601 +} +``` + +**프로필 수정 요청** +```typescript +interface UpdateProfileRequest { + name?: string; + phoneNumber?: string; + email?: string; + storeName?: string; + industry?: string; + address?: string; + businessHours?: string; +} +``` + +**비밀번호 변경 요청** +```typescript +interface ChangePasswordRequest { + currentPassword: string; + newPassword: string; // 8자 이상, 영문/숫자/특수문자 +} +``` + +#### AUTH-04: 로그아웃 + +| 기능 | 백엔드 서비스 | API 경로 | HTTP 메서드 | 인증 필요 | +|-----|------------|---------|-----------|---------| +| 로그아웃 | User Service | `/users/logout` | POST | ✅ | + +**응답 데이터** +```typescript +interface LogoutResponse { + success: boolean; + message: string; // "안전하게 로그아웃되었습니다" +} +``` + +--- + +### 2.2 대시보드 영역 + +#### DASH-01: 메인 대시보드 + +| 기능 | 백엔드 서비스 | API 경로 | HTTP 메서드 | 인증 필요 | +|-----|------------|---------|-----------|---------| +| 이벤트 요약 조회 | Event Service | `/events?status=PUBLISHED&page=0&size=5` | GET | ✅ | +| 최근 이벤트 조회 | Event Service | `/events?sort=createdAt&order=desc&page=0&size=3` | GET | ✅ | + +**이벤트 목록 응답** +```typescript +interface EventListResponse { + content: EventSummary[]; + page: PageInfo; +} + +interface EventSummary { + eventId: string; // UUID + eventName: string; + objective: string; // "신규 고객 유치", "재방문 유도" 등 + status: 'DRAFT' | 'PUBLISHED' | 'ENDED'; + startDate: string; // "YYYY-MM-DD" + endDate: string; // "YYYY-MM-DD" + thumbnailUrl?: string; + createdAt: string; // ISO 8601 +} + +interface PageInfo { + page: number; // 현재 페이지 (0부터 시작) + size: number; // 페이지 크기 + totalElements: number; // 전체 요소 수 + totalPages: number; // 전체 페이지 수 +} +``` + +#### DASH-02: 이벤트 목록 화면 + +| 기능 | 백엔드 서비스 | API 경로 | HTTP 메서드 | 인증 필요 | +|-----|------------|---------|-----------|---------| +| 전체 이벤트 조회 | Event Service | `/events` | GET | ✅ | +| 상태별 필터링 | Event Service | `/events?status={status}` | GET | ✅ | +| 검색 | Event Service | `/events?search={keyword}` | GET | ✅ | + +**Query Parameters** +- `status`: DRAFT, PUBLISHED, ENDED +- `objective`: 이벤트 목적 필터 +- `search`: 이벤트명 검색 +- `page`: 페이지 번호 (기본 0) +- `size`: 페이지 크기 (기본 20, 최대 100) +- `sort`: 정렬 기준 (createdAt, startDate, endDate) +- `order`: 정렬 순서 (asc, desc) + +--- + +### 2.3 이벤트 생성 플로우 (Funnel) + +#### EVENT-01: 목적 선택 (Step 1) + +| 기능 | 백엔드 서비스 | API 경로 | HTTP 메서드 | 인증 필요 | +|-----|------------|---------|-----------|---------| +| 목적 선택 및 이벤트 생성 | Event Service | `/events/objectives` | POST | ✅ | + +**요청 데이터** +```typescript +interface SelectObjectiveRequest { + objective: string; // "신규 고객 유치", "재방문 유도", "매출 증대", "브랜드 인지도 향상" +} +``` + +**응답 데이터** +```typescript +interface EventCreatedResponse { + eventId: string; // UUID (생성된 이벤트 ID) + status: 'DRAFT'; // 항상 DRAFT + objective: string; + createdAt: string; // ISO 8601 +} +``` + +#### EVENT-02: AI 추천 확인 (Step 2) + +| 기능 | 백엔드 서비스 | API 경로 | HTTP 메서드 | 인증 필요 | +|-----|------------|---------|-----------|---------| +| AI 추천 요청 | Event Service | `/events/{eventId}/ai-recommendations` | POST | ✅ | +| Job 상태 폴링 | Event Service | `/jobs/{jobId}` | GET | ✅ | +| AI 추천 선택 | Event Service | `/events/{eventId}/recommendations` | PUT | ✅ | + +**AI 추천 요청** +```typescript +interface AiRecommendationRequest { + storeInfo: { + storeId: string; + storeName: string; + category: string; + description?: string; + }; +} +``` + +**Job 접수 응답 (202 Accepted)** +```typescript +interface JobAcceptedResponse { + jobId: string; // UUID + status: 'PENDING'; + message: string; // "AI 추천 생성 요청이 접수되었습니다..." +} +``` + +**Job 상태 조회 응답** +```typescript +interface JobStatusResponse { + jobId: string; + jobType: 'AI_RECOMMENDATION' | 'IMAGE_GENERATION'; + status: 'PENDING' | 'PROCESSING' | 'COMPLETED' | 'FAILED'; + progress: number; // 0-100 (%) + resultKey?: string; // Redis 결과 키 (COMPLETED 시) + errorMessage?: string; // 에러 메시지 (FAILED 시) + createdAt: string; + completedAt?: string; +} +``` + +**AI 추천 선택 요청** +```typescript +interface SelectRecommendationRequest { + recommendationId: string; // 선택한 추천 ID + customizations?: { + eventName?: string; + description?: string; + startDate?: string; // "YYYY-MM-DD" + endDate?: string; // "YYYY-MM-DD" + discountRate?: number; + }; +} +``` + +#### EVENT-03: 콘텐츠 미리보기 (Step 3) + +| 기능 | 백엔드 서비스 | API 경로 | HTTP 메서드 | 인증 필요 | +|-----|------------|---------|-----------|---------| +| 이미지 생성 요청 | Event Service | `/events/{eventId}/images` | POST | ✅ | +| Job 상태 폴링 | Event Service | `/jobs/{jobId}` | GET | ✅ | +| 이미지 선택 | Event Service | `/events/{eventId}/images/{imageId}/select` | PUT | ✅ | + +**이미지 생성 요청** +```typescript +interface ImageGenerationRequest { + eventInfo: { + eventName: string; + description: string; + promotionType: string; // "할인", "증정", "쿠폰" 등 + }; + imageCount?: number; // 1-5, 기본 3 +} +``` + +#### EVENT-04: 콘텐츠 편집 (Step 4) + +| 기능 | 백엔드 서비스 | API 경로 | HTTP 메서드 | 인증 필요 | +|-----|------------|---------|-----------|---------| +| 이미지 편집 | Event Service | `/events/{eventId}/images/{imageId}/edit` | PUT | ✅ | + +**이미지 편집 요청** +```typescript +interface ImageEditRequest { + editType: 'TEXT_OVERLAY' | 'COLOR_ADJUST' | 'CROP' | 'FILTER'; + parameters: { + // TEXT_OVERLAY 예시 + text?: string; + fontSize?: number; + color?: string; // HEX 코드 + position?: 'center' | 'top' | 'bottom'; + + // COLOR_ADJUST 예시 + brightness?: number; // -100 ~ 100 + contrast?: number; // -100 ~ 100 + saturation?: number; // -100 ~ 100 + }; +} +``` + +**이미지 편집 응답** +```typescript +interface ImageEditResponse { + imageId: string; + imageUrl: string; // 편집된 이미지 URL + editedAt: string; +} +``` + +#### EVENT-05: 배포 채널 선택 (Step 5) + +| 기능 | 백엔드 서비스 | API 경로 | HTTP 메서드 | 인증 필요 | +|-----|------------|---------|-----------|---------| +| 배포 채널 선택 | Event Service | `/events/{eventId}/channels` | PUT | ✅ | + +**배포 채널 선택 요청** +```typescript +interface SelectChannelsRequest { + channels: ('WEBSITE' | 'KAKAO' | 'INSTAGRAM' | 'FACEBOOK' | 'NAVER_BLOG')[]; + // 최소 1개 이상 선택 +} +``` + +#### EVENT-06: 최종 승인 (Step 6) + +| 기능 | 백엔드 서비스 | API 경로 | HTTP 메서드 | 인증 필요 | +|-----|------------|---------|-----------|---------| +| 이벤트 배포 | Event Service | `/events/{eventId}/publish` | POST | ✅ | + +**이벤트 배포 응답** +```typescript +interface EventPublishedResponse { + eventId: string; + status: 'PUBLISHED'; + publishedAt: string; + channels: string[]; // 배포된 채널 목록 + distributionResults: DistributionResult[]; +} + +interface DistributionResult { + channel: string; // "WEBSITE", "KAKAO" 등 + success: boolean; + url?: string; // 배포된 URL + message: string; +} +``` + +--- + +### 2.4 이벤트 관리 영역 + +#### MANAGE-01: 이벤트 상세 화면 + +| 기능 | 백엔드 서비스 | API 경로 | HTTP 메서드 | 인증 필요 | +|-----|------------|---------|-----------|---------| +| 이벤트 상세 조회 | Event Service | `/events/{eventId}` | GET | ✅ | +| 이벤트 수정 | Event Service | `/events/{eventId}` | PUT | ✅ | +| 이벤트 삭제 | Event Service | `/events/{eventId}` | DELETE | ✅ | +| 이벤트 조기 종료 | Event Service | `/events/{eventId}/end` | POST | ✅ | + +**이벤트 상세 응답** +```typescript +interface EventDetailResponse { + eventId: string; + userId: string; + storeId: string; + eventName: string; + objective: string; + description: string; + targetAudience: string; // "20-30대 여성" 등 + promotionType: string; // "할인", "증정", "쿠폰" + discountRate?: number; // 할인율 (%) + startDate: string; + endDate: string; + status: 'DRAFT' | 'PUBLISHED' | 'ENDED'; + selectedImageId?: string; + selectedImageUrl?: string; + generatedImages?: GeneratedImage[]; + channels?: string[]; + aiRecommendations?: AiRecommendation[]; + createdAt: string; + updatedAt: string; +} + +interface GeneratedImage { + imageId: string; + imageUrl: string; + isSelected: boolean; + createdAt: string; +} + +interface AiRecommendation { + recommendationId: string; + eventName: string; + description: string; + promotionType: string; + targetAudience: string; + isSelected: boolean; +} +``` + +**이벤트 수정 요청** +```typescript +interface UpdateEventRequest { + eventName?: string; + description?: string; + startDate?: string; + endDate?: string; + discountRate?: number; +} +``` + +**이벤트 조기 종료 요청** +```typescript +interface EndEventRequest { + reason: string; // "목표 달성으로 조기 종료" 등 +} +``` + +#### MANAGE-02: 참여자 목록 화면 + +| 기능 | 백엔드 서비스 | API 경로 | HTTP 메서드 | 인증 필요 | +|-----|------------|---------|-----------|---------| +| 참여자 목록 조회 | Participation Service | `/events/{eventId}/participants` | GET | ✅ | +| 참여자 상세 조회 | Participation Service | `/events/{eventId}/participants/{participantId}` | GET | ✅ | + +**참여자 목록 응답** +```typescript +interface ParticipantListResponse { + success: boolean; + message: string; + data: { + participants: ParticipantInfo[]; + pagination: Pagination; + }; +} + +interface ParticipantInfo { + participantId: string; + eventId: string; + name: string; + phoneNumber: string; // "010-1234-5678" 형식 + email?: string; + participatedAt: string; // ISO 8601 + storeVisited: boolean; + bonusEntries: number; // 보너스 응모권 (매장 방문 시 +1) + isWinner: boolean; +} + +interface Pagination { + currentPage: number; + pageSize: number; + totalElements: number; + totalPages: number; + hasNext: boolean; + hasPrevious: boolean; +} +``` + +**Query Parameters** +- `page`: 페이지 번호 (기본 0) +- `size`: 페이지 크기 (기본 20, 최대 100) +- `storeVisited`: 매장 방문 여부 필터 (true/false) + +#### MANAGE-03: 고객 참여 화면 (Public) + +| 기능 | 백엔드 서비스 | API 경로 | HTTP 메서드 | 인증 필요 | +|-----|------------|---------|-----------|---------| +| 이벤트 참여 | Participation Service | `/events/{eventId}/participate` | POST | ❌ | +| 이벤트 정보 조회 | Event Service | `/events/{eventId}` | GET | ❌ | + +**참여 요청** +```typescript +interface ParticipationRequest { + name: string; // 2-50자 + phoneNumber: string; // "010-XXXX-XXXX" 형식 + email?: string; + agreeMarketing: boolean; // 마케팅 정보 수신 동의 + agreePrivacy: boolean; // 개인정보 수집 동의 (필수) + storeVisited: boolean; // 매장 방문 여부 (보너스 응모권) +} +``` + +**참여 응답** +```typescript +interface ParticipationResponse { + success: boolean; + message: string; // "이벤트 참여가 완료되었습니다" + data: ParticipantInfo; +} +``` + +#### MANAGE-04: 당첨자 추첨 화면 + +| 기능 | 백엔드 서비스 | API 경로 | HTTP 메서드 | 인증 필요 | +|-----|------------|---------|-----------|---------| +| 당첨자 추첨 | Participation Service | `/events/{eventId}/draw-winners` | POST | ✅ | +| 당첨자 목록 조회 | Participation Service | `/events/{eventId}/winners` | GET | ✅ | + +**당첨자 추첨 요청** +```typescript +interface DrawWinnersRequest { + winnerCount: number; // 당첨자 수 (최소 1) + applyStoreVisitBonus?: boolean; // 매장 방문 보너스 적용 (기본 true) +} +``` + +**당첨자 추첨 응답** +```typescript +interface DrawWinnersResponse { + success: boolean; + message: string; + data: { + eventId: string; + totalParticipants: number; + winnerCount: number; + drawnAt: string; + winners: WinnerSummary[]; + }; +} + +interface WinnerSummary { + participantId: string; + name: string; + phoneNumber: string; + rank: number; // 당첨 순위 (1등, 2등, ...) +} +``` + +**당첨자 목록 응답** +```typescript +interface WinnerListResponse { + success: boolean; + message: string; + data: { + eventId: string; + drawnAt: string; + totalWinners: number; + winners: WinnerInfo[]; + pagination: Pagination; + }; +} + +interface WinnerInfo { + participantId: string; + name: string; + phoneNumber: string; + email?: string; + rank: number; + wonAt: string; +} +``` + +#### MANAGE-05: 성과 분석 화면 + +| 기능 | 백엔드 서비스 | API 경로 | HTTP 메서드 | 인증 필요 | +|-----|------------|---------|-----------|---------| +| 이벤트 상세 조회 | Event Service | `/events/{eventId}` | GET | ✅ | +| 참여자 통계 | Participation Service | `/events/{eventId}/participants` | GET | ✅ | + +**성과 분석용 데이터 조합** +- 이벤트 기본 정보 (Event Service) +- 참여자 수, 매장 방문율 (Participation Service) +- 당첨자 수 (Participation Service) + +**계산 지표** +```typescript +interface EventAnalytics { + // 이벤트 정보 + eventName: string; + eventPeriod: { start: string; end: string }; + status: string; + + // 참여 지표 + totalParticipants: number; + storeVisitCount: number; + storeVisitRate: number; // (매장 방문 / 전체 참여) * 100 + + // 당첨 지표 + totalWinners: number; + winnerDrawnDate?: string; + + // 채널별 배포 현황 + distributionChannels: string[]; + + // 시간대별 참여 추이 (프론트엔드에서 계산) + participationTrend: { + date: string; + count: number; + }[]; +} +``` + +--- + +## 3. API 호출 예시 + +### 3.1 로그인 플로우 + +```typescript +// src/lib/api/auth.ts +import { userClient } from './client'; + +export const authApi = { + // 로그인 + async login(phoneNumber: string, password: string) { + const response = await userClient.post('/users/login', { + phoneNumber, + password, + }); + + // JWT 토큰 저장 + localStorage.setItem('accessToken', response.data.token); + + return response.data; + }, + + // 로그아웃 + async logout() { + const response = await userClient.post('/users/logout'); + + // 토큰 삭제 + localStorage.removeItem('accessToken'); + + return response.data; + }, + + // 회원가입 + async register(data: RegisterRequest) { + const response = await userClient.post('/users/register', data); + + // 자동 로그인 (JWT 토큰 저장) + localStorage.setItem('accessToken', response.data.token); + + return response.data; + }, +}; +``` + +**호출 예시** +```typescript +// 컴포넌트에서 사용 +const handleLogin = async () => { + try { + const result = await authApi.login('01012345678', 'Password123!'); + console.log('로그인 성공:', result.userName); + router.push('/'); // 대시보드로 이동 + } catch (error) { + console.error('로그인 실패:', error); + toast.error('로그인에 실패했습니다'); + } +}; +``` + +### 3.2 이벤트 생성 플로우 + +```typescript +// src/lib/api/events.ts +import { eventClient } from './client'; + +export const eventApi = { + // Step 1: 목적 선택 + async selectObjective(objective: string) { + const response = await eventClient.post('/events/objectives', { + objective, + }); + return response.data; // { eventId, status, objective, createdAt } + }, + + // Step 2: AI 추천 요청 + async requestAiRecommendations(eventId: string, storeInfo: any) { + const response = await eventClient.post( + `/events/${eventId}/ai-recommendations`, + { storeInfo } + ); + return response.data; // { jobId, status, message } + }, + + // Job 상태 폴링 + async getJobStatus(jobId: string) { + const response = await eventClient.get(`/jobs/${jobId}`); + return response.data; + }, + + // AI 추천 선택 + async selectRecommendation( + eventId: string, + recommendationId: string, + customizations?: any + ) { + const response = await eventClient.put( + `/events/${eventId}/recommendations`, + { recommendationId, customizations } + ); + return response.data; + }, + + // Step 3: 이미지 생성 요청 + async requestImageGeneration(eventId: string, eventInfo: any) { + const response = await eventClient.post(`/events/${eventId}/images`, { + eventInfo, + imageCount: 3, + }); + return response.data; // { jobId, status, message } + }, + + // 이미지 선택 + async selectImage(eventId: string, imageId: string) { + const response = await eventClient.put( + `/events/${eventId}/images/${imageId}/select` + ); + return response.data; + }, + + // Step 4: 이미지 편집 + async editImage(eventId: string, imageId: string, editRequest: any) { + const response = await eventClient.put( + `/events/${eventId}/images/${imageId}/edit`, + editRequest + ); + return response.data; + }, + + // Step 5: 배포 채널 선택 + async selectChannels(eventId: string, channels: string[]) { + const response = await eventClient.put(`/events/${eventId}/channels`, { + channels, + }); + return response.data; + }, + + // Step 6: 최종 배포 + async publishEvent(eventId: string) { + const response = await eventClient.post(`/events/${eventId}/publish`); + return response.data; + }, +}; +``` + +**Funnel 컴포넌트에서 사용 예시** +```typescript +// Step 2: AI 추천 컴포넌트 +const RecommendationStep = ({ eventId, onNext }) => { + const [jobId, setJobId] = useState(null); + const [recommendations, setRecommendations] = useState([]); + + // AI 추천 요청 + const requestRecommendations = async () => { + const storeInfo = { /* User Service에서 조회한 매장 정보 */ }; + const result = await eventApi.requestAiRecommendations(eventId, storeInfo); + setJobId(result.jobId); + + // Job 폴링 시작 + pollJobStatus(result.jobId); + }; + + // Job 상태 폴링 (3초 간격) + const pollJobStatus = async (jobId: string) => { + const interval = setInterval(async () => { + const status = await eventApi.getJobStatus(jobId); + + if (status.status === 'COMPLETED') { + clearInterval(interval); + // Redis에서 결과 조회 (resultKey 사용) + fetchRecommendations(status.resultKey); + } else if (status.status === 'FAILED') { + clearInterval(interval); + toast.error('AI 추천 생성에 실패했습니다'); + } + }, 3000); + }; + + // 추천 선택 및 다음 단계 + const handleSelectRecommendation = async (recommendationId: string) => { + await eventApi.selectRecommendation(eventId, recommendationId); + onNext(); // Step 3으로 이동 + }; + + return ( +
+ {/* AI 추천 UI */} + {recommendations.map(rec => ( + handleSelectRecommendation(rec.recommendationId)} + /> + ))} +
+ ); +}; +``` + +### 3.3 참여자 관리 + +```typescript +// src/lib/api/participants.ts +import { participationClient } from './client'; + +export const participantApi = { + // 참여자 목록 조회 + async getParticipants( + eventId: string, + page: number = 0, + size: number = 20, + storeVisited?: boolean + ) { + const params = new URLSearchParams({ + page: page.toString(), + size: size.toString(), + }); + + if (storeVisited !== undefined) { + params.append('storeVisited', storeVisited.toString()); + } + + const response = await participationClient.get( + `/events/${eventId}/participants?${params}` + ); + return response.data.data; + }, + + // 참여자 상세 조회 + async getParticipantDetail(eventId: string, participantId: string) { + const response = await participationClient.get( + `/events/${eventId}/participants/${participantId}` + ); + return response.data.data; + }, + + // 당첨자 추첨 + async drawWinners( + eventId: string, + winnerCount: number, + applyStoreVisitBonus: boolean = true + ) { + const response = await participationClient.post( + `/events/${eventId}/draw-winners`, + { winnerCount, applyStoreVisitBonus } + ); + return response.data.data; + }, + + // 당첨자 목록 조회 + async getWinners(eventId: string, page: number = 0, size: number = 20) { + const response = await participationClient.get( + `/events/${eventId}/winners?page=${page}&size=${size}` + ); + return response.data.data; + }, + + // 고객 참여 (인증 불필요) + async participate(eventId: string, data: ParticipationRequest) { + const response = await participationClient.post( + `/events/${eventId}/participate`, + data + ); + return response.data.data; + }, +}; +``` + +**React Query를 사용한 데이터 패칭 예시** +```typescript +// src/hooks/useParticipants.ts +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { participantApi } from '@/lib/api/participants'; + +export const useParticipants = (eventId: string, page: number = 0) => { + return useQuery({ + queryKey: ['participants', eventId, page], + queryFn: () => participantApi.getParticipants(eventId, page), + enabled: !!eventId, + }); +}; + +export const useDrawWinners = (eventId: string) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ winnerCount }: { winnerCount: number }) => + participantApi.drawWinners(eventId, winnerCount), + onSuccess: () => { + // 참여자 목록 갱신 + queryClient.invalidateQueries({ queryKey: ['participants', eventId] }); + // 당첨자 목록 갱신 + queryClient.invalidateQueries({ queryKey: ['winners', eventId] }); + }, + }); +}; +``` + +### 3.4 에러 처리 + +```typescript +// src/lib/api/errorHandler.ts +import { AxiosError } from 'axios'; + +export interface ApiError { + code: string; + message: string; + timestamp: string; + details?: string[]; +} + +export const handleApiError = (error: unknown): string => { + if (error instanceof AxiosError) { + const apiError = error.response?.data as ApiError; + + // 에러 코드별 처리 + switch (apiError.code) { + case 'USER_001': + return '이미 가입된 전화번호입니다'; + case 'AUTH_001': + return '전화번호 또는 비밀번호를 확인해주세요'; + case 'DUPLICATE_PARTICIPATION': + return '이미 참여하신 이벤트입니다'; + case 'INVALID_WINNER_COUNT': + return '당첨자 수가 참여자 수보다 많습니다'; + default: + return apiError.message || '요청 처리 중 오류가 발생했습니다'; + } + } + + return '알 수 없는 오류가 발생했습니다'; +}; +``` + +--- + +## 4. 요약 + +### 4.1 주요 API 엔드포인트 요약 + +| 서비스 | 주요 기능 | 엔드포인트 수 | +|--------|---------|------------| +| **User Service** | 인증, 프로필 관리 | 6개 | +| **Event Service** | 이벤트 생성/관리, Job 폴링 | 14개 | +| **Participation Service** | 참여자/당첨자 관리 | 5개 | + +### 4.2 비동기 작업 처리 + +**AI 추천 생성 & 이미지 생성** +- 요청 시 `202 Accepted` 응답과 함께 `jobId` 반환 +- `/jobs/{jobId}`로 3초 간격 폴링 +- `status: COMPLETED` 시 `resultKey`로 Redis 결과 조회 +- `status: FAILED` 시 에러 메시지 표시 + +### 4.3 인증 처리 + +**JWT 토큰 관리** +- 로그인/회원가입 성공 시 `localStorage`에 저장 +- 모든 API 요청 시 `Authorization: Bearer {token}` 헤더 자동 추가 +- 401 응답 시 자동 로그아웃 및 로그인 페이지 리다이렉트 + +### 4.4 데이터 캐싱 전략 + +**React Query 캐싱 키** +```typescript +['user', 'profile'] // 프로필 정보 +['events', { status, page }] // 이벤트 목록 +['event', eventId] // 이벤트 상세 +['participants', eventId, page] // 참여자 목록 +['winners', eventId] // 당첨자 목록 +['job', jobId] // Job 상태 +``` + +--- + +**문서 버전**: 1.0 +**작성일**: 2025-01-24 +**작성자**: Frontend Design Team diff --git a/design/frontend/ia.md b/design/frontend/ia.md new file mode 100644 index 0000000..f776684 --- /dev/null +++ b/design/frontend/ia.md @@ -0,0 +1,614 @@ +# 정보 아키텍처 설계서 + +## 목차 +1. [사이트맵](#1-사이트맵) +2. [프로젝트 구조 설계](#2-프로젝트-구조-설계) + +--- + +## 1. 사이트맵 + +### 1.1 전체 사이트 구조 + +``` +KT 이벤트 마케팅 플랫폼 +│ +├── 🔐 인증 영역 (Public) +│ ├── /login - 로그인 +│ ├── /register - 회원가입 +│ └── /profile - 프로필 관리 +│ +├── 🏠 대시보드 영역 (Private) +│ ├── / - 메인 대시보드 +│ │ ├── 진행중 이벤트 현황 +│ │ ├── 최근 성과 요약 +│ │ └── 빠른 작업 버튼 +│ │ +│ └── /events - 이벤트 목록 +│ ├── 전체 이벤트 조회 +│ ├── 상태별 필터링 (진행중/예정/종료) +│ └── 검색 기능 +│ +├── ✨ 이벤트 생성 플로우 (Private) +│ └── /events/create - 이벤트 생성 Funnel +│ ├── Step 1: /events/create?step=objective - 목적 선택 +│ ├── Step 2: /events/create?step=recommendation - AI 추천 확인 +│ ├── Step 3: /events/create?step=content - 콘텐츠 미리보기 +│ ├── Step 4: /events/create?step=edit - 콘텐츠 편집 +│ ├── Step 5: /events/create?step=channels - 배포 채널 선택 +│ └── Step 6: /events/create?step=publish - 최종 승인 +│ +└── 📊 이벤트 관리 영역 (Private) + └── /events/[eventId] + ├── /events/[eventId] - 이벤트 상세 + ├── /events/[eventId]/participants - 참여자 목록 + ├── /events/[eventId]/participate - 고객 참여 화면 (Public) + ├── /events/[eventId]/draw - 당첨자 추첨 + └── /events/[eventId]/analytics - 성과 분석 +``` + +### 1.2 네비게이션 흐름 + +#### Bottom Navigation (인증 후 모든 화면) +``` +┌─────────────────────────────────────────────────┐ +│ 홈 이벤트 분석 프로필 │ +│ / /events /analytics /profile │ +└─────────────────────────────────────────────────┘ +``` + +#### 주요 사용자 여정 + +**여정 1: 신규 이벤트 생성** +``` +/ (대시보드) + → [+ 새 이벤트 생성] 버튼 클릭 + → /events/create?step=objective (목적 선택) + → /events/create?step=recommendation (AI 추천) + → /events/create?step=content (미리보기) + → /events/create?step=edit (편집) + → /events/create?step=channels (채널 선택) + → /events/create?step=publish (승인) + → /events/[eventId] (생성 완료, 상세 페이지) +``` + +**여정 2: 이벤트 관리 및 분석** +``` +/events (이벤트 목록) + → [이벤트 카드] 클릭 + → /events/[eventId] (상세) + ├── [참여자 관리] → /events/[eventId]/participants + ├── [당첨자 추첨] → /events/[eventId]/draw + └── [성과 분석] → /events/[eventId]/analytics +``` + +**여정 3: 고객 참여** +``` +QR 코드 스캔 또는 링크 접속 + → /events/[eventId]/participate (참여 화면) + → 참여 정보 입력 + → 참여 완료 +``` + +### 1.3 접근 권한 설정 + +| 페이지 경로 | 접근 권한 | 인증 필요 | 설명 | +|------------|----------|---------|------| +| `/login` | Public | ❌ | 로그인 페이지 | +| `/register` | Public | ❌ | 회원가입 페이지 | +| `/` | Private | ✅ | 메인 대시보드 | +| `/events` | Private | ✅ | 이벤트 목록 | +| `/events/create` | Private | ✅ | 이벤트 생성 플로우 | +| `/events/[eventId]` | Private | ✅ | 이벤트 상세 | +| `/events/[eventId]/participants` | Private | ✅ | 참여자 관리 | +| `/events/[eventId]/draw` | Private | ✅ | 당첨자 추첨 | +| `/events/[eventId]/analytics` | Private | ✅ | 성과 분석 | +| `/events/[eventId]/participate` | **Public** | ❌ | 고객 참여 화면 | +| `/profile` | Private | ✅ | 프로필 관리 | + +--- + +## 2. 프로젝트 구조 설계 + +### 2.1 Next.js 14 App Router 기반 디렉토리 구조 + +``` +fe-kt-event-marketing/ +│ +├── 📁 app/ # Next.js 14 App Router +│ ├── (auth)/ # 인증 레이아웃 그룹 +│ │ ├── login/ +│ │ │ └── page.tsx # 로그인 페이지 +│ │ ├── register/ +│ │ │ └── page.tsx # 회원가입 페이지 +│ │ └── layout.tsx # 인증 전용 레이아웃 +│ │ +│ ├── (main)/ # 메인 레이아웃 그룹 (인증 필요) +│ │ ├── page.tsx # 대시보드 (/) +│ │ ├── events/ +│ │ │ ├── page.tsx # 이벤트 목록 +│ │ │ ├── create/ +│ │ │ │ └── page.tsx # 이벤트 생성 Funnel +│ │ │ └── [eventId]/ +│ │ │ ├── page.tsx # 이벤트 상세 +│ │ │ ├── participants/ +│ │ │ │ └── page.tsx # 참여자 목록 +│ │ │ ├── draw/ +│ │ │ │ └── page.tsx # 당첨자 추첨 +│ │ │ └── analytics/ +│ │ │ └── page.tsx # 성과 분석 +│ │ ├── profile/ +│ │ │ └── page.tsx # 프로필 관리 +│ │ └── layout.tsx # 메인 레이아웃 (Bottom Nav 포함) +│ │ +│ ├── events/ # Public 이벤트 경로 +│ │ └── [eventId]/ +│ │ └── participate/ +│ │ └── page.tsx # 고객 참여 화면 (인증 불필요) +│ │ +│ ├── api/ # API Routes (서버 컴포넌트) +│ │ └── auth/ +│ │ └── [...nextauth]/ +│ │ └── route.ts # NextAuth API 라우트 +│ │ +│ ├── layout.tsx # 루트 레이아웃 +│ ├── error.tsx # 전역 에러 핸들링 +│ ├── loading.tsx # 전역 로딩 상태 +│ └── not-found.tsx # 404 페이지 +│ +├── 📁 src/ +│ ├── components/ # React 컴포넌트 +│ │ ├── common/ # 공통 컴포넌트 +│ │ │ ├── Button/ +│ │ │ │ ├── Button.tsx +│ │ │ │ └── Button.test.tsx +│ │ │ ├── Card/ +│ │ │ │ └── Card.tsx +│ │ │ ├── Input/ +│ │ │ │ └── Input.tsx +│ │ │ ├── Layout/ +│ │ │ │ ├── BottomNavigation.tsx +│ │ │ │ ├── Header.tsx +│ │ │ │ └── Container.tsx +│ │ │ └── Loading/ +│ │ │ └── Loading.tsx +│ │ │ +│ │ ├── auth/ # 인증 관련 컴포넌트 +│ │ │ ├── LoginForm.tsx +│ │ │ ├── RegisterForm.tsx +│ │ │ └── ProfileForm.tsx +│ │ │ +│ │ ├── dashboard/ # 대시보드 컴포넌트 +│ │ │ ├── EventSummaryCard.tsx +│ │ │ ├── PerformanceChart.tsx +│ │ │ └── QuickActions.tsx +│ │ │ +│ │ ├── event/ # 이벤트 관련 컴포넌트 +│ │ │ ├── EventCard.tsx +│ │ │ ├── EventList.tsx +│ │ │ ├── EventDetail.tsx +│ │ │ ├── ParticipantTable.tsx +│ │ │ └── WinnerDrawModal.tsx +│ │ │ +│ │ └── funnel/ # Funnel 관련 컴포넌트 +│ │ ├── ObjectiveStep.tsx +│ │ ├── RecommendationStep.tsx +│ │ ├── ContentStep.tsx +│ │ ├── EditStep.tsx +│ │ ├── ChannelsStep.tsx +│ │ ├── PublishStep.tsx +│ │ └── FunnelLayout.tsx +│ │ +│ ├── hooks/ # Custom React Hooks +│ │ ├── useAuth.ts # 인증 상태 관리 +│ │ ├── useEvent.ts # 이벤트 CRUD +│ │ ├── useEventList.ts # 이벤트 목록 +│ │ ├── useParticipants.ts # 참여자 관리 +│ │ ├── useJobPolling.ts # Job 상태 폴링 +│ │ └── useToast.ts # Toast 알림 +│ │ +│ ├── lib/ # 라이브러리 및 유틸리티 +│ │ ├── api/ # API 클라이언트 +│ │ │ ├── client.ts # Axios 인스턴스 +│ │ │ ├── auth.ts # 인증 API +│ │ │ ├── events.ts # 이벤트 API +│ │ │ └── participants.ts # 참여 API +│ │ │ +│ │ ├── utils/ # 유틸리티 함수 +│ │ │ ├── format.ts # 날짜/숫자 포맷팅 +│ │ │ ├── validation.ts # 폼 검증 헬퍼 +│ │ │ └── storage.ts # localStorage 헬퍼 +│ │ │ +│ │ └── constants/ # 상수 정의 +│ │ ├── api.ts # API 엔드포인트 +│ │ ├── routes.ts # 라우트 경로 +│ │ └── event.ts # 이벤트 상수 +│ │ +│ ├── store/ # 상태 관리 (Zustand) +│ │ ├── authStore.ts # 인증 상태 +│ │ ├── funnelStore.ts # Funnel 임시 저장 +│ │ └── uiStore.ts # UI 상태 (Toast, Modal 등) +│ │ +│ ├── styles/ # 스타일 +│ │ ├── globals.css # 전역 스타일 +│ │ └── theme.ts # MUI 테마 설정 +│ │ +│ └── types/ # TypeScript 타입 정의 +│ ├── auth.ts # 인증 관련 타입 +│ ├── event.ts # 이벤트 타입 +│ ├── participant.ts # 참여자 타입 +│ └── api.ts # API 응답 타입 +│ +├── 📁 public/ # 정적 파일 +│ ├── images/ # 이미지 파일 +│ ├── icons/ # 아이콘 파일 +│ └── runtime-env.js # 런타임 환경 변수 +│ +├── 📁 tests/ # 테스트 파일 +│ ├── unit/ # 단위 테스트 +│ ├── integration/ # 통합 테스트 +│ └── e2e/ # E2E 테스트 (Playwright) +│ +├── .env.local # 로컬 환경 변수 +├── .env.development # 개발 환경 변수 +├── .env.production # 프로덕션 환경 변수 +├── next.config.js # Next.js 설정 +├── tsconfig.json # TypeScript 설정 +├── package.json # 의존성 관리 +└── README.md # 프로젝트 문서 +``` + +### 2.2 주요 파일 설명 + +#### 2.2.1 레이아웃 구조 + +**app/layout.tsx** (루트 레이아웃) +```typescript +// React Query Provider, MUI ThemeProvider, Toast Provider 설정 +export default function RootLayout({ children }) { + return ( + + + + + + {children} + + + + + ); +} +``` + +**app/(main)/layout.tsx** (메인 레이아웃) +```typescript +// Bottom Navigation 포함 레이아웃 +// 인증 체크 미들웨어 +export default function MainLayout({ children }) { + return ( + <> +
+ {children} + + + ); +} +``` + +**app/(auth)/layout.tsx** (인증 레이아웃) +```typescript +// 로고 중심의 심플한 레이아웃 +export default function AuthLayout({ children }) { + return ( + + + {children} + + ); +} +``` + +#### 2.2.2 Funnel 구현 + +**app/(main)/events/create/page.tsx** +```typescript +'use client'; + +import { useFunnel } from '@use-funnel/next'; +import { ObjectiveStep, RecommendationStep, ... } from '@/components/funnel'; + +export default function EventCreatePage() { + const [Funnel, state, setStep] = useFunnel({ + id: 'event-creation', + initial: 'objective', + }); + + return ( + + + setStep('recommendation')} /> + + + setStep('content')} + onBack={() => setStep('objective')} + /> + + {/* ... 나머지 Step들 */} + + ); +} +``` + +#### 2.2.3 API 클라이언트 + +**src/lib/api/client.ts** +```typescript +import axios from 'axios'; + +// runtime-env.js에서 환경 변수 로드 +declare global { + interface Window { + __runtime_config__: { + API_GROUP: string; + USER_HOST: string; + EVENT_HOST: string; + CONTENT_HOST: string; + AI_HOST: string; + PARTICIPATION_HOST: string; + DISTRIBUTION_HOST: string; + ANALYTICS_HOST: string; + }; + } +} + +const config = window.__runtime_config__; + +// 1. User Service API Client (인증, 프로필) +export const userClient = axios.create({ + baseURL: `${config.USER_HOST}${config.API_GROUP}`, + timeout: 10000, +}); + +// 2. Event Service API Client (이벤트 생명주기 관리) +export const eventClient = axios.create({ + baseURL: `${config.EVENT_HOST}${config.API_GROUP}`, + timeout: 30000, // AI/이미지 생성 Job 폴링 고려 +}); + +// 3. Content Service API Client (이미지 생성 및 편집) +export const contentClient = axios.create({ + baseURL: `${config.CONTENT_HOST}${config.API_GROUP}`, + timeout: 30000, // 이미지 생성 Job 폴링 +}); + +// 4. AI Service API Client (AI 추천 생성) +export const aiClient = axios.create({ + baseURL: `${config.AI_HOST}${config.API_GROUP}`, + timeout: 30000, // AI 생성 Job 폴링 +}); + +// 5. Participation Service API Client (참여자/당첨자 관리) +export const participationClient = axios.create({ + baseURL: `${config.PARTICIPATION_HOST}${config.API_GROUP}`, + timeout: 10000, +}); + +// 6. Distribution Service API Client (다중 채널 배포) +export const distributionClient = axios.create({ + baseURL: `${config.DISTRIBUTION_HOST}${config.API_GROUP}`, + timeout: 20000, // 다중 채널 배포 시간 고려 +}); + +// 7. Analytics Service API Client (성과 분석 및 대시보드) +export const analyticsClient = axios.create({ + baseURL: `${config.ANALYTICS_HOST}${config.API_GROUP}`, + timeout: 10000, +}); +``` + +### 2.3 컴포넌트 재사용 전략 + +#### Atomic Design 기반 컴포넌트 계층 + +``` +Atoms (원자) + └── Button, Input, Icon, Typography + ↓ +Molecules (분자) + └── SearchBar, EventCard, ParticipantRow + ↓ +Organisms (유기체) + └── EventList, ParticipantTable, Dashboard + ↓ +Templates (템플릿) + └── MainLayout, AuthLayout, FunnelLayout + ↓ +Pages (페이지) + └── HomePage, EventListPage, EventCreatePage +``` + +#### MUI 컴포넌트 커스터마이징 + +```typescript +// src/components/common/Button/Button.tsx +import { Button as MuiButton, ButtonProps } from '@mui/material'; + +export const Button: React.FC = (props) => { + return ( + + ); +}; +``` + +### 2.4 상태 관리 전략 + +| 상태 유형 | 관리 도구 | 사용 사례 | +|---------|---------|---------| +| 서버 상태 | React Query v5 | API 데이터 캐싱, 자동 재검증 | +| 전역 클라이언트 상태 | Zustand | 인증 상태, UI 상태 (Toast, Modal) | +| 로컬 상태 | useState | 컴포넌트 내부 상태 | +| 폼 상태 | React Hook Form | 폼 입력, 검증 | +| Funnel 상태 | @use-funnel/next | Step 전환, 데이터 임시 저장 | + +### 2.5 라우팅 및 미들웨어 + +**middleware.ts** (Next.js Middleware) +```typescript +import { NextResponse } from 'next/server'; +import type { NextRequest } from 'next/server'; + +export function middleware(request: NextRequest) { + const token = request.cookies.get('accessToken'); + const { pathname } = request.nextUrl; + + // 인증이 필요한 경로 + const protectedRoutes = ['/', '/events', '/profile']; + const isProtectedRoute = protectedRoutes.some(route => + pathname.startsWith(route) + ); + + // 인증되지 않은 사용자가 보호된 경로 접근 시 로그인으로 리다이렉트 + if (isProtectedRoute && !token) { + return NextResponse.redirect(new URL('/login', request.url)); + } + + // 인증된 사용자가 로그인/회원가입 접근 시 대시보드로 리다이렉트 + if ((pathname === '/login' || pathname === '/register') && token) { + return NextResponse.redirect(new URL('/', request.url)); + } + + return NextResponse.next(); +} + +export const config = { + matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'], +}; +``` + +### 2.6 환경 변수 관리 + +**.env.local** +``` +# 로컬 개발 환경 +NEXT_PUBLIC_USER_HOST=http://localhost:8081 +NEXT_PUBLIC_EVENT_HOST=http://localhost:8080 +NEXT_PUBLIC_CONTENT_HOST=http://localhost:8082 +NEXT_PUBLIC_AI_HOST=http://localhost:8083 +NEXT_PUBLIC_PARTICIPATION_HOST=http://localhost:8084 +NEXT_PUBLIC_DISTRIBUTION_HOST=http://localhost:8085 +NEXT_PUBLIC_ANALYTICS_HOST=http://localhost:8086 +NEXT_PUBLIC_API_VERSION=v1 +``` + +**.env.production** +``` +# 프로덕션 환경 +NEXT_PUBLIC_USER_HOST=https://api.kt-event-marketing.com/user/v1 +NEXT_PUBLIC_EVENT_HOST=https://api.kt-event-marketing.com/event/v1 +NEXT_PUBLIC_CONTENT_HOST=https://api.kt-event-marketing.com/content/v1 +NEXT_PUBLIC_AI_HOST=https://api.kt-event-marketing.com/ai/v1 +NEXT_PUBLIC_PARTICIPATION_HOST=https://api.kt-event-marketing.com/participation/v1 +NEXT_PUBLIC_DISTRIBUTION_HOST=https://api.kt-event-marketing.com/distribution/v1 +NEXT_PUBLIC_ANALYTICS_HOST=https://api.kt-event-marketing.com/analytics/v1 +NEXT_PUBLIC_API_VERSION=v1 +``` + +**public/runtime-env.js** +```javascript +window.__runtime_config__ = { + API_GROUP: "/api/v1", + + // 7개 마이크로서비스 호스트 + USER_HOST: process.env.NEXT_PUBLIC_USER_HOST || "http://localhost:8081", + EVENT_HOST: process.env.NEXT_PUBLIC_EVENT_HOST || "http://localhost:8080", + CONTENT_HOST: process.env.NEXT_PUBLIC_CONTENT_HOST || "http://localhost:8082", + AI_HOST: process.env.NEXT_PUBLIC_AI_HOST || "http://localhost:8083", + PARTICIPATION_HOST: process.env.NEXT_PUBLIC_PARTICIPATION_HOST || "http://localhost:8084", + DISTRIBUTION_HOST: process.env.NEXT_PUBLIC_DISTRIBUTION_HOST || "http://localhost:8085", + ANALYTICS_HOST: process.env.NEXT_PUBLIC_ANALYTICS_HOST || "http://localhost:8086", +}; +``` + +--- + +## 3. 기술 스택 정리 + +| 카테고리 | 기술 | 버전 | 용도 | +|---------|-----|------|-----| +| **Framework** | Next.js | 14.x | App Router, SSR, SSG | +| **Library** | React | 18.x | UI 라이브러리 | +| **Language** | TypeScript | 5.x | 정적 타입 검사 | +| **UI Framework** | Material-UI (MUI) | 6.x | UI 컴포넌트 | +| **State (Server)** | React Query | 5.x | 서버 상태 관리 | +| **State (Client)** | Zustand | 4.x | 전역 상태 관리 | +| **Form** | React Hook Form | 7.x | 폼 관리 | +| **Validation** | Zod | 3.x | 스키마 검증 | +| **Funnel** | @use-funnel/next | 1.x | Funnel 상태 관리 | +| **HTTP Client** | Axios | 1.x | API 통신 | +| **Charts** | Chart.js | 4.x | 데이터 시각화 | +| **Testing** | Jest, Playwright | - | 단위/E2E 테스트 | + +--- + +## 4. 배포 및 환경 설정 + +### 4.1 개발 환경 +```bash +npm run dev # 개발 서버 실행 (localhost:3000) +npm run build # 프로덕션 빌드 +npm run start # 프로덕션 서버 실행 +npm run lint # ESLint 검사 +npm run test # Jest 단위 테스트 +npm run test:e2e # Playwright E2E 테스트 +``` + +### 4.2 빌드 최적화 +- **Code Splitting**: 동적 import로 번들 크기 최적화 +- **Image Optimization**: Next.js Image 컴포넌트 사용 +- **Font Optimization**: next/font로 웹폰트 최적화 +- **Tree Shaking**: 사용하지 않는 코드 제거 + +### 4.3 성능 모니터링 +- **Lighthouse**: 성능, 접근성, SEO 점수 +- **Web Vitals**: LCP, FID, CLS 측정 +- **Bundle Analyzer**: 번들 크기 분석 + +--- + +## 5. 접근성 및 반응형 설계 + +### 5.1 반응형 브레이크포인트 +```typescript +// src/styles/theme.ts +const breakpoints = { + xs: 0, // Mobile: 320px ~ 599px + sm: 600, // Tablet: 600px ~ 1023px + md: 1024, // Desktop: 1024px ~ 1439px + lg: 1440, // Large Desktop: 1440px+ +}; +``` + +### 5.2 접근성 준수 (WCAG 2.1 AA) +- **키보드 네비게이션**: Tab, Enter, Space 지원 +- **스크린 리더**: aria-label, role 속성 +- **색상 대비**: 최소 4.5:1 비율 +- **포커스 표시**: 명확한 focus 상태 +- **대체 텍스트**: 모든 이미지에 alt 속성 + +--- + +**문서 버전**: 1.0 +**작성일**: 2025-01-24 +**작성자**: Frontend Design Team diff --git a/design/frontend/style-guide.md b/design/frontend/style-guide.md new file mode 100644 index 0000000..b1f46d5 --- /dev/null +++ b/design/frontend/style-guide.md @@ -0,0 +1,1554 @@ +# KT AI 기반 소상공인 이벤트 자동 생성 서비스 - 스타일 가이드 + +## 문서 정보 + +- 작성일: 2025-10-17 +- 버전: 1.0 +- 기반 설계: UI/UX 설계서 v1.0 +- 설계 원칙: Mobile First, 접근성 우선, 일관성 + +--- + +## 1. 브랜드 아이덴티티 + +### 1.1 브랜드 비전 + +**"AI로 간편하게, 성공으로 확실하게"** + +소상공인이 전문 마케터 없이도 AI의 도움으로 효과적인 이벤트를 기획하고 실행할 수 있도록 돕는 혁신적인 서비스 + +### 1.2 디자인 철학 + +#### 혁신성 (Innovation) + +- 최신 AI 기술을 활용한 자동화된 이벤트 생성 +- 복잡한 마케팅 프로세스를 단순화 +- 3분 만에 완성되는 이벤트 콘텐츠 + +#### 신뢰성 (Trust) + +- KT 브랜드의 안정성과 신뢰감 +- 명확한 프로세스와 투명한 결과 제공 +- 실시간 성과 모니터링 + +#### 친근함 (Approachability) + +- 소상공인 눈높이에 맞춘 쉬운 인터페이스 +- 초보자도 이해할 수 있는 명확한 안내 +- 따뜻하고 친근한 톤앤매너 + +### 1.3 브랜드 컬러 + +**Primary Color: KT Red** + +- 정체성, 브랜드 대표 색상 +- 행동 유도(CTA), 강조 요소 +- KT 브랜드 헤리티지 계승 + +**Secondary Color: AI Blue** + +- AI 기능, 기술적 신뢰감 +- 정보 전달, 안내 요소 +- 혁신과 미래지향성 + +--- + +## 2. 디자인 원칙 + +### 2.1 명확성 (Clarity) + +**사용자가 무엇을 해야 하는지 항상 명확해야 합니다** + +- 직관적인 아이콘과 레이블 사용 +- 명확한 시각적 계층 구조 +- 현재 위치와 다음 단계를 항상 표시 + +예시: + +``` +✓ 좋은 예: "새 이벤트 만들기" + 큰 CTA 버튼 +✗ 나쁜 예: "시작하기" (무엇을 시작하는지 불분명) +``` + +### 2.2 효율성 (Efficiency) + +**최소한의 단계로 목표를 달성할 수 있어야 합니다** + +- 불필요한 스텝 제거 +- 자동 완성 및 추천 기능 활용 +- AI가 대부분의 작업 자동화 + +목표: + +- 이벤트 기획: 10초 이내 +- 콘텐츠 생성: 3분 이내 +- 배포 설정: 1분 이내 + +### 2.3 신뢰성 (Trust) + +**AI 처리 과정과 결과를 투명하게 보여줍니다** + +- AI 처리 시간 명시 (예: "AI가 분석중입니다 약 3초 소요") +- 중간 단계 결과 확인 가능 +- 언제든 이전 단계로 돌아갈 수 있음 + +### 2.4 친근함 (Approachability) + +**초보자도 쉽게 사용할 수 있어야 합니다** + +- 전문 용어 최소화 +- 친근한 톤의 안내 문구 +- 도움말과 예시 제공 + +### 2.5 일관성 (Consistency) + +**모든 화면에서 동일한 패턴을 유지합니다** + +- 컴포넌트 재사용 +- 일관된 색상과 타이포그래피 +- 예측 가능한 인터랙션 + +--- + +## 3. 색상 시스템 + +### 3.1 Primary Color (주 색상) + +#### KT Red - 브랜드 정체성 + +``` +Main: #E31E24 // CTA 버튼, 주요 액션, 활성화 상태 +Light: #FF4D52 // 호버 상태, 배경 강조 +Dark: #C71820 // 눌림 상태, 진한 강조 + +사용 예시: +- Primary Button 배경 +- 활성화된 네비게이션 아이템 +- 중요한 알림 배지 +- 링크 텍스트 +``` + +#### 색상 접근성 + +- White 배경 대비: 7.2:1 (WCAG AAA) +- Gray-100 배경 대비: 6.8:1 (WCAG AAA) + +### 3.2 Secondary Color (보조 색상) + +#### AI Blue - 혁신과 신뢰 + +``` +Main: #0066FF // AI 기능 강조, 정보 아이콘 +Light: #4D94FF // AI 로딩 배경, 안내 영역 +Dark: #004DBF // AI 프로세스 완료 + +사용 예시: +- AI 처리 중 상태 표시 +- 정보 제공 영역 +- 트렌드 분석 차트 +- 도움말 아이콘 +``` + +### 3.3 Grayscale (회색조) + +``` +Black (Gray-900): #1A1A1A // 주요 제목, 본문 텍스트 +Gray-700: #4A4A4A // 보조 텍스트, 아이콘 +Gray-500: #9E9E9E // 비활성화 텍스트, 플레이스홀더 +Gray-300: #D9D9D9 // 구분선, 비활성화 테두리 +Gray-100: #F5F5F5 // 배경, 비활성화 버튼 +White (Gray-50): #FFFFFF // 카드 배경, 기본 배경 + +색상 대비 (White 배경 기준): +- Gray-900: 14.2:1 (AAA) +- Gray-700: 8.5:1 (AAA) +- Gray-500: 4.6:1 (AA) +``` + +### 3.4 Semantic Colors (의미 색상) + +``` +Success (성공): #00C853 // 완료, 승인, 성공 메시지 +Warning (경고): #FFA000 // 주의, 대기 중, 확인 필요 +Error (오류): #D32F2F // 오류, 거부, 삭제 확인 +Info (정보): #0288D1 // 안내, 팁, 추가 정보 + +사용 예시: +- Success: "이벤트가 성공적으로 배포되었습니다" +- Warning: "AI 처리가 평소보다 오래 걸리고 있습니다" +- Error: "이미지 생성에 실패했습니다" +- Info: "트렌드 분석 결과를 확인하세요" +``` + +### 3.5 Gradient (그라데이션) + +#### AI Feature Gradient + +``` +Primary Gradient: + background: linear-gradient(135deg, #E31E24 0%, #FF4D52 100%); + 사용: AI 기능 강조 카드, 프리미엄 기능 + +Secondary Gradient: + background: linear-gradient(135deg, #0066FF 0%, #4D94FF 100%); + 사용: AI 처리 중 배경, 정보 강조 영역 +``` + +--- + +## 4. 타이포그래피 시스템 + +### 4.1 Font Family + +**Primary: Pretendard** + +```css +font-family: "Pretendard", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Helvetica Neue", system-ui, sans-serif; +``` + +**선택 이유:** + +- 한글 가독성이 뛰어남 +- 다양한 Font Weight 지원 (100~900) +- 모던하고 깔끔한 디자인 +- Variable Font 지원으로 성능 최적화 + +### 4.2 Type Scale (Mobile First) + +``` +Display (메인 타이틀) +- Size: 28px +- Weight: 700 (Bold) +- Line Height: 1.3 (36px) +- Letter Spacing: -0.5px +- 사용: 메인 대시보드 타이틀, 랜딩 화면 + +H1 (화면 제목) +- Size: 24px +- Weight: 700 (Bold) +- Line Height: 1.3 (31px) +- Letter Spacing: -0.3px +- 사용: 각 화면의 메인 제목 + +H2 (섹션 제목) +- Size: 20px +- Weight: 700 (Bold) +- Line Height: 1.4 (28px) +- Letter Spacing: -0.2px +- 사용: 카드 그룹, 섹션 구분 + +H3 (카드 제목) +- Size: 18px +- Weight: 600 (SemiBold) +- Line Height: 1.4 (25px) +- Letter Spacing: 0px +- 사용: 카드 제목, 모달 제목 + +Body-Large (큰 본문) +- Size: 16px +- Weight: 400 (Regular) +- Line Height: 1.5 (24px) +- Letter Spacing: 0px +- 사용: 입력 필드, 중요한 본문 + +Body-Medium (기본 본문) +- Size: 14px +- Weight: 400 (Regular) +- Line Height: 1.5 (21px) +- Letter Spacing: 0px +- 사용: 일반 본문, 설명 텍스트 + +Body-Small (작은 본문) +- Size: 12px +- Weight: 400 (Regular) +- Line Height: 1.5 (18px) +- Letter Spacing: 0px +- 사용: 캡션, 보조 정보, 메타 데이터 + +Button (버튼 레이블) +- Size: 16px +- Weight: 600 (SemiBold) +- Line Height: 1.5 (24px) +- Letter Spacing: 0px +- 사용: 모든 버튼 텍스트 +``` + +### 4.3 Font Weights + +``` +Regular (400): 일반 본문, 설명 +Medium (500): 강조하고 싶은 본문 +SemiBold (600): 버튼, 중요 정보 +Bold (700): 제목, 헤딩 +``` + +### 4.4 Responsive Typography + +**Tablet (768px~)** + +``` +Display: 32px (+4px) +H1: 28px (+4px) +H2: 22px (+2px) +H3: 20px (+2px) +Body-L: 18px (+2px) +Body-M: 16px (+2px) +Body-S: 14px (+2px) +Button: 16px (유지) +``` + +**Desktop (1024px~)** + +``` +Display: 36px (+8px) +H1: 32px (+8px) +H2: 24px (+4px) +H3: 20px (+2px) +Body-L: 18px (+2px) +Body-M: 16px (+2px) +Body-S: 14px (+2px) +Button: 16px (유지) +``` + +--- + +## 5. 간격 시스템 (Spacing) + +### 5.1 Base Unit + +**4px Grid System** - 모든 간격은 4의 배수 + +### 5.2 Spacing Scale + +``` +XS (Extra Small): 4px // 매우 작은 요소 간 (아이콘-텍스트) +S (Small): 8px // 관련 요소 간 (레이블-입력, 버튼 내부) +M (Medium): 16px // 섹션 내 요소 간 (카드 내 항목) +L (Large): 24px // 카드 내부 패딩, 섹션 제목 하단 +XL (Extra Large): 32px // 섹션 간 간격 +2XL (2X Large): 48px // 화면 상하단 여백 +``` + +### 5.3 Component Spacing + +#### Button + +``` +Padding (세로 x 가로): +- Large: 16px x 24px (높이 48px) +- Medium: 12px x 20px (높이 44px) +- Small: 8px x 16px (높이 36px) + +Button 간 간격: 12px (S + XS) +``` + +#### Card + +``` +내부 패딩: 24px (L) +카드 간 간격: 16px (M) +``` + +#### Input Field + +``` +내부 패딩: 16px (M) +레이블-입력 간격: 8px (S) +입력 필드 간 간격: 16px (M) +``` + +#### Screen Margins + +``` +Mobile: 20px (양쪽) +Tablet: 40px (양쪽) +Desktop: 80px (양쪽, 최대 1200px container) +``` + +### 5.4 Touch Target + +**WCAG 2.1 AA 준수** + +``` +최소 터치 영역: 44 x 44px +권장 터치 영역: 48 x 48px + +적용 대상: +- 모든 버튼 +- 탭 항목 +- 체크박스, 라디오 버튼 +- 링크가 있는 카드 +``` + +--- + +## 6. 컴포넌트 스타일 + +### 6.1 Button (버튼) + +#### Primary Button + +``` +배경: #E31E24 (KT Red) +텍스트: #FFFFFF (White) +둥근 모서리: 8px +그림자: 0 2px 4px rgba(227, 30, 36, 0.2) + +상태별: +- Default: 배경 #E31E24 +- Hover: 배경 #FF4D52 (10% 밝게) +- Pressed: 배경 #C71820 (10% 어둡게) +- Disabled: 배경 #D9D9D9, 텍스트 #9E9E9E +``` + +#### Secondary Button + +``` +배경: #FFFFFF (White) +텍스트: #E31E24 (KT Red) +테두리: 2px solid #E31E24 +둥근 모서리: 8px + +상태별: +- Default: 테두리 #E31E24 +- Hover: 배경 #FFF5F5 (5% Red tint) +- Pressed: 배경 #FFEBEB (10% Red tint) +- Disabled: 테두리 #D9D9D9, 텍스트 #9E9E9E +``` + +#### Text Button + +``` +배경: 없음 +텍스트: #E31E24 (KT Red) +둥근 모서리: 8px + +상태별: +- Default: 텍스트 #E31E24 +- Hover: 배경 #FFF5F5 +- Pressed: 배경 #FFEBEB +- Disabled: 텍스트 #9E9E9E +``` + +#### Button Sizes + +``` +Large: +- 높이: 48px +- 패딩: 16px x 24px +- 폰트: Button (16px SemiBold) +- 사용: 주요 CTA + +Medium: +- 높이: 44px +- 패딩: 12px x 20px +- 폰트: Body-M (14px SemiBold) +- 사용: 일반 액션 + +Small: +- 높이: 36px +- 패딩: 8px x 16px +- 폰트: Body-S (12px SemiBold) +- 사용: 보조 액션 +``` + +### 6.2 Card (카드) + +#### Default Card + +``` +배경: #FFFFFF (White) +테두리: 1px solid #E0E0E0 +둥근 모서리: 12px +그림자: 0 2px 8px rgba(0, 0, 0, 0.08) +내부 패딩: 24px + +상태별: +- Default: 테두리 #E0E0E0 +- Hover: 테두리 #E31E24, 그림자 0 4px 12px rgba(227, 30, 36, 0.12) +- Selected: 테두리 2px solid #E31E24 +``` + +#### Event Card (이벤트 카드) + +``` +배경: #FFFFFF +둥근 모서리: 12px +그림자: 0 2px 8px rgba(0, 0, 0, 0.08) + +구조: +┌─────────────────────────┐ +│ [이미지 썸네일 16:9] │ +├─────────────────────────┤ +│ H3 제목 │ +│ Body-S 메타 정보 │ +│ [상태 배지] [CTA 버튼] │ +└─────────────────────────┘ +``` + +#### Selection Card (선택형 카드) + +``` +사용: 옵션 선택 화면 (09-콘텐츠미리보기.html) +특징: 카드 전체가 선택 가능한 인터랙티브 영역 + +배경: #FFFFFF +테두리: 3px solid transparent +둥근 모서리: 12px +그림자: 0 2px 8px rgba(0, 0, 0, 0.08) +내부 패딩: 24px +커서: pointer + +상태별: +- Default: + - 테두리 transparent + - 그림자 0 2px 8px rgba(0, 0, 0, 0.08) + +- Hover: + - transform: translateY(-2px) + - 그림자 0 8px 16px rgba(0, 0, 0, 0.1) + +- Selected: + - 테두리 3px solid #E31E24 + - 그림자 0 4px 12px rgba(227, 30, 36, 0.2) + - 우측 상단 체크 배지 표시 + +구조: +┌─────────────────────────┐ +│ [✓] │ ← 선택 배지 (조건부) +│ [미리보기 이미지 1:1] │ +│ │ +│ H3 옵션 제목 │ +│ Body-S 설명 │ +│ │ +│ [크게보기] │ +└─────────────────────────┘ + +Selected Badge (체크 배지): +- 위치: 우측 상단 (absolute) +- 크기: 32 x 32px +- 배경: #E31E24 +- 아이콘: check (White, 20px) +- 둥근 모서리: 50% (원형) +- Display: none (기본), flex (선택 시) +- z-index: 10 + +Image Preview: +- 비율: 1:1 (aspect-ratio) +- 배경: #F5F5F5 (플레이스홀더) +- 둥근 모서리: 12px +- object-fit: cover + +Radio Button: +- Display: none (숨김) +- 기능: 유지 (폼 제출용) +- 접근성: 키보드 네비게이션 지원 + +전환 애니메이션: +- Duration: 0.3s +- Easing: ease + +주의사항: +- 카드 내부 버튼 클릭 시 이벤트 버블링 방지 필요 + - event.stopPropagation() 사용 +- 카드 클릭과 보조 액션 버튼 클릭 구분 +- 키보드 접근성: Enter/Space로 선택 가능 +``` + +#### Stat Card (지표 카드) + +``` +배경: Gradient 또는 Solid +둥근 모서리: 16px +내부 패딩: 24px + +구조: +┌─────────────────────────┐ +│ [아이콘] Body-S 레이블 │ +│ Display 수치 │ +│ Body-S 변화율 +32% ↑ │ +└─────────────────────────┘ +``` + +### 6.3 Input Field (입력 필드) + +#### Text Input + +``` +배경: #FFFFFF (White) +테두리: 1px solid #D9D9D9 +둥근 모서리: 8px +높이: 48px +내부 패딩: 16px +폰트: Body-L (16px Regular) + +상태별: +- Default: 테두리 #D9D9D9 +- Focus: 테두리 2px solid #0066FF, 그림자 0 0 0 4px rgba(0, 102, 255, 0.1) +- Error: 테두리 2px solid #D32F2F, 그림자 0 0 0 4px rgba(211, 47, 47, 0.1) +- Disabled: 배경 #F5F5F5, 테두리 #E0E0E0, 텍스트 #9E9E9E +- Filled: 배경 유지, 텍스트 #1A1A1A +``` + +#### Textarea + +``` +배경: #FFFFFF +테두리: 1px solid #D9D9D9 +둥근 모서리: 8px +최소 높이: 120px (약 5줄) +내부 패딩: 16px +폰트: Body-M (14px Regular) +Line Height: 1.5 + +Resize: vertical (세로 방향만) +``` + +#### Editable Field (인라인 편집) + +``` +사용: 08-AI이벤트추천.html (제목, 경품 편집) +특징: contenteditable을 활용한 인라인 편집 + +배경: 투명 (기본), #F5F5F5 (hover), #FFFFFF (focus) +테두리: 1px dashed #D9D9D9 +둥근 모서리: 4px +내부 패딩: 4px 8px +폰트: 문맥에 따름 (제목: H3, 경품: Body) +커서: text + +상태별: +- Default: + - 테두리: 1px dashed #D9D9D9 + - 배경: 투명 + +- Hover: + - 테두리: 1px dashed #E31E24 + - 배경: #F5F5F5 + +- Focus: + - outline: none + - 테두리: 1px solid #E31E24 + - 배경: #FFFFFF + +전환 애니메이션: +- Duration: 200ms (Fast) +- Easing: ease-out + +접근성: +- contenteditable="true" +- role="textbox" +- aria-label="편집 가능한 필드명" + +주의사항: +- 빈 값 방지 (최소 1자 이상) +- Enter 키로 편집 완료 (blur) +- maxLength 속성으로 길이 제한 +``` + +#### Placeholder + +``` +색상: #9E9E9E (Gray-500) +폰트: Body-L (16px Regular) +스타일: italic (선택적) +``` + +### 6.4 Checkbox & Radio + +#### Checkbox + +``` +크기: 24 x 24px (터치 영역 44px) +둥근 모서리: 4px +테두리: 2px solid #D9D9D9 + +상태별: +- Unchecked: 배경 White, 테두리 #D9D9D9 +- Checked: 배경 #E31E24, 체크마크 White +- Disabled: 배경 #F5F5F5, 테두리 #E0E0E0 +``` + +#### Radio Button + +``` +크기: 24 x 24px (터치 영역 44px) +둥근 모서리: 50% (원형) +테두리: 2px solid #D9D9D9 + +상태별: +- Unselected: 배경 White, 테두리 #D9D9D9 +- Selected: 테두리 #E31E24, 내부 점 12px #E31E24 +- Disabled: 배경 #F5F5F5, 테두리 #E0E0E0 +``` + +### 6.5 Budget Navigation (예산 선택 탭) + +``` +사용: 08-AI이벤트추천.html +특징: Sticky 네비게이션으로 예산 섹션 이동 + +배경: #F5F5F5 (배경색) +위치: sticky, top 56px (Header 아래) +z-index: 10 +패딩: 16px 0 + +구조: +┌────────────────────────────────┐ +│ [💰 저비용] [💰💰 중비용] [💰💰💰 고비용] │ +└────────────────────────────────┘ + +버튼: +- 크기: Medium (44px 높이) +- 간격: 8px +- flex-1 (균등 분배) + +상태별: +- Default: + - 배경: White + - 텍스트: #4A4A4A + - 테두리: 1px solid #E0E0E0 + +- Active: + - 배경: #E31E24 + - 텍스트: White + - 테두리: none + - 그림자: 0 2px 4px rgba(227, 30, 36, 0.2) + +- Hover (비활성화 탭): + - 배경: #FFF5F5 + - 테두리: 1px solid #E31E24 + +상호작용: +- 클릭 시: 해당 예산 섹션으로 smooth scroll +- Scroll 시: 현재 보이는 섹션에 맞춰 Active 상태 변경 +``` + +### 6.6 Option Card (이벤트 옵션 카드) + +``` +사용: 08-AI이벤트추천.html +특징: 온라인/오프라인 배지 + Editable Field + Radio 선택 + +배경: #FFFFFF +테두리: 1px solid #E0E0E0 +둥근 모서리: 12px +그림자: 0 2px 8px rgba(0, 0, 0, 0.08) +내부 패딩: 24px + +구조: +┌─────────────────────────┐ +│ [온라인] 배지 [◯] │ +│ │ +│ 제목 (editable) │ +│ 경품명 (editable) │ +│ │ +│ 참여 방법: ... │ +│ │ +│ 📊 예상 참여자: 200명 │ +│ 💰 예상 비용: 30만원 │ +│ 📈 예상 ROI: 180% │ +└─────────────────────────┘ + +온라인/오프라인 배지: +- 크기: 패딩 4px 12px +- 폰트: Body-S (12px SemiBold) +- 둥근 모서리: 12px (pill) +- 온라인: 배경 #E3F2FD (Blue), 텍스트 #0066FF +- 오프라인: 배경 #FCE4EC (Pink), 텍스트 #E31E24 + +Radio 버튼: +- 위치: 우측 상단 +- 크기: 24 x 24px +- Display: visible (기본 보임) + +Editable Field: +- 스타일: 인라인 편집 (상세 내용은 6.3 참조) +- Hover: 점선 테두리로 편집 가능 표시 +- Focus: 실선 테두리로 변경 + +통계 정보: +- 폰트: Body-S (12px Regular) +- 색상: #4A4A4A (Secondary) +- 아이콘: Material Icons (16px) +``` + +### 6.7 Bottom Navigation + +``` +배경: #FFFFFF (White) +높이: 60px +그림자: 0 -2px 8px rgba(0, 0, 0, 0.08) +아이템 개수: 4개 (홈, 이벤트, 분석, 마이) + +아이템 구조: +┌─────────────┐ +│ [아이콘] │ +│ 레이블 │ +└─────────────┘ + +아이콘: +- 크기: 24 x 24px +- 스타일: Outlined (기본), Filled (활성화) + +레이블: +- 폰트: Body-S (12px Regular) + +색상: +- 비활성화: 아이콘/텍스트 #9E9E9E +- 활성화: 아이콘/텍스트 #E31E24 +- 배경: 활성화 시 투명 + +간격: +- 아이콘-레이블: 4px (XS) +``` + +### 6.8 Stepper (단계 표시) + +#### Progress Stepper (AI 이벤트 생성) + +``` +전체 높이: 48px +배경: #F5F5F5 +진행률 바: #E31E24 +둥근 모서리: 24px + +구조: +┌─────────────────────────────┐ +│ ████████░░░░░░░░ 3/7 단계 │ +└─────────────────────────────┘ + +텍스트: +- 폰트: Body-M (14px SemiBold) +- 색상: #1A1A1A +``` + +#### Step Indicator (단계별 표시) + +``` +┌────┐ ┌────┐ ┌────┐ +│ ✓ │─ │ 2 │─ │ 3 │ +└────┘ └────┘ └────┘ +완료 진행중 대기 + +원 크기: 32px +선 두께: 2px +간격: 8px (선 길이 가변) + +색상: +- 완료: 배경 #00C853, 아이콘 White +- 진행중: 배경 #E31E24, 숫자 White +- 대기: 배경 #F5F5F5, 테두리 #D9D9D9, 숫자 #9E9E9E +``` + +--- + +## 7. 반응형 브레이크포인트 + +### 7.1 Breakpoints + +``` +Mobile (기본): +- Range: 320px ~ 767px +- Container: 100% - 40px (양쪽 20px 마진) +- Columns: 4 columns +- Gutter: 16px +- 레이아웃: 1열 스택 + +Tablet: +- Range: 768px ~ 1023px +- Container: 100% - 80px (양쪽 40px 마진) +- Columns: 8 columns +- Gutter: 24px +- 레이아웃: 2열 그리드 + +Desktop: +- Range: 1024px 이상 +- Container: Max 1200px (중앙 정렬) +- Columns: 12 columns +- Gutter: 32px +- 레이아웃: 3열 그리드 + 사이드바 +``` + +### 7.2 CSS Media Queries + +```css +/* Mobile First - 기본 스타일 */ +.component { + /* 320px ~ 767px */ +} + +/* Tablet */ +@media (min-width: 768px) { + .component { + /* Tablet 스타일 */ + } +} + +/* Desktop */ +@media (min-width: 1024px) { + .component { + /* Desktop 스타일 */ + } +} +``` + +### 7.3 Grid System + +#### Mobile Grid (4 Columns) + +``` +┌─────┬─────┬─────┬─────┐ +│ 1 │ 2 │ 3 │ 4 │ +└─────┴─────┴─────┴─────┘ + +사용 예: +- Full Width: span 4 +- Half Width: span 2 +``` + +#### Tablet Grid (8 Columns) + +``` +┌──┬──┬──┬──┬──┬──┬──┬──┐ +│ 1│ 2│ 3│ 4│ 5│ 6│ 7│ 8│ +└──┴──┴──┴──┴──┴──┴──┴──┘ + +사용 예: +- Full Width: span 8 +- Half Width: span 4 (2개 카드) +- 1/3 Width: span 2-3 (3개 카드) +``` + +#### Desktop Grid (12 Columns) + +``` +┌┬┬┬┬┬┬┬┬┬┬┬┬┐ +│││││││││││││ +└┴┴┴┴┴┴┴┴┴┴┴┴┘ + +사용 예: +- Main Content: span 8-9 (왼쪽) +- Sidebar: span 3-4 (오른쪽) +- 3 Column Cards: span 4 each +``` + +--- + +## 8. 서비스 특화 컴포넌트 + +### 8.1 AI 처리 상태 컴포넌트 + +#### Loading Skeleton + +``` +배경: #F5F5F5 +애니메이션: Shimmer (좌→우 반짝임) +Duration: 1.5s infinite + +┌─────────────────────────┐ +│ ▓▓▓▓░░░░░░░░░░░░░░░░░░ │ ← 제목 +│ ░░░░░░░░░░░░░░░░░░░░░░ │ ← 본문 +│ ░░░░░░░░░░░░░░░░░░░░░░ │ +│ ▓▓▓░░░ ▓▓▓░░░ ▓▓▓░░ │ ← 버튼들 +└─────────────────────────┘ + +사용: AI 이미지 생성, 트렌드 분석 등 +``` + +#### Progress Indicator (AI 단계별) + +``` +┌─────────────────────────────┐ +│ 🤖 AI가 트렌드를 분석중입니다 │ +│ │ +│ ████████░░░░░░░░░░░░ 35% │ +│ │ +│ 예상 시간: 약 2초 남음 │ +└─────────────────────────────┘ + +배경: White +테두리: 1px solid #E0E0E0 +진행바: #0066FF (AI Blue) +텍스트: Body-M #4A4A4A +``` + +#### Spinner (간단한 로딩) + +``` +크기: 32px +두께: 3px +색상: #E31E24 (Primary) 또는 #0066FF (AI Blue) +애니메이션: rotate 360deg, 0.8s linear infinite + +사용: 버튼 내부, 작은 액션 로딩 +``` + +### 8.2 AI 결과 카드 + +#### AI 생성 옵션 카드 (선택형) + +``` +┌─────────────────────────────┐ +│ ◯ [이미지 미리보기] │ +│ │ +│ H3 옵션 제목 │ +│ Body-S 설명 │ +│ [재생성 버튼] │ +└─────────────────────────────┘ + +상태별: +- 비선택: 테두리 1px #E0E0E0 +- 선택: 테두리 2px #E31E24, 배경 #FFF5F5 +- 호버: 그림자 0 4px 12px rgba(0,0,0,0.1) + +라디오 버튼: +- 위치: 좌상단 또는 우상단 +- 크기: 24px +``` + +#### AI 추천 배지 + +``` +배경: linear-gradient(135deg, #0066FF 0%, #4D94FF 100%) +텍스트: White +폰트: Body-S (12px SemiBold) +둥근 모서리: 12px (pill 형태) +패딩: 4px 12px +위치: 카드 좌상단 절대 배치 + +텍스트: "AI 추천" 또는 "인기" +``` + +### 8.3 Real-time Dashboard Components + +#### KPI Card (핵심 지표) + +``` +┌─────────────────────────┐ +│ 📊 참여자 수 │ +│ │ +│ 152명 │ +│ +32% ↑ 전주 대비 │ +└─────────────────────────┘ + +배경: White +둥근 모서리: 16px +그림자: 0 2px 8px rgba(0,0,0,0.08) +패딩: 24px + +구조: +- 아이콘 + 레이블: Body-S #4A4A4A +- 수치: Display (28px Bold) #1A1A1A +- 변화율: Body-S, 증가 #00C853, 감소 #D32F2F +``` + +#### Chart Container + +``` +배경: White +둥근 모서리: 12px +그림자: 0 2px 8px rgba(0,0,0,0.08) +패딩: 24px + +차트 색상: +- Primary Line: #E31E24 +- Secondary Line: #0066FF +- Grid: #F5F5F5 +- Axis Label: #9E9E9E +``` + +--- + +## 9. 인터랙션 패턴 + +### 9.1 Bottom Sheet + +``` +배경: White +둥근 모서리: 24px 24px 0 0 (상단만) +최대 높이: 80vh +Handle: 40px x 4px, #D9D9D9, 상단 중앙 +그림자: 0 -4px 12px rgba(0,0,0,0.15) + +애니메이션: +- Open: transform translateY(100% → 0), 300ms ease-out +- Close: transform translateY(0 → 100%), 250ms ease-in + +Backdrop: +- 배경: rgba(0,0,0,0.5) +- 클릭 시: Bottom Sheet 닫힘 +``` + +### 9.2 Toast (알림) + +``` +┌─────────────────────────────┐ +│ ✓ 이벤트가 성공적으로 저장되었습니다 │ +└─────────────────────────────┘ + +배경: #1A1A1A (90% opacity) +텍스트: White +둥근 모서리: 8px +패딩: 16px 24px +위치: 하단 중앙, Bottom Navigation 위 80px +자동 닫힘: 3초 + +애니메이션: +- Show: opacity 0→1, translateY(20px→0), 200ms +- Hide: opacity 1→0, 200ms + +타입별 아이콘: +- Success: ✓ (#00C853) +- Error: ✕ (#D32F2F) +- Info: ⓘ (#0288D1) +``` + +### 9.3 Modal (모달 다이얼로그) + +``` +┌─────────────────────────────┐ +│ H2 제목 [✕] │ +├─────────────────────────────┤ +│ │ +│ Body-M 본문 내용 │ +│ │ +├─────────────────────────────┤ +│ [취소 버튼] [확인 버튼] │ +└─────────────────────────────┘ + +배경: White +둥근 모서리: 16px +최대 너비: 400px (Mobile), 480px (Tablet+) +패딩: 24px +그림자: 0 8px 24px rgba(0,0,0,0.2) + +Backdrop: +- 배경: rgba(0,0,0,0.6) +- 클릭 시: 모달 닫힘 (확인 필요한 경우 제외) + +애니메이션: +- Show: opacity 0→1, scale(0.95→1), 250ms ease-out +- Hide: opacity 1→0, scale(1→0.95), 200ms ease-in +``` + +### 9.4 Pull to Refresh + +``` +상태 표시: +1. Pull Down (당기기): + - 아이콘: ↓ 회전 + - 텍스트: "당겨서 새로고침" + +2. Release to Refresh (놓아서 새로고침): + - 아이콘: ↻ 회전 + - 텍스트: "놓아서 새로고침" + +3. Refreshing (새로고침 중): + - Spinner 표시 + - 텍스트: "새로고침 중..." + +색상: +- 아이콘/텍스트: #9E9E9E +- Spinner: #E31E24 + +위치: 화면 최상단 +높이: 60px +``` + +### 9.5 Swipe Actions (카드 스와이프) + +``` +좌측 스와이프 (삭제): +┌─────────────────────────────┐ +│ ◀ 삭제 │ +└─────────────────────────────┘ +배경: #D32F2F +아이콘: 🗑️ White +텍스트: White Body-M + +우측 스와이프 (편집): +┌─────────────────────────────┐ +│ 편집 ▶ │ +└─────────────────────────────┘ +배경: #0066FF +아이콘: ✏️ White +텍스트: White Body-M + +Threshold: 30% 너비 이상 스와이프 시 액션 트리거 +``` + +### 9.6 Drag and Drop + +``` +드래그 중 상태: +- 원본 카드: opacity 0.5 +- 드래그 카드: 그림자 0 8px 16px rgba(0,0,0,0.2), scale(1.05) +- Drop Zone: 배경 #F5F5F5, 테두리 2px dashed #E31E24 + +드롭 가능 영역: +- 배경: #FFF5F5 (light red tint) +- 테두리: 2px dashed #E31E24 +``` + +--- + +## 10. 애니메이션 가이드라인 + +### 10.1 Duration (지속 시간) + +``` +즉시 (Instant): 0ms // 색상 변화 +매우 빠름 (Very Fast): 100ms // Hover 효과 +빠름 (Fast): 200ms // Toast 등장 +일반 (Normal): 300ms // Modal, Bottom Sheet +느림 (Slow): 500ms // Page Transition +``` + +### 10.2 Easing (가속도) + +``` +Ease-Out (감속): +- cubic-bezier(0, 0, 0.2, 1) +- 사용: 요소 등장 (Modal, Sheet, Toast) + +Ease-In (가속): +- cubic-bezier(0.4, 0, 1, 1) +- 사용: 요소 퇴장 + +Ease-In-Out (가속+감속): +- cubic-bezier(0.4, 0, 0.2, 1) +- 사용: 전환 효과 (Tab 전환) + +Linear (일정): +- linear +- 사용: 무한 회전 (Spinner, Loading) +``` + +### 10.3 주요 애니메이션 + +#### Page Transition + +``` +진입: +- opacity: 0 → 1 +- transform: translateX(20px) → translateX(0) +- duration: 300ms +- easing: ease-out + +퇴장: +- opacity: 1 → 0 +- transform: translateX(0) → translateX(-20px) +- duration: 250ms +- easing: ease-in +``` + +#### Card Hover + +``` +transform: translateY(0) → translateY(-4px) +box-shadow: 증가 (0 2px 8px → 0 8px 16px) +duration: 200ms +easing: ease-out +``` + +#### Button Press + +``` +transform: scale(1) → scale(0.95) +duration: 100ms +easing: ease-out +``` + +--- + +## 11. 아이콘 시스템 + +### 11.1 Icon Style + +**Material Icons Outlined** (기본) +**Material Icons Filled** (활성화 상태) + +``` +스타일 특징: +- Outlined: 선 두께 2px, 심플하고 깔끔 +- Filled: 활성화 시 직관적 구분 +``` + +### 11.2 Icon Sizes + +``` +Small: 16 x 16px // 텍스트 내 인라인 +Medium: 24 x 24px // 버튼, 네비게이션 (기본) +Large: 32 x 32px // 헤더 액션 +XLarge: 48 x 48px // Empty State 일러스트 +``` + +### 11.3 Icon Colors + +``` +Default: #4A4A4A (Gray-700) +Active: #E31E24 (Primary Red) +Disabled: #9E9E9E (Gray-500) +On Color: #FFFFFF (White) - 색상 버튼 위 + +AI Feature: #0066FF (AI Blue) +Success: #00C853 +Warning: #FFA000 +Error: #D32F2F +``` + +### 11.4 주요 아이콘 매핑 + +``` +홈: home +이벤트: event / campaign +분석: analytics / bar_chart +마이: person / account_circle +추가: add / add_circle +알림: notifications +설정: settings +검색: search +필터: filter_list +정렬: sort +공유: share +다운로드: download +업로드: upload +편집: edit +삭제: delete +닫기: close +뒤로: arrow_back +앞으로: arrow_forward +체크: check / check_circle +오류: error / cancel +정보: info +경고: warning +도움말: help / help_outline + +AI 관련: +트렌드: trending_up +경품: card_giftcard +이미지: image / photo +영상: videocam +SNS: share / language +QR: qr_code +달력: calendar_today +시간: schedule +위치: place +참여자: group / people +``` + +--- + +## 12. 접근성 가이드라인 + +### 12.1 색상 대비 (WCAG 2.1 AA) + +``` +텍스트: +- 일반 텍스트 (14px+): 4.5:1 이상 +- 큰 텍스트 (18px+ or 14px+ Bold): 3:1 이상 + +UI 요소: +- 버튼, 입력 필드, 아이콘: 3:1 이상 + +검증된 조합: +✓ #1A1A1A (Black) on #FFFFFF (White): 14.2:1 +✓ #4A4A4A (Gray-700) on #FFFFFF: 8.5:1 +✓ #E31E24 (KT Red) on #FFFFFF: 7.2:1 +✓ #0066FF (AI Blue) on #FFFFFF: 7.8:1 +✓ #FFFFFF (White) on #E31E24 (Red): 7.2:1 +✗ #9E9E9E (Gray-500) on #FFFFFF: 4.6:1 (작은 텍스트 부적합) +``` + +### 12.2 키보드 네비게이션 + +``` +Tab Order: +- 논리적 순서 (상→하, 좌→우) +- 모든 인터랙티브 요소 접근 가능 + +Focus Indicator: +- 테두리: 2px solid #0066FF +- Offset: 2px +- 둥근 모서리: 버튼과 동일 + +Focus Trap: +- Modal, Bottom Sheet 열림 시 내부만 탭 이동 +- ESC 키로 닫기 가능 +``` + +### 12.3 스크린 리더 + +``` +ARIA Labels: +- 모든 버튼에 명확한 레이블 +- 아이콘 버튼: aria-label="설정 열기" +- 이미지: alt="AI가 생성한 홍보 이미지" + +Role & State: +- role="button", role="dialog" +- aria-expanded="true/false" +- aria-selected="true/false" +- aria-disabled="true/false" + +Live Regions: +- Toast: aria-live="polite" +- Error: aria-live="assertive" +``` + +### 12.4 터치 영역 + +``` +최소 크기: 44 x 44px (WCAG 2.1 AA) +권장 크기: 48 x 48px + +충분한 간격: +- 인접 터치 요소 간: 8px 이상 +``` + +### 12.5 대안 제공 + +``` +색상만으로 정보 전달 금지: +✗ 나쁜 예: 빨간색만으로 오류 표시 +✓ 좋은 예: 빨간색 + ✕ 아이콘 + "오류" 텍스트 + +드래그 앤 드롭 대안: +- 버튼 클릭으로도 순서 변경 가능 +- 키보드로 순서 조정 가능 +``` + +--- + +## 13. 성능 최적화 + +### 13.1 이미지 최적화 + +``` +포맷: +- 사진: WebP (fallback: JPG) +- 일러스트: SVG 또는 PNG +- 아이콘: SVG Sprite + +압축: +- JPG: Quality 80-85% +- PNG: TinyPNG 또는 ImageOptim + +Lazy Loading: +- 스크롤 시 로드 (Intersection Observer) +- 중요 이미지: eager loading +``` + +### 13.2 폰트 최적화 + +``` +Font Loading: +- font-display: swap +- Preload 주요 폰트 + +Subsetting: +- 한글: 자주 쓰는 글자만 (Subset) +- Variable Font 사용 (Pretendard Variable) + +WOFF2 우선: +@font-face { + font-family: 'Pretendard'; + src: url('/fonts/Pretendard-Variable.woff2') format('woff2-variations'); + font-display: swap; +} +``` + +### 13.3 애니메이션 성능 + +``` +GPU 가속 사용: +- transform: translate3d(), scale3d() +- opacity +- filter + +피해야 할 속성: +- width, height (Reflow 발생) +- top, left (Reflow 발생) +- background-position + +Will-Change 사용: +will-change: transform, opacity; +(애니메이션 직전에만 적용) +``` + +--- + +## 14. 다크 모드 (향후 지원) + +### 14.1 색상 매핑 (참고용) + +``` +Light Mode → Dark Mode + +배경: +#FFFFFF → #121212 +#F5F5F5 → #1E1E1E + +텍스트: +#1A1A1A → #E0E0E0 +#4A4A4A → #B0B0B0 +#9E9E9E → #707070 + +카드: +#FFFFFF → #1E1E1E +border #E0E0E0 → #2C2C2C + +Primary (유지): +#E31E24 → #E31E24 (동일) + +Secondary: +#0066FF → #4D94FF (밝게 조정) +``` + +--- + +## 15. 변경 이력 + +### Version 1.1 (2025-10-21) + +- 프로토타입 기반 컴포넌트 업데이트 +- Editable Field 컴포넌트 추가 (인라인 편집) +- Budget Navigation 컴포넌트 추가 (Sticky 탭 네비게이션) +- Option Card 컴포넌트 추가 (온라인/오프라인 배지) +- Selection Card 세부사항 보완 (이미지 비율, z-index) +- 이벤트 버블링 방지 가이드 추가 + +### Version 1.0 (2025-10-17) + +- 초안 작성 +- 브랜드 아이덴티티 정의 +- 디자인 원칙 5가지 수립 +- 색상 시스템 (Primary/Secondary/Grayscale/Semantic) 정의 +- 타이포그래피 시스템 (Pretendard, 8단계 스케일) 정의 +- 간격 시스템 (4px 기반, 6단계) 정의 +- 컴포넌트 스타일 (Button/Card/Input/Navigation) 정의 +- 반응형 브레이크포인트 (Mobile/Tablet/Desktop) 정의 +- AI 특화 컴포넌트 (로딩/결과/Stepper) 정의 +- 인터랙션 패턴 (BottomSheet/Toast/Modal/Swipe) 정의 +- 애니메이션 가이드라인 정의 +- 아이콘 시스템 (Material Icons) 정의 +- 접근성 가이드라인 (WCAG 2.1 AA) 정의 +- 성능 최적화 가이드 정의 + +--- + +## 16. 참고 자료 + +- UI/UX 설계서: design/uiux/uiux.md +- 유저스토리: design/userstory.md +- KT 사장님Easy: https://product.kt.com/wDic/soho/marketing.do?itemCode=sajangeasy +- wwit.design 닷슬래시대시: https://wwit.design/2023/09/30/dotslashdash/ +- Material Design Icons: https://fonts.google.com/icons +- WCAG 2.1 Guidelines: https://www.w3.org/WAI/WCAG21/quickref/ +- Pretendard Font: https://github.com/orioncactus/pretendard + +--- + +**문서 끝** diff --git a/design/frontend/uiux-design.md b/design/frontend/uiux-design.md new file mode 100644 index 0000000..982a465 --- /dev/null +++ b/design/frontend/uiux-design.md @@ -0,0 +1,502 @@ +# KT AI 기반 소상공인 이벤트 자동 생성 서비스 - 프론트엔드 UI/UX 설계서 + +## 문서 정보 +- **작성일**: 2025-10-24 +- **버전**: 1.0 +- **기술 스택**: Next.js 14 + React 18 + TypeScript 5 +- **기반 문서**: design/uiux/uiux-design.md, 프로토타입 분석 +- **프로토타입**: design/uiux/prototype/\*.html + +--- + +## 1. UI/UX 설계 + +### 1.1 UI 프레임워크 선택 + +**선택한 프레임워크**: Material-UI (MUI) v6 + +**선택 이유**: +- React 18과 Next.js 14와의 완벽한 호환성 +- TypeScript 기본 지원 +- 모바일 우선 반응형 디자인 (Mobile First) +- 접근성 (WCAG 2.1 AA) 기본 준수 +- 풍부한 컴포넌트 라이브러리 +- KT 브랜드 컬러 커스터마이징 용이 + +**대안 고려**: +- Ant Design: 한국어 지원 우수하나 파일 크기가 큼 +- Chakra UI: 경량이나 기업 디자인 시스템에 부적합 +- Tailwind CSS + HeadlessUI: 커스터마이징은 우수하나 개발 속도 느림 + +**최종 결정**: MUI의 컴포넌트 완성도와 기업용 UI 패턴 지원으로 선택 + +--- + +### 1.2 화면 목록 정의 + +#### 인증 영역 (4개) +| 화면 ID | 화면명 | URL | 설명 | +|---------|--------|-----|------| +| AUTH-01 | 로그인 | `/login` | 전화번호/비밀번호 로그인 | +| AUTH-02 | 회원가입 | `/register` | 소상공인 회원가입 (매장 정보 포함) | +| AUTH-03 | 프로필 | `/profile` | 사용자 및 매장 정보 관리 | +| AUTH-04 | 로그아웃 확인 | Modal | 로그아웃 확인 다이얼로그 | + +#### 대시보드 영역 (2개) +| 화면 ID | 화면명 | URL | 설명 | +|---------|--------|-----|------| +| DASH-01 | 대시보드 | `/` | KPI 요약, 빠른 시작, 진행 중 이벤트 | +| DASH-02 | 이벤트 목록 | `/events` | 전체 이벤트 목록 (필터, 검색, 페이징) | + +#### 이벤트 생성 플로우 (6개) +| 화면 ID | 화면명 | URL | 설명 | Funnel Step | +|---------|--------|-----|------|-------------| +| EVENT-01 | 이벤트 목적 선택 | `/events/create/objective` | 4가지 목적 선택 | Step 1 | +| EVENT-02 | AI 이벤트 추천 | `/events/create/recommendation` | AI 트렌드 분석 및 추천 | Step 2 | +| EVENT-03 | 콘텐츠 미리보기 | `/events/create/content` | SNS 이미지 스타일 선택 | Step 3 | +| EVENT-04 | 콘텐츠 편집 | `/events/create/edit` | 텍스트 및 이미지 편집 | Step 4 | +| EVENT-05 | 배포 채널 선택 | `/events/create/channels` | 배포 채널 선택 | Step 5 | +| EVENT-06 | 최종 승인 | `/events/create/publish` | 최종 검토 및 배포 | Step 6 | + +#### 이벤트 관리 및 모니터링 (5개) +| 화면 ID | 화면명 | URL | 설명 | +|---------|--------|-----|------| +| MANAGE-01 | 이벤트 상세 | `/events/[id]` | 이벤트 상세 정보 및 실시간 KPI | +| MANAGE-02 | 참여자 목록 | `/events/[id]/participants` | 참여자 관리 및 필터링 | +| MANAGE-03 | 이벤트 참여 (고객용) | `/participate/[id]` | 고객 이벤트 참여 화면 | +| MANAGE-04 | 당첨자 추첨 | `/events/[id]/draw` | 당첨자 추첨 및 관리 | +| MANAGE-05 | 성과 분석 | `/analytics` | 실시간 대시보드 및 성과 분석 | + +**총 17개 화면 (모달 포함)** + +--- + +### 1.3 화면 간 사용자 플로우 + +``` +[로그인] → [대시보드] ←→ [Bottom Navigation] + ↓ + ┌──────┴──────┐ + ↓ ↓ + [이벤트 목록] [성과 분석] + ↓ ↓ + [이벤트 상세] → [참여자 목록] → [당첨자 추첨] + +[대시보드] → [FAB: 새 이벤트] → [이벤트 생성 Funnel] + ↓ + (Objective → AI추천 → 콘텐츠 → 편집 → 채널 → 승인) + ↓ + [이벤트 상세] + +[Bottom Navigation] +- 홈: 대시보드 +- 이벤트: 이벤트 목록 +- 분석: 성과 분석 +- 프로필: 프로필 +``` + +--- + +### 1.4 화면별 상세 설계 + +#### 1.4.1 AUTH-01: 로그인 + +**상세 기능**: +- 전화번호 (010XXXXXXXX) 입력 +- 비밀번호 입력 +- "로그인 유지" 체크박스 +- 로그인 버튼 (API: POST /users/login) +- 회원가입 링크 +- 비밀번호 찾기 링크 (향후 구현) + +**UI 구성요소**: +- Logo (KT 로고) +- TextField: phoneNumber (pattern 검증) +- TextField: password (type="password") +- Checkbox: "로그인 유지" +- Button: "로그인" (variant="contained", color="primary") +- Link: "회원가입", "비밀번호 찾기" + +**인터랙션**: +- 전화번호 입력 시 자동 하이픈 제거 +- 비밀번호 8자 이상 검증 +- Enter 키로 로그인 +- 로그인 성공 시 "/" 리다이렉트 +- 로그인 실패 시 에러 Toast 표시 + +--- + +#### 1.4.2 EVENT-01 ~ EVENT-06: 이벤트 생성 Funnel + +**Funnel 구현**: `@use-funnel/next` 사용 + +**Funnel 설계**: +```typescript +const steps = [ + 'objective', // 목적 선택 + 'recommendation', // AI 추천 + 'content', // 콘텐츠 미리보기 + 'edit', // 콘텐츠 편집 + 'channels', // 배포 채널 + 'publish' // 최종 승인 +] as const; + +// useFunnel 사용 +const [Funnel, state, setStep] = useFunnel({ + id: 'event-creation', + initial: 'objective' +}); +``` + +**Step 1: Objective (목적 선택)** + +*상세 기능*: +- 4개 목적 중 1개 선택 (Radio) + 1. 신규 고객 유치 + 2. 재방문 유도 + 3. 매출 증대 + 4. 인지도 향상 +- 선택 후 다음 버튼 활성화 +- API: POST /events/objectives + +*UI 구성요소*: +- Stepper: 1/6 진행 표시 +- OptionCard: 4개 (아이콘 + 제목 + 설명) +- Button: "다음" (하단 고정) + +*인터랙션*: +- 카드 클릭 시 Radio 선택 +- 선택 시 카드 강조 (border: KT Red) +- 다음 버튼 클릭 → API 호출 → eventId 저장 → Step 2 이동 + +--- + +**Step 2: AI Recommendation (AI 추천)** + +*상세 기능*: +- AI 트렌드 분석 결과 표시 (업종, 지역, 시즌) +- 예산별 추천 이벤트 (저/중/고) + - 각 예산당 온라인/오프라인 2가지 방식 제공 + - 총 6개 옵션 중 1개 선택 +- 이벤트명, 경품 인라인 편집 가능 +- 예산 네비게이션 (Sticky) +- API: POST /events/{eventId}/ai-recommendations + - Job 발행 → 폴링 (GET /jobs/{jobId}) + +*UI 구성요소*: +- TrendAnalysis Card: 3개 (업종/지역/시즌) +- BudgetNavigation: 3개 버튼 (Sticky) +- RecommendationCard: 6개 (온라인/오프라인 배지, 편집 가능 제목/경품) +- LoadingIndicator: AI 처리 중 (5초 예상) +- Button: "다음" + +*인터랙션*: +- 예산 네비게이션 클릭 → smooth scroll +- 이벤트명/경품 hover → 점선 테두리 (편집 가능 표시) +- 클릭 → 인라인 편집 (TextField 전환) +- Enter/Blur → 저장 +- AI Job 폴링 (5초 간격) +- 완료 시 추천 결과 표시 +- 1개 선택 → PUT /events/{eventId}/recommendations → Step 3 이동 + +--- + +**Step 3: Content (콘텐츠 미리보기)** + +*상세 기능*: +- 5가지 SNS 이미지 스타일 선택 + 1. 심플 + 2. 모던 + 3. 귀여움 + 4. 고급스러움 + 5. 트렌디 +- 각 스타일 미리보기 이미지 +- "크게보기" 버튼 → 전체화면 모달 +- API: POST /events/{eventId}/images + - Job 발행 → 폴링 (GET /jobs/{jobId}) + +*UI 구성요소*: +- StyleCard: 5개 (이미지 + 제목 + 설명 + "크게보기") +- SelectBadge: 우측 상단 (선택 시만 표시) +- FullscreenModal: 이미지 확대 +- LoadingIndicator: 이미지 생성 중 (5초 예상) +- Button: "다음" + +*인터랙션*: +- 카드 클릭 → Radio 선택 (숨김) +- 선택 시 테두리 강조 + 체크 배지 +- "크게보기" → 전체화면 모달 (이벤트 버블링 방지) +- Job 폴링 (3초 간격) +- 완료 시 이미지 URL 표시 +- 1개 선택 → PUT /events/{eventId}/images/{imageId}/select → Step 4 이동 + +--- + +**Step 4: Edit (콘텐츠 편집)** + +*상세 기능*: +- 텍스트 편집 (제목, 경품, 참여 안내) +- 실시간 미리보기 +- API: PUT /events/{eventId}/images/{imageId}/edit + +*UI 구성요소*: +- TextField: 제목 +- TextField: 경품 +- TextField: 참여 안내 (multiline) +- PreviewCard: 실시간 미리보기 (우측/하단) +- Button: "다음", "저장" + +*인터랙션*: +- 입력 시 debounce (300ms) 후 미리보기 업데이트 +- 저장 버튼 → 임시 저장 +- 다음 버튼 → API 호출 → Step 5 이동 + +--- + +**Step 5: Channels (배포 채널 선택)** + +*상세 기능*: +- 복수 채널 선택 (Checkbox) + - 우리동네TV + - 링고비즈 + - SNS (Instagram, Naver, Kakao) + - QR코드 +- 채널별 옵션 (조건부 표시) +- API: PUT /events/{eventId}/channels + +*UI 구성요소*: +- Checkbox: 각 채널 +- ConditionalOptions: 각 채널 (펼침/접힘) +- Button: "다음" + +*인터랙션*: +- 체크박스 선택 → 해당 옵션 펼침 +- 최소 1개 선택 필수 +- 다음 버튼 → API 호출 → Step 6 이동 + +--- + +**Step 6: Publish (최종 승인)** + +*상세 기능*: +- 이벤트 요약 표시 +- 이미지 미리보기 +- 배포 채널 목록 +- 일정 설정 (시작일, 종료일) +- 최종 승인 및 배포 +- API: POST /events/{eventId}/publish + +*UI 구성요소*: +- SummaryCard: 이벤트 정보 +- ImagePreview: 최종 이미지 +- ChannelBadges: 선택된 채널 +- DatePicker: 시작일, 종료일 +- Button: "수정" (이전 단계), "승인 및 배포" + +*인터랙션*: +- 수정 버튼 → Funnel 이전 단계 이동 +- 승인 버튼 → Confirm 다이얼로그 → API 호출 → 배포 완료 Toast → `/events/[id]` 이동 + +--- + +#### 1.4.3 MANAGE-01: 이벤트 상세 + +**상세 기능**: +- 이벤트 헤더 (이미지, 제목, 상태, 기간) +- 실시간 KPI (4개) + - 참여자 수 + - 조회수 + - ROI + - 전환율 +- 빠른 액션 (4개 버튼) + - 참여자 목록 + - 당첨자 관리 + - 성과 분석 + - 이벤트 수정 +- 참여 추이 차트 (Line Chart) +- API: GET /events/{eventId} + +**UI 구성요소**: +- EventHeader: 이미지 + 정보 +- KPICard: 4개 (Grid) +- ActionButton: 4개 (Grid) +- LineChart: 참여 추이 (Chart.js) + +**인터랙션**: +- 실시간 업데이트 (5분마다 자동 갱신) +- Pull to Refresh (모바일) +- 액션 버튼 클릭 → 해당 페이지 이동 + +--- + +#### 1.4.4 MANAGE-03: 이벤트 참여 (고객용) + +**상세 기능**: +- 이벤트 이미지 +- 이벤트 정보 (제목, 경품, 참여 방법) +- 참여 폼 (이름, 전화번호) +- 개인정보 동의 (필수) +- 마케팅 수신 동의 (선택) +- API: POST /events/{eventId}/participate + +**UI 구성요소**: +- EventBanner: 이미지 +- InfoCard: 이벤트 정보 +- TextField: 이름, 전화번호 +- Checkbox: 개인정보 동의, 마케팅 동의 +- Button: "참여하기" + +**인터랙션**: +- 전화번호 검증 (010-XXXX-XXXX) +- 중복 참여 방지 +- 개인정보 동의 필수 +- 참여 성공 시 응모 번호 모달 표시 + +--- + +### 1.5 화면 간 전환 및 네비게이션 + +**Bottom Navigation** (4개 탭): +- 홈 (`/`): DASH-01 대시보드 +- 이벤트 (`/events`): DASH-02 이벤트 목록 +- 분석 (`/analytics`): MANAGE-05 성과 분석 +- 프로필 (`/profile`): AUTH-03 프로필 + +**Header Navigation**: +- 로고 (클릭 시 홈 이동) +- 뒤로가기 버튼 (조건부 표시) +- 프로필 아이콘 (로그인 시만 표시) + +**FAB (Floating Action Button)**: +- 위치: 우측 하단 고정 +- 기능: 새 이벤트 생성 (`/events/create/objective`) +- 표시 조건: 대시보드, 이벤트 목록 화면에서만 + +--- + +### 1.6 반응형 설계 전략 + +**Breakpoints**: +- Mobile: 320px ~ 767px (기본) +- Tablet: 768px ~ 1023px +- Desktop: 1024px 이상 + +**레이아웃 변화**: + +| 화면 | Mobile | Tablet | Desktop | +|------|--------|--------|---------| +| 대시보드 | KPI 세로 스택 | KPI 2x2 Grid | KPI 4열 + 사이드바 | +| 이벤트 목록 | 1열 | 2열 | 3열 | +| 이벤트 생성 | 세로 스택 | 세로 스택 | 편집 \| 미리보기 (Split) | +| 성과 분석 | 차트 세로 스택 | 차트 2x1 Grid | 차트 2x2 Grid | + +**반응형 컴포넌트**: +- `Box` with `sx` prop (MUI) +- `Grid` with `xs/md/lg` props +- `useMediaQuery` hook +- `Container` with `maxWidth` prop + +--- + +### 1.7 접근성 보장 방안 + +**WCAG 2.1 AA 준수**: +- 색상 대비: 4.5:1 (텍스트), 3:1 (UI 요소) +- 터치 영역: 최소 44x44px +- 키보드 네비게이션: Tab, Enter, Escape 지원 +- Focus Indicator: 명확한 포커스 표시 + +**스크린 리더 지원**: +- ARIA Labels: 모든 버튼, 링크, 폼 필드 +- ARIA Roles: 적절한 역할 지정 +- Live Regions: 동적 콘텐츠 업데이트 알림 + +**대안 제공**: +- 색상 외 표현: 아이콘 + 텍스트 조합 +- 이미지 alt 텍스트 +- 폼 검증 에러 메시지 + +--- + +### 1.8 성능 최적화 방안 + +**Next.js 최적화**: +- App Router 사용 (Next.js 14) +- Server Components (기본) +- Client Components (인터랙션 필요 시만) +- Image 컴포넌트 (자동 최적화) +- Font 최적화 (next/font) + +**Code Splitting**: +- 페이지별 자동 코드 분할 +- Dynamic Import (Chart.js, 큰 라이브러리) +- Lazy Loading (이미지, 컴포넌트) + +**캐싱 전략**: +- React Query (서버 상태 관리) + - staleTime: 5분 (대시보드) + - cacheTime: 30분 + - refetchOnWindowFocus: true +- SWR (대안) +- Redux (클라이언트 상태 관리, 필요 시) + +**이미지 최적화**: +- WebP 포맷 (fallback: JPG) +- 압축: Quality 80-85% +- Lazy Loading +- Responsive Images (srcset) + +**폰트 최적화**: +- next/font 사용 +- Font Display: swap +- Preload: 주요 폰트 +- Subset: 한글 + 영문 + 숫자 + +**애니메이션 최적화**: +- GPU 가속: transform, opacity +- Framer Motion (선택적 사용) +- CSS Transitions (기본) + +--- + +## 2. 참조 문서 + +- **프로토타입**: design/uiux/prototype/*.html +- **UI/UX 설계서**: design/uiux/uiux-design.md +- **스타일 가이드**: design/uiux/style-guide.md (→ design/frontend/style-guide.md) +- **정보 아키텍처**: design/frontend/ia.md +- **API 매핑 설계서**: design/frontend/api-mapping.md + +--- + +## 3. 기술 스택 요약 + +| 항목 | 기술 | +|------|------| +| 프레임워크 | Next.js 14 (App Router) | +| 라이브러리 | React 18 | +| 언어 | TypeScript 5 | +| UI 프레임워크 | Material-UI (MUI) v6 | +| Funnel 관리 | @use-funnel/next | +| 상태 관리 | React Query v5 (서버 상태), Zustand (클라이언트 상태) | +| 폼 관리 | React Hook Form v7 + Zod (검증) | +| 차트 | Chart.js v4 + react-chartjs-2 | +| 날짜 | dayjs | +| HTTP 클라이언트 | Axios | +| 스타일링 | MUI sx prop, Emotion (CSS-in-JS) | + +--- + +## 4. 변경 이력 + +### Version 1.0 (2025-10-24) +- 초안 작성 +- Next.js 14 기반 설계 +- @use-funnel/next 검토 및 적용 +- MUI v6 선택 +- 반응형 디자인 전략 수립 +- 접근성 및 성능 최적화 방안 수립 + +--- + +**문서 끝**