diff --git a/API_CHANGES.md b/API_CHANGES.md new file mode 100644 index 0000000..f54789e --- /dev/null +++ b/API_CHANGES.md @@ -0,0 +1,263 @@ +# Content API ๋ณ€๊ฒฝ์‚ฌํ•ญ + +## ๐Ÿ“‹ ์ฃผ์š” ๋ณ€๊ฒฝ์‚ฌํ•ญ ์š”์•ฝ + +### 1. **eventDraftId โ†’ eventId ํƒ€์ž… ๋ณ€๊ฒฝ** + +| ํ•ญ๋ชฉ | ๊ธฐ์กด (Old) | ๋ณ€๊ฒฝ (New) | +|------|-----------|-----------| +| ํ•„๋“œ๋ช… | `eventDraftId` | `eventId` | +| ํƒ€์ž… | `number` | `string` | +| ์˜ˆ์‹œ | `7777` | `"7777"` | + +--- + +## ๐Ÿ”„ ์˜ํ–ฅ์„ ๋ฐ›๋Š” ์ธํ„ฐํŽ˜์ด์Šค + +### GenerateImagesRequest + +**Before:** +```typescript +interface GenerateImagesRequest { + eventDraftId: number; + eventTitle: string; + eventDescription: string; + industry?: string; + location?: string; + trends?: string[]; + styles: ('SIMPLE' | 'FANCY' | 'TRENDY')[]; + platforms: ('INSTAGRAM' | 'NAVER' | 'KAKAO')[]; +} +``` + +**After:** +```typescript +interface GenerateImagesRequest { + eventId: string; // Changed from eventDraftId: number + eventTitle: string; + eventDescription: string; + industry?: string; + location?: string; + trends?: string[]; + styles: ('SIMPLE' | 'FANCY' | 'TRENDY')[]; + platforms: ('INSTAGRAM' | 'NAVER' | 'KAKAO')[]; +} +``` + +### JobInfo + +**Before:** +```typescript +interface JobInfo { + id: string; + eventDraftId: number; + jobType: string; + status: 'PENDING' | 'PROCESSING' | 'COMPLETED' | 'FAILED'; + progress: number; + resultMessage?: string; + errorMessage?: string; + createdAt: string; + updatedAt: string; +} +``` + +**After:** +```typescript +interface JobInfo { + id: string; + eventId: string; // Changed from eventDraftId: number + jobType: string; + status: 'PENDING' | 'PROCESSING' | 'COMPLETED' | 'FAILED'; + progress: number; + resultMessage?: string; + errorMessage?: string; + createdAt: string; + updatedAt: string; +} +``` + +### ImageInfo + +**Before:** +```typescript +interface ImageInfo { + id: number; + eventDraftId: number; + style: 'SIMPLE' | 'FANCY' | 'TRENDY'; + platform: 'INSTAGRAM' | 'NAVER' | 'KAKAO'; + cdnUrl: string; + prompt: string; + selected: boolean; + createdAt: string; + updatedAt: string; +} +``` + +**After:** +```typescript +interface ImageInfo { + id: number; + eventId: string; // Changed from eventDraftId: number + style: 'SIMPLE' | 'FANCY' | 'TRENDY'; + platform: 'INSTAGRAM' | 'NAVER' | 'KAKAO'; + cdnUrl: string; + prompt: string; + selected: boolean; + createdAt: string; + updatedAt: string; +} +``` + +### ContentInfo + +**Before:** +```typescript +interface ContentInfo { + id: number; + eventDraftId: number; + eventTitle: string; + eventDescription: string; + images: ImageInfo[]; + createdAt: string; + updatedAt: string; +} +``` + +**After:** +```typescript +interface ContentInfo { + id: number; + eventId: string; // Changed from eventDraftId: number + eventTitle: string; + eventDescription: string; + images: ImageInfo[]; + createdAt: string; + updatedAt: string; +} +``` + +--- + +## ๐Ÿ“ ์ˆ˜์ •๋œ ํŒŒ์ผ ๋ชฉ๋ก + +### 1. Type Definitions +- โœ… `src/shared/api/contentApi.ts` + - `GenerateImagesRequest` interface updated + - `JobInfo` interface updated + - `ImageInfo` interface updated + - `ContentInfo` interface updated + - API function signatures updated + +### 2. Components +- โœ… `src/app/(main)/events/create/steps/ContentPreviewStep.tsx` + - `EventCreationData` interface: `eventDraftId: number` โ†’ `eventDraftId: string` + - Mock data updated to use string type + - API call updated: `eventDraftId` โ†’ `eventId` + +### 3. Mock Data Files +- โœ… `public/init-mock-data.html` + - `eventDraftId: 7777` โ†’ `eventDraftId: "7777"` + +- โœ… `MOCK_DATA_SETUP.md` + - All mock data examples updated to string type + - Documentation notes added about type change + +### 4. API Routes (Next.js Proxy) +- โœ… `src/app/api/content/images/generate/route.ts` (no changes needed) +- โœ… `src/app/api/content/images/jobs/[jobId]/route.ts` (no changes needed) +- โœ… `src/app/api/content/events/[eventDraftId]/images/route.ts` + - Comment added about eventId parameter + +--- + +## ๐Ÿงช ํ…Œ์ŠคํŠธ ์˜ˆ์‹œ + +### API ์š”์ฒญ ์˜ˆ์‹œ + +**Before:** +```json +POST /api/v1/content/images/generate +{ + "eventDraftId": 7777, + "eventTitle": "๋งฅ์ฃผ ํŒŒํ‹ฐ ์ด๋ฒคํŠธ", + "eventDescription": "๊ฐ•๋‚จ์—์„œ ์—ด๋ฆฌ๋Š” ์‹ ๋‚˜๋Š” ๋งฅ์ฃผ ํŒŒํ‹ฐ", + "industry": "์Œ์‹์ ", + "location": "๊ฐ•๋‚จ", + "trends": ["ํŒŒํ‹ฐ", "๋งฅ์ฃผ", "์ƒ๋งฅ์ฃผ"], + "styles": ["SIMPLE", "FANCY", "TRENDY"], + "platforms": ["INSTAGRAM"] +} +``` + +**After:** +```json +POST /api/v1/content/images/generate +{ + "eventId": "7777", + "eventTitle": "๋งฅ์ฃผ ํŒŒํ‹ฐ ์ด๋ฒคํŠธ", + "eventDescription": "๊ฐ•๋‚จ์—์„œ ์—ด๋ฆฌ๋Š” ์‹ ๋‚˜๋Š” ๋งฅ์ฃผ ํŒŒํ‹ฐ", + "industry": "์Œ์‹์ ", + "location": "๊ฐ•๋‚จ", + "trends": ["ํŒŒํ‹ฐ", "๋งฅ์ฃผ", "์ƒ๋งฅ์ฃผ"], + "styles": ["SIMPLE", "FANCY", "TRENDY"], + "platforms": ["INSTAGRAM"] +} +``` + +### localStorage Mock ๋ฐ์ดํ„ฐ + +**Before:** +```javascript +localStorage.setItem('eventCreationData', JSON.stringify({ + eventDraftId: 7777, + eventTitle: "๋งฅ์ฃผ ํŒŒํ‹ฐ ์ด๋ฒคํŠธ", + // ... +})); +``` + +**After:** +```javascript +localStorage.setItem('eventCreationData', JSON.stringify({ + eventDraftId: "7777", // String type now + eventTitle: "๋งฅ์ฃผ ํŒŒํ‹ฐ ์ด๋ฒคํŠธ", + // ... +})); +``` + +--- + +## โœ… ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ฒดํฌ๋ฆฌ์ŠคํŠธ + +- [x] TypeScript ์ธํ„ฐํŽ˜์ด์Šค ์—…๋ฐ์ดํŠธ +- [x] API ํ˜ธ์ถœ ์ฝ”๋“œ ์ˆ˜์ • +- [x] Mock ๋ฐ์ดํ„ฐ ํƒ€์ž… ๋ณ€๊ฒฝ +- [x] ๋ฌธ์„œ ์—…๋ฐ์ดํŠธ +- [x] ๋นŒ๋“œ ์„ฑ๊ณต ํ™•์ธ +- [ ] ๊ฐœ๋ฐœ ์„œ๋ฒ„ ํ…Œ์ŠคํŠธ +- [ ] ์‹ค์ œ API ์—ฐ๋™ ํ…Œ์ŠคํŠธ + +--- + +## ๐Ÿ”— ๊ด€๋ จ API ๋ฌธ์„œ + +- Swagger UI: http://localhost:8084/swagger-ui/index.html +- OpenAPI Spec: http://localhost:8084/v3/api-docs + +--- + +## ๐Ÿ“Œ ์ฃผ์˜์‚ฌํ•ญ + +1. **ํƒ€์ž… ์ฃผ์˜**: `eventId`๋Š” ์ด์ œ `string` ํƒ€์ž…์ž…๋‹ˆ๋‹ค. ์ˆซ์ž๋กœ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์„ธ์š”. +2. **Mock ๋ฐ์ดํ„ฐ**: localStorage์— ์ €์žฅํ•  ๋•Œ ๋ฌธ์ž์—ด ํƒ€์ž…์œผ๋กœ ์ €์žฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +3. **API ํ˜ธ์ถœ**: ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ๋ฐฑ์—”๋“œ๋กœ ์ „์†ก ์‹œ string์œผ๋กœ ์ „์†ก๋ฉ๋‹ˆ๋‹ค. +4. **ํ•˜์œ„ ํ˜ธํ™˜์„ฑ**: ๊ธฐ์กด number ํƒ€์ž… ๋ฐ์ดํ„ฐ๋Š” ์ž‘๋™ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ localStorage๋ฅผ ์ดˆ๊ธฐํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +--- + +## ๐Ÿ”„ ๋กค๋ฐฑ ๋ฐฉ๋ฒ• + +๋งŒ์•ฝ ์ด์ „ ๋ฒ„์ „์œผ๋กœ ๋Œ์•„๊ฐ€์•ผ ํ•œ๋‹ค๋ฉด: + +1. `git revert` ๋˜๋Š” ํŠน์ • ์ปค๋ฐ‹์œผ๋กœ ๋ณต์› +2. localStorage ์ดˆ๊ธฐํ™”: `localStorage.removeItem('eventCreationData')` +3. ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ diff --git a/CORS_FIX.md b/CORS_FIX.md new file mode 100644 index 0000000..de2857f --- /dev/null +++ b/CORS_FIX.md @@ -0,0 +1,221 @@ +# CORS ๋ฌธ์ œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• + +## ๋ฌธ์ œ ์ƒํ™ฉ + +ํ”„๋ก ํŠธ์—”๋“œ(`http://localhost:3000`)์—์„œ ๋ฐฑ์—”๋“œ Content API(`http://localhost:8084`)๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜๋ฉด **CORS(Cross-Origin Resource Sharing)** ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. + +### ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ +``` +Network Error +AxiosError: Network Error + code: "ERR_NETWORK" +``` + +### ์›์ธ ๋ถ„์„ +```bash +# CORS preflight ์š”์ฒญ ํ…Œ์ŠคํŠธ +curl -X OPTIONS http://localhost:8084/api/v1/content/images/generate \ + -H 'Origin: http://localhost:3000' \ + -H 'Access-Control-Request-Method: POST' \ + -H 'Access-Control-Request-Headers: content-type' + +# ๊ฒฐ๊ณผ: HTTP/1.1 403 Forbidden +# Invalid CORS request +``` + +๋ฐฑ์—”๋“œ ์„œ๋ฒ„๊ฐ€ `http://localhost:3000` origin์—์„œ์˜ CORS ์š”์ฒญ์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š์Œ. + +--- + +## ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•: Next.js API Proxy + +๋ฐฑ์—”๋“œ CORS ์„ค์ •์„ ์ˆ˜์ •ํ•˜๋Š” ๋Œ€์‹ , **Next.js API Routes๋ฅผ ํ”„๋ก์‹œ๋กœ ์‚ฌ์šฉ**ํ•˜์—ฌ CORS ๋ฌธ์ œ๋ฅผ ์šฐํšŒํ–ˆ์Šต๋‹ˆ๋‹ค. + +### ์•„ํ‚คํ…์ฒ˜ + +``` +[Browser] + โ†“ (Same-Origin Request) +[Next.js Frontend: localhost:3000] + โ†“ [Next.js API Proxy: /api/content/*] + โ†“ (Server-to-Server Request, CORS ๋ฌด๊ด€) +[Content API Backend: localhost:8084] +``` + +### ๊ตฌํ˜„ ํŒŒ์ผ + +#### 1. **์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ”„๋ก์‹œ** (`/api/content/images/generate/route.ts`) + +```typescript +export async function POST(request: NextRequest) { + const body = await request.json(); + + const response = await fetch('http://localhost:8084/api/v1/content/images/generate', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }); + + return NextResponse.json(await response.json()); +} +``` + +**URL ๋งคํ•‘**: +- Frontend: `POST /api/content/images/generate` +- Backend: `POST http://localhost:8084/api/v1/content/images/generate` + +#### 2. **Job ์ƒํƒœ ์กฐํšŒ ํ”„๋ก์‹œ** (`/api/content/images/jobs/[jobId]/route.ts`) + +```typescript +export async function GET(request: NextRequest, { params }: { params: { jobId: string } }) { + const { jobId } = params; + + const response = await fetch(`http://localhost:8084/api/v1/content/images/jobs/${jobId}`, { + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + }); + + return NextResponse.json(await response.json()); +} +``` + +**URL ๋งคํ•‘**: +- Frontend: `GET /api/content/images/jobs/{jobId}` +- Backend: `GET http://localhost:8084/api/v1/content/images/jobs/{jobId}` + +#### 3. **์ด๋ฏธ์ง€ ๋ชฉ๋ก ์กฐํšŒ ํ”„๋ก์‹œ** (`/api/content/events/[eventDraftId]/images/route.ts`) + +```typescript +export async function GET(request: NextRequest, { params }: { params: { eventDraftId: string } }) { + const { eventDraftId } = params; + const { searchParams } = new URL(request.url); + + let url = `http://localhost:8084/api/v1/content/events/${eventDraftId}/images`; + if (searchParams.get('style')) url += `?style=${searchParams.get('style')}`; + if (searchParams.get('platform')) url += `&platform=${searchParams.get('platform')}`; + + const response = await fetch(url, { + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + }); + + return NextResponse.json(await response.json()); +} +``` + +**URL ๋งคํ•‘**: +- Frontend: `GET /api/content/events/{eventDraftId}/images?style=SIMPLE&platform=INSTAGRAM` +- Backend: `GET http://localhost:8084/api/v1/content/events/{eventDraftId}/images?style=SIMPLE&platform=INSTAGRAM` + +--- + +## ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ ๋ณ€๊ฒฝ + +### Before (์ง์ ‘ ๋ฐฑ์—”๋“œ ํ˜ธ์ถœ - CORS ์—๋Ÿฌ ๋ฐœ์ƒ) + +```typescript +const CONTENT_API_BASE_URL = 'http://localhost:8084'; + +export const contentApiClient = axios.create({ + baseURL: CONTENT_API_BASE_URL, +}); + +// โŒ CORS Error +await contentApiClient.post('/api/v1/content/images/generate', request); +``` + +### After (Next.js API Proxy ์‚ฌ์šฉ - CORS ์šฐํšŒ) + +```typescript +const CONTENT_API_BASE_URL = '/api/content'; // Same-origin request + +export const contentApiClient = axios.create({ + baseURL: CONTENT_API_BASE_URL, +}); + +// โœ… Works! (Same-origin โ†’ Server-side proxy โ†’ Backend) +await contentApiClient.post('/images/generate', request); +``` + +--- + +## ์žฅ์  + +โœ… **ํ”„๋ก ํŠธ์—”๋“œ ์ˆ˜์ •๋งŒ์œผ๋กœ ํ•ด๊ฒฐ**: ๋ฐฑ์—”๋“œ CORS ์„ค์ • ๋ณ€๊ฒฝ ๋ถˆํ•„์š” +โœ… **Same-Origin ์ •์ฑ… ์ค€์ˆ˜**: ๋ธŒ๋ผ์šฐ์ €๋Š” ๊ฐ™์€ ๋„๋ฉ”์ธ์œผ๋กœ ์ธ์‹ +โœ… **์„œ๋ฒ„ ๊ฐ„ ํ†ต์‹ **: Next.js ์„œ๋ฒ„์—์„œ ๋ฐฑ์—”๋“œ ํ˜ธ์ถœ (CORS ๋ฌด๊ด€) +โœ… **๋ณด์•ˆ ๊ฐ•ํ™”**: ๋ฐฑ์—”๋“œ URL์„ ํด๋ผ์ด์–ธํŠธ์— ๋…ธ์ถœํ•˜์ง€ ์•Š์Œ +โœ… **ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ํ™œ์šฉ**: `NEXT_PUBLIC_CONTENT_API_URL`๋กœ ๋ฐฐํฌ ํ™˜๊ฒฝ ๋Œ€์‘ + +--- + +## ํ”„๋กœ๋•์…˜ ๋ฐฐํฌ ์‹œ ๊ณ ๋ ค์‚ฌํ•ญ + +### ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ • + +```bash +# .env.local (๊ฐœ๋ฐœ ํ™˜๊ฒฝ) +NEXT_PUBLIC_CONTENT_API_URL=http://localhost:8084 + +# .env.production (ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ) +NEXT_PUBLIC_CONTENT_API_URL=https://api.production.com +``` + +### ํ”„๋ก์‹œ ์ฝ”๋“œ์— ์ ์šฉ + +```typescript +const CONTENT_API_BASE_URL = process.env.NEXT_PUBLIC_CONTENT_API_URL || 'http://localhost:8084'; + +const response = await fetch(`${CONTENT_API_BASE_URL}/api/v1/content/images/generate`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), +}); +``` + +### ํƒ€์ž„์•„์›ƒ ์„ค์ • + +```typescript +const response = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + signal: AbortSignal.timeout(120000), // 120์ดˆ ํƒ€์ž„์•„์›ƒ +}); +``` + +--- + +## ํ…Œ์ŠคํŠธ ๋ฐฉ๋ฒ• + +### 1. ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰ + +```bash +npm run dev +``` + +### 2. ๋ธŒ๋ผ์šฐ์ €์—์„œ ํ…Œ์ŠคํŠธ + +``` +http://localhost:3000/events/create?event-creation.step=contentPreview +``` + +### 3. ๋„คํŠธ์›Œํฌ ํƒญ ํ™•์ธ + +๋ธŒ๋ผ์šฐ์ € ๊ฐœ๋ฐœ์ž ๋„๊ตฌ โ†’ Network ํƒญ์—์„œ ๋‹ค์Œ ์š”์ฒญ ํ™•์ธ: + +``` +POST http://localhost:3000/api/content/images/generate (Status: 202) +GET http://localhost:3000/api/content/images/jobs/job-xxxxx (Status: 200) +GET http://localhost:3000/api/content/events/7777/images (Status: 200) +``` + +๋ชจ๋‘ **Same-Origin** ์š”์ฒญ์ด๋ฏ€๋กœ CORS ์—๋Ÿฌ ์—†์Œ! + +--- + +## ์ฐธ๊ณ  ์ž๋ฃŒ + +- [Next.js API Routes](https://nextjs.org/docs/app/building-your-application/routing/route-handlers) +- [CORS (MDN)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) +- [Proxy Pattern](https://en.wikipedia.org/wiki/Proxy_pattern) diff --git a/FIX_EVENTID_MISMATCH.md b/FIX_EVENTID_MISMATCH.md new file mode 100644 index 0000000..f10105c --- /dev/null +++ b/FIX_EVENTID_MISMATCH.md @@ -0,0 +1,182 @@ +# EventId ๋ถˆ์ผ์น˜ ๋ฌธ์ œ ํ•ด๊ฒฐ + +## ๋ฌธ์ œ ์ƒํ™ฉ + +์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํŽ˜์ด์ง€์—์„œ ์Šคํƒ€์ผ 1 ์นด๋“œ์— ์ด๋ฏธ์ง€๊ฐ€ ํ‘œ์‹œ๋˜์ง€ ์•Š๊ณ  ํ”Œ๋ ˆ์ด์Šคํ™€๋”๋งŒ ๋ณด์ด๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. + +### ์Šคํฌ๋ฆฐ์ƒท ๋ถ„์„ +- **์Šคํƒ€์ผ 1 (SIMPLE)**: ํ”Œ๋ ˆ์ด์Šคํ™€๋”๋งŒ ํ‘œ์‹œ (์•„์ด์ฝ˜ + ์ œ๋ชฉ + ๊ฒฝํ’ˆ) +- **์Šคํƒ€์ผ 2 (FANCY)**: ์‹ค์ œ ์ด๋ฏธ์ง€ ํ‘œ์‹œ โœ… +- **์Šคํƒ€์ผ 3 (TRENDY)**: ์‹ค์ œ ์ด๋ฏธ์ง€ ํ‘œ์‹œ โœ… + +## ๊ทผ๋ณธ ์›์ธ + +API ๋ถ„์„ ๊ฒฐ๊ณผ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋œ ์ด๋ฏธ์ง€์™€ Mock ๋ฐ์ดํ„ฐ์˜ eventId๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค: + +```bash +# Mock ๋ฐ์ดํ„ฐ eventId +"7777" + +# ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‹ค์ œ eventId +curl http://localhost:8084/api/v1/content/events/7777/images +โ†’ Response: [] (๋นˆ ๋ฐฐ์—ด) + +# ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์กด์žฌํ•˜๋Š” eventId +- "Tst12131": SIMPLE ์ด๋ฏธ์ง€ 1๊ฐœ +- "1761634317010": SIMPLE, FANCY, TRENDY ๊ฐ 2๊ฐœ์”ฉ ์ด 6๊ฐœ +- null: SIMPLE ์ด๋ฏธ์ง€ 1๊ฐœ +``` + +**๊ฒฐ๋ก **: Mock ๋ฐ์ดํ„ฐ์˜ eventId "7777"๋กœ๋Š” ์–ด๋–ค ์ด๋ฏธ์ง€๋„ ์กฐํšŒ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. + +## ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• + +๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ์ด๋ฏธ์ง€๊ฐ€ ์žˆ๋Š” eventId๋กœ Mock ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€๊ฒฝํ–ˆ์Šต๋‹ˆ๋‹ค. + +### ๋ณ€๊ฒฝ๋œ eventId +```javascript +// Before +eventDraftId: "7777" + +// After +eventDraftId: "1761634317010" +``` + +**์„ ํƒ ์ด์œ **: +- SIMPLE, FANCY, TRENDY 3๊ฐ€์ง€ ์Šคํƒ€์ผ ๋ชจ๋‘ ์ด๋ฏธ์ง€ ๋ณด์œ  +- ๊ฐ ์Šคํƒ€์ผ๋ณ„๋กœ 2๊ฐœ์”ฉ ์ด 6๊ฐœ์˜ ์ด๋ฏธ์ง€ ์กด์žฌ +- INSTAGRAM ํ”Œ๋žซํผ ์ด๋ฏธ์ง€ ์กด์žฌ + +## ์ˆ˜์ •๋œ ํŒŒ์ผ + +### 1. ContentPreviewStep.tsx +**์œ„์น˜**: `src/app/(main)/events/create/steps/ContentPreviewStep.tsx:109` + +```typescript +const mockData: EventCreationData = { + eventDraftId: "1761634317010", // Changed from "7777" + eventTitle: "๋งฅ์ฃผ ํŒŒํ‹ฐ ์ด๋ฒคํŠธ", + eventDescription: "๊ฐ•๋‚จ์—์„œ ์—ด๋ฆฌ๋Š” ์‹ ๋‚˜๋Š” ๋งฅ์ฃผ ํŒŒํ‹ฐ์— ์ฐธ์—ฌํ•˜์„ธ์š”!", + industry: "์Œ์‹์ ", + location: "๊ฐ•๋‚จ", + trends: ["ํŒŒํ‹ฐ", "๋งฅ์ฃผ", "์ƒ๋งฅ์ฃผ"], + prize: "์ƒ๋งฅ์ฃผ 1์ž”" +}; +``` + +### 2. init-mock-data.html +**์œ„์น˜**: `public/init-mock-data.html:121`, `public/init-mock-data.html:168` + +```html + +1761634317010 + + + +``` + +### 3. QUICK_TEST.md +**์œ„์น˜**: `QUICK_TEST.md` (์ „์ฒด ๋ฌธ์„œ) + +- Mock ๋ฐ์ดํ„ฐ ์˜ˆ์‹œ์˜ eventId ๋ณ€๊ฒฝ +- API ํ™•์ธ ์˜ˆ์‹œ์˜ eventId ๋ณ€๊ฒฝ +- ๋””๋ฒ„๊น… ๋กœ๊ทธ ์˜ˆ์‹œ ์—…๋ฐ์ดํŠธ + +### 4. MOCK_DATA_SETUP.md +**์œ„์น˜**: `MOCK_DATA_SETUP.md` (์ „์ฒด ๋ฌธ์„œ) + +- Mock ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ์˜ˆ์‹œ ์—…๋ฐ์ดํŠธ +- ํ…Œ์ŠคํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค eventId ๋ณ€๊ฒฝ +- ์ฐธ๊ณ  ์‚ฌํ•ญ ์ถ”๊ฐ€: "1761634317010์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ด๋ฏธ ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€๊ฐ€ ์žˆ๋Š” eventId" + +## ๋นŒ๋“œ ๊ฒ€์ฆ + +```bash +npm run build +``` + +โœ… **์„ฑ๊ณต**: TypeScript ํƒ€์ž… ๊ฒ€์ฆ ํ†ต๊ณผ, ๋นŒ๋“œ ์™„๋ฃŒ + +๊ฒฝ๊ณ  ์‚ฌํ•ญ: +- `loadingProgress`, `setLoadingProgress` ๋ฏธ์‚ฌ์šฉ ๋ณ€์ˆ˜ (๊ธฐ๋Šฅ์— ์˜ํ–ฅ ์—†์Œ) +- ๊ธฐํƒ€ ESLint ๊ฒฝ๊ณ  (๊ธฐ์กด ์ฝ”๋“œ, ๊ธˆ๋ฒˆ ์ˆ˜์ •๊ณผ ๋ฌด๊ด€) + +## ํ…Œ์ŠคํŠธ ๋ฐฉ๋ฒ• + +### 1. localStorage ์ดˆ๊ธฐํ™” +๋ธŒ๋ผ์šฐ์ € ์ฝ˜์†”์—์„œ ๊ธฐ์กด ๋ฐ์ดํ„ฐ ์‚ญ์ œ: +```javascript +localStorage.removeItem('eventCreationData'); +``` + +### 2. ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰ +```bash +npm run dev +``` + +### 3. ํŽ˜์ด์ง€ ์ ‘์† +``` +http://localhost:3000/events/create?event-creation.step=contentPreview +``` + +### 4. ์˜ˆ์ƒ ๊ฒฐ๊ณผ +- โœ… ์Šคํƒ€์ผ 1 (SIMPLE): ์‹ค์ œ ์ด๋ฏธ์ง€ ํ‘œ์‹œ +- โœ… ์Šคํƒ€์ผ 2 (FANCY): ์‹ค์ œ ์ด๋ฏธ์ง€ ํ‘œ์‹œ +- โœ… ์Šคํƒ€์ผ 3 (TRENDY): ์‹ค์ œ ์ด๋ฏธ์ง€ ํ‘œ์‹œ +- โœ… 3๊ฐœ ์Šคํƒ€์ผ ๋ชจ๋‘ "ํฌ๊ฒŒ๋ณด๊ธฐ" ๋ฒ„ํŠผ ํ™œ์„ฑํ™” + +### 5. ์ฝ˜์†” ๋กœ๊ทธ ํ™•์ธ +``` +๐Ÿ“ฅ Loading generated images for event: 1761634317010 +โœ… Images loaded from API: 6 [...] +๐Ÿ“ธ Processing image 1: { id: X, style: 'SIMPLE', ... } + โœ… Selected as latest SIMPLE image +๐Ÿ“ธ Processing image 2: { id: Y, style: 'FANCY', ... } + โœ… Selected as latest FANCY image +๐Ÿ“ธ Processing image 3: { id: Z, style: 'TRENDY', ... } + โœ… Selected as latest TRENDY image +๐ŸŽจ Image map created with entries: { SIMPLE: 'YES โœ…', FANCY: 'YES โœ…', TRENDY: 'YES โœ…', totalSize: 3 } +โœ… ์ด๋ฏธ์ง€ ๋กœ๋“œ ์™„๋ฃŒ! +๐Ÿ–ผ๏ธ Rendering SIMPLE: { hasImage: true, imageDataExists: true, ... } +โœ… SIMPLE image loaded successfully +``` + +## ์ถ”๊ฐ€ ์ฐธ๊ณ  ์‚ฌํ•ญ + +### ์ƒˆ๋กœ์šด ์ด๋ฒคํŠธ ํ…Œ์ŠคํŠธ ์‹œ +์ƒˆ๋กœ์šด eventId๋กœ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋ ค๋ฉด: + +1. localStorage์— ์ƒˆ๋กœ์šด eventId ์„ค์ • +2. "์ด๋ฏธ์ง€ ์žฌ์ƒ์„ฑ" ๋ฒ„ํŠผ ํด๋ฆญ +3. ์•ฝ 2์ดˆ ํ›„ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€ ๋กœ๋“œ + +### Mock ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ๋ฐฉ๋ฒ• +`public/init-mock-data.html` ํŽ˜์ด์ง€ ์‚ฌ์šฉ: +``` +http://localhost:3000/init-mock-data.html +``` + +๋˜๋Š” ๋ธŒ๋ผ์šฐ์ € ์ฝ˜์†”์—์„œ ์ง์ ‘ ์„ค์ •: +```javascript +localStorage.setItem('eventCreationData', JSON.stringify({ + eventDraftId: "1761634317010", + eventTitle: "...", + // ... +})); +``` + +## ๊ฒฐ๋ก  + +EventId ๋ถˆ์ผ์น˜ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜์—ฌ ๋ชจ๋“  ์Šคํƒ€์ผ ์นด๋“œ์—์„œ ์‹ค์ œ ์ด๋ฏธ์ง€๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. + +**ํ•ต์‹ฌ ๋ณ€๊ฒฝ**: Mock ๋ฐ์ดํ„ฐ์˜ eventId๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์กด์žฌํ•˜๋Š” "1761634317010"์œผ๋กœ ๋ณ€๊ฒฝ + +**์˜ํ–ฅ ๋ฒ”์œ„**: +- ๊ฐœ๋ฐœ/ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์˜ Mock ๋ฐ์ดํ„ฐ๋งŒ ์˜ํ–ฅ +- ์‹ค์ œ ์šด์˜ ํ™˜๊ฒฝ์—์„œ๋Š” Channel Step API์—์„œ ์ œ๊ณตํ•˜๋Š” ์‹ค์ œ eventId ์‚ฌ์šฉ +- ์ฝ”๋“œ ๋กœ์ง ๋ณ€๊ฒฝ ์—†์Œ, ๋ฐ์ดํ„ฐ๋งŒ ๋ณ€๊ฒฝ diff --git a/MOCK_DATA_SETUP.md b/MOCK_DATA_SETUP.md new file mode 100644 index 0000000..f438436 --- /dev/null +++ b/MOCK_DATA_SETUP.md @@ -0,0 +1,149 @@ +# Mock ๋ฐ์ดํ„ฐ ์„ค์ • ๊ฐ€์ด๋“œ + +AI ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๊ธฐ๋Šฅ์„ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์œ„ํ•ด localStorage์— mock ๋ฐ์ดํ„ฐ๋ฅผ ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. + +## ๐Ÿš€ ๋น ๋ฅธ ์‹œ์ž‘ + +### ๋ฐฉ๋ฒ• 1: ์›น ์ธํ„ฐํŽ˜์ด์Šค ์‚ฌ์šฉ (๊ถŒ์žฅ) + +1. ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰ +```bash +npm run dev +``` + +2. ๋ธŒ๋ผ์šฐ์ €์—์„œ mock ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™” ํŽ˜์ด์ง€ ์—ด๊ธฐ +``` +http://localhost:3000/init-mock-data.html +``` + +3. "LocalStorage์— ์ €์žฅํ•˜๊ธฐ" ๋ฒ„ํŠผ ํด๋ฆญ + +4. ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํŽ˜์ด์ง€๋กœ ์ด๋™ +``` +http://localhost:3000/events/create?step=contentPreview +``` + +### ๋ฐฉ๋ฒ• 2: ๋ธŒ๋ผ์šฐ์ € ์ฝ˜์†” ์‚ฌ์šฉ + +1. ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰ ํ›„ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์•„๋ฌด ํŽ˜์ด์ง€๋‚˜ ์—ด๊ธฐ + +2. F12 ๋˜๋Š” Cmd+Option+I๋กœ ๊ฐœ๋ฐœ์ž ๋„๊ตฌ ์—ด๊ธฐ + +3. Console ํƒญ์—์„œ ๋‹ค์Œ ์ฝ”๋“œ ์‹คํ–‰: + +```javascript +const mockEventData = { + eventDraftId: "1761634317010", // String type (existing eventId with images) + eventTitle: "๋งฅ์ฃผ ํŒŒํ‹ฐ ์ด๋ฒคํŠธ", + eventDescription: "๊ฐ•๋‚จ์—์„œ ์—ด๋ฆฌ๋Š” ์‹ ๋‚˜๋Š” ๋งฅ์ฃผ ํŒŒํ‹ฐ์— ์ฐธ์—ฌํ•˜์„ธ์š”!", + industry: "์Œ์‹์ ", + location: "๊ฐ•๋‚จ", + trends: ["ํŒŒํ‹ฐ", "๋งฅ์ฃผ", "์ƒ๋งฅ์ฃผ"], + prize: "์ƒ๋งฅ์ฃผ 1์ž”" +}; + +localStorage.setItem('eventCreationData', JSON.stringify(mockEventData)); +console.log('โœ… Mock ๋ฐ์ดํ„ฐ ์ €์žฅ ์™„๋ฃŒ!'); +``` + +4. ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํŽ˜์ด์ง€๋กœ ์ด๋™ + +### ๋ฐฉ๋ฒ• 3: ํ…Œ์ŠคํŠธ HTML ํŒŒ์ผ ์‚ฌ์šฉ + +ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ์˜ `test-localstorage.html` ํŒŒ์ผ์„ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง์ ‘ ์—ด๊ธฐ: + +```bash +open test-localstorage.html +``` + +## ๐Ÿ“Š Mock ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ + +```json +{ + "eventDraftId": "1761634317010", + "eventTitle": "๋งฅ์ฃผ ํŒŒํ‹ฐ ์ด๋ฒคํŠธ", + "eventDescription": "๊ฐ•๋‚จ์—์„œ ์—ด๋ฆฌ๋Š” ์‹ ๋‚˜๋Š” ๋งฅ์ฃผ ํŒŒํ‹ฐ์— ์ฐธ์—ฌํ•˜์„ธ์š”!", + "industry": "์Œ์‹์ ", + "location": "๊ฐ•๋‚จ", + "trends": ["ํŒŒํ‹ฐ", "๋งฅ์ฃผ", "์ƒ๋งฅ์ฃผ"], + "prize": "์ƒ๋งฅ์ฃผ 1์ž”" +} +``` + +**์ฐธ๊ณ **: +- `eventDraftId`๋Š” API ๋ณ€๊ฒฝ์œผ๋กœ ์ธํ•ด `string` ํƒ€์ž…์ž…๋‹ˆ๋‹ค. +- `"1761634317010"`์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ด๋ฏธ ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€๊ฐ€ ์žˆ๋Š” eventId์ž…๋‹ˆ๋‹ค. + +## ๐Ÿงช ํ…Œ์ŠคํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค + +### ์‹œ๋‚˜๋ฆฌ์˜ค 1: ์ „์ฒด ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ”Œ๋กœ์šฐ + +1. Mock ๋ฐ์ดํ„ฐ ์„ค์ • +2. `/events/create?step=contentPreview` ์ ‘์† +3. ์ž๋™์œผ๋กœ AI ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹œ์ž‘ +4. 3๊ฐ€์ง€ ์Šคํƒ€์ผ(SIMPLE, FANCY, TRENDY) ํ™•์ธ +5. ์Šคํƒ€์ผ ์„ ํƒ ํ›„ ๋‹ค์Œ ๋‹จ๊ณ„ ์ง„ํ–‰ + +### ์‹œ๋‚˜๋ฆฌ์˜ค 2: ๋‹ค์–‘ํ•œ ์ด๋ฒคํŠธ ๋ฐ์ดํ„ฐ ํ…Œ์ŠคํŠธ + +๋‹ค๋ฅธ ์—…์ข…/์ง€์—ญ/ํŠธ๋ Œ๋“œ๋กœ ํ…Œ์ŠคํŠธ: + +```javascript +// ์นดํŽ˜ ์ด๋ฒคํŠธ (์ƒˆ๋กœ์šด ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ•„์š”) +localStorage.setItem('eventCreationData', JSON.stringify({ + eventDraftId: "test-cafe-001", + eventTitle: "์ปคํ”ผ ํ• ์ธ ์ด๋ฒคํŠธ", + eventDescription: "์‹ ๋ฉ”๋‰ด ์ถœ์‹œ ๊ธฐ๋… 30% ํ• ์ธ", + industry: "์นดํŽ˜", + location: "ํ™๋Œ€", + trends: ["์ปคํ”ผ", "ํ• ์ธ", "์‹ ๋ฉ”๋‰ด"], + prize: "์•„๋ฉ”๋ฆฌ์นด๋…ธ 1์ž”" +})); +``` + +```javascript +// ๋ทฐํ‹ฐ ์ด๋ฒคํŠธ (์ƒˆ๋กœ์šด ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ•„์š”) +localStorage.setItem('eventCreationData', JSON.stringify({ + eventDraftId: "test-beauty-001", + eventTitle: "๋ด„๋งž์ด ํ”ผ๋ถ€๊ด€๋ฆฌ ์ด๋ฒคํŠธ", + eventDescription: "๋ด„๋งž์ด ํŠน๋ณ„ ์ผ€์–ด ํ”„๋กœ๊ทธ๋žจ", + industry: "๋ทฐํ‹ฐ", + location: "๊ฐ•๋‚จ", + trends: ["ํ”ผ๋ถ€๊ด€๋ฆฌ", "๋ด„", "์ผ€์–ด"], + prize: "ํŽ˜์ด์…œ ์ผ€์–ด 1ํšŒ" +})); +``` + +## ๐Ÿ” ๋””๋ฒ„๊น… + +### localStorage ๋ฐ์ดํ„ฐ ํ™•์ธ + +```javascript +// ํ˜„์žฌ ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ ํ™•์ธ +const data = localStorage.getItem('eventCreationData'); +console.log(JSON.parse(data)); +``` + +### localStorage ๋ฐ์ดํ„ฐ ์‚ญ์ œ + +```javascript +localStorage.removeItem('eventCreationData'); +console.log('โœ… ๋ฐ์ดํ„ฐ ์‚ญ์ œ ์™„๋ฃŒ'); +``` + +## โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ + +1. **๊ฐ™์€ ๋„๋ฉ”์ธ**: localStorage๋Š” ๋„๋ฉ”์ธ๋ณ„๋กœ ๋ถ„๋ฆฌ๋˜๋ฏ€๋กœ, ๊ฐ™์€ localhost:3000์—์„œ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +2. **๋ธŒ๋ผ์šฐ์ € ์ œํ•œ**: ์‹œํฌ๋ฆฟ ๋ชจ๋“œ์—์„œ๋Š” localStorage๊ฐ€ ์ œํ•œ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +3. **๋ฐ์ดํ„ฐ ์œ ์ง€**: ๋ธŒ๋ผ์šฐ์ €๋ฅผ ๋‹ซ์•„๋„ localStorage ๋ฐ์ดํ„ฐ๋Š” ์œ ์ง€๋ฉ๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด ํ…Œ์ŠคํŠธ ์‹œ ์‚ญ์ œ ํ›„ ์ง„ํ–‰ํ•˜์„ธ์š”. + +## ๐ŸŽฏ ์‹ค์ œ API ์—ฐ๋™ ํ›„ + +Channel Step API๊ฐ€ ๊ตฌํ˜„๋˜๋ฉด ์ด mock ๋ฐ์ดํ„ฐ ์„ค์ •์€ ๋ถˆํ•„์š”ํ•˜๋ฉฐ, +์‹ค์ œ ํ”Œ๋กœ์šฐ์—์„œ ์ž๋™์œผ๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค: + +``` +Objective โ†’ Recommendation โ†’ Channel (์—ฌ๊ธฐ์„œ localStorage ์ €์žฅ) โ†’ ContentPreview (์ด๋ฏธ์ง€ ์ƒ์„ฑ) +``` diff --git a/QUICK_TEST.md b/QUICK_TEST.md new file mode 100644 index 0000000..8a1b748 --- /dev/null +++ b/QUICK_TEST.md @@ -0,0 +1,145 @@ +# ๐Ÿš€ AI ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋น ๋ฅธ ํ…Œ์ŠคํŠธ ๊ฐ€์ด๋“œ + +## โšก ๊ฐ€์žฅ ๋น ๋ฅธ ๋ฐฉ๋ฒ• (๊ธฐ์กด ์ด๋ฏธ์ง€ ํ™•์ธ) + +```bash +# 1. ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰ +npm run dev + +# 2. ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋ฐ”๋กœ ์ ‘์† +http://localhost:3000/events/create?event-creation.step=contentPreview +``` + +โœจ **๋!** ์ž๋™์œผ๋กœ Mock ๋ฐ์ดํ„ฐ(eventId: "1761634317010")๊ฐ€ ์ƒ์„ฑ๋˜๊ณ  ๊ธฐ์กด ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€๋ฅผ ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค. + +๐Ÿ’ก **์ด๋ฏธ์ง€๊ฐ€ ์—†์„ ๊ฒฝ์šฐ**: "์ด๋ฏธ์ง€ ์ƒ์„ฑํ•˜๊ธฐ" ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ์ƒˆ๋กœ์šด ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + +--- + +## ๐Ÿ“‹ ์ปค์Šคํ…€ ๋ฐ์ดํ„ฐ๋กœ ํ…Œ์ŠคํŠธ (์„ ํƒ์‚ฌํ•ญ) + +### 1๋‹จ๊ณ„: ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰ + +```bash +npm run dev +``` + +### 2๋‹จ๊ณ„: Mock ๋ฐ์ดํ„ฐ ์„ค์ • (3๊ฐ€์ง€ ๋ฐฉ๋ฒ• ์ค‘ ์„ ํƒ) + +### โœจ ๋ฐฉ๋ฒ• A: ์›น UI ์‚ฌ์šฉ (๊ฐ€์žฅ ์‰ฌ์›€!) + +๋ธŒ๋ผ์šฐ์ €์—์„œ ์—ด๊ธฐ: +``` +http://localhost:3000/init-mock-data.html +``` + +"LocalStorage์— ์ €์žฅํ•˜๊ธฐ" ๋ฒ„ํŠผ ํด๋ฆญ โ†’ ์™„๋ฃŒ! + +--- + +### ๋ฐฉ๋ฒ• B: ๋ธŒ๋ผ์šฐ์ € ์ฝ˜์†” ์‚ฌ์šฉ + +1. `http://localhost:3000` ์ ‘์† +2. F12 (๊ฐœ๋ฐœ์ž ๋„๊ตฌ) โ†’ Console ํƒญ +3. ๋‹ค์Œ ์ฝ”๋“œ ๋ณต์‚ฌ & ๋ถ™์—ฌ๋„ฃ๊ธฐ: + +```javascript +localStorage.setItem('eventCreationData', JSON.stringify({ + eventDraftId: "1761634317010", + eventTitle: "๋งฅ์ฃผ ํŒŒํ‹ฐ ์ด๋ฒคํŠธ", + eventDescription: "๊ฐ•๋‚จ์—์„œ ์—ด๋ฆฌ๋Š” ์‹ ๋‚˜๋Š” ๋งฅ์ฃผ ํŒŒํ‹ฐ์— ์ฐธ์—ฌํ•˜์„ธ์š”!", + industry: "์Œ์‹์ ", + location: "๊ฐ•๋‚จ", + trends: ["ํŒŒํ‹ฐ", "๋งฅ์ฃผ", "์ƒ๋งฅ์ฃผ"], + prize: "์ƒ๋งฅ์ฃผ 1์ž”" +})); +``` + +--- + +### ๋ฐฉ๋ฒ• C: HTML ํŒŒ์ผ ์ง์ ‘ ์—ด๊ธฐ + +```bash +open test-localstorage.html +``` + +## 3๋‹จ๊ณ„: ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํŽ˜์ด์ง€ ์ ‘์† + +๋ธŒ๋ผ์šฐ์ €์—์„œ ์—ด๊ธฐ: +``` +http://localhost:3000/events/create?event-creation.step=contentPreview +``` + +## 4๋‹จ๊ณ„: ์ž๋™ ์‹คํ–‰ ํ™•์ธ โœ… + +1. ํŽ˜์ด์ง€ ๋กœ๋”ฉ๋˜๋ฉด ์ž๋™์œผ๋กœ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹œ์ž‘ +2. ๋กœ๋”ฉ ์Šคํ”ผ๋„ˆ์™€ ์ง„ํ–‰๋ฅ  ํ™•์ธ +3. ์•ฝ 60์ดˆ ํ›„ 3๊ฐ€์ง€ ์Šคํƒ€์ผ ์ด๋ฏธ์ง€ ์™„์„ฑ + - ์Šคํƒ€์ผ 1: ์‹ฌํ”Œ + - ์Šคํƒ€์ผ 2: ํ™”๋ ค + - ์Šคํƒ€์ผ 3: ํŠธ๋ Œ๋”” + +## ์˜ˆ์ƒ ๊ฒฐ๊ณผ + +### ์ด๋ฏธ์ง€๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ +- โœ… **์ฆ‰์‹œ ํ‘œ์‹œ**: ๋กœ๋”ฉ ํ›„ ๋ฐ”๋กœ ์ด๋ฏธ์ง€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ํ™”๋ฉด +- โœ… **3๊ฐœ ์Šคํƒ€์ผ ์ด๋ฏธ์ง€**: SIMPLE, FANCY, TRENDY ๊ฐ๊ฐ ์ตœ์‹  ์ด๋ฏธ์ง€ ํ‘œ์‹œ +- โœ… **์ด๋ฏธ์ง€ ์„ ํƒ**: ๋ผ๋””์˜ค ๋ฒ„ํŠผ์œผ๋กœ ์›ํ•˜๋Š” ์Šคํƒ€์ผ ์„ ํƒ +- โœ… **์žฌ์ƒ์„ฑ ๋ฒ„ํŠผ**: "์ด๋ฏธ์ง€ ์žฌ์ƒ์„ฑ" ๋ฒ„ํŠผ์œผ๋กœ ์ƒˆ๋กœ์šด ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๊ฐ€๋Šฅ +- โœ… **ํฌ๊ฒŒ๋ณด๊ธฐ**: ๊ฐ ์ด๋ฏธ์ง€ ํด๋ฆญ ์‹œ ์ „์ฒดํ™”๋ฉด ๋ฏธ๋ฆฌ๋ณด๊ธฐ + +### ์ด๋ฏธ์ง€๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ +- โš ๏ธ **์—๋Ÿฌ ๋ฉ”์‹œ์ง€**: "์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์ด๋ฏธ์ง€๋ฅผ ๋จผ์ € ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”." +- ๐Ÿ”„ **์ƒ์„ฑ ๋ฒ„ํŠผ**: "์ด๋ฏธ์ง€ ์ƒ์„ฑํ•˜๊ธฐ" ๋ฒ„ํŠผ ํด๋ฆญ +- โณ **์ƒ์„ฑ ๋Œ€๊ธฐ**: API ์š”์ฒญ ํ›„ 2์ดˆ ๋’ค ์ž๋™์œผ๋กœ ์ด๋ฏธ์ง€ ์กฐํšŒ +- โœ… **์ด๋ฏธ์ง€ ํ‘œ์‹œ**: ์ƒ์„ฑ ์™„๋ฃŒ๋œ ์ด๋ฏธ์ง€ ์ž๋™ ํ‘œ์‹œ + +## ๋ฌธ์ œ ํ•ด๊ฒฐ + +### ~~"์ด๋ฒคํŠธ ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค" ์—๋Ÿฌ~~ +โ†’ โœ… **ํ•ด๊ฒฐ๋จ!** ์ด์ œ ์ž๋™์œผ๋กœ Mock ๋ฐ์ดํ„ฐ๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. + +### ~~Network Error / CORS ์—๋Ÿฌ~~ +โ†’ โœ… **ํ•ด๊ฒฐ๋จ!** Next.js API proxy๋ฅผ ํ†ตํ•ด CORS ๋ฌธ์ œ ์šฐํšŒ +โ†’ ํ”„๋ก ํŠธ์—”๋“œ๊ฐ€ `/api/content/*` โ†’ ๋ฐฑ์—”๋“œ `localhost:8084` ๋กœ ์ž๋™ ํ”„๋ก์‹œ + +### ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹คํŒจ +โ†’ Content API (localhost:8084) ์‹คํ–‰ ์—ฌ๋ถ€ ํ™•์ธ +โ†’ ํ„ฐ๋ฏธ๋„์—์„œ ํ™•์ธ: `curl http://localhost:8084/api/v1/content/events/7777/images` + +### ์ด๋ฏธ์ง€๊ฐ€ ํ‘œ์‹œ๋˜์ง€ ์•Š์Œ +โ†’ ๋„คํŠธ์›Œํฌ ํƒญ์—์„œ CDN URL ๋กœ๋“œ ์ƒํƒœ ํ™•์ธ +โ†’ Azure Blob Storage ์ ‘๊ทผ ๊ถŒํ•œ ํ™•์ธ + +## API ํ™•์ธ + +```bash +# ์ด๋ฒคํŠธ 1761634317010์˜ ์ด๋ฏธ์ง€ ํ™•์ธ +curl http://localhost:8084/api/v1/content/events/1761634317010/images + +# ํ”„๋ก ํŠธ์—”๋“œ ํ”„๋ก์‹œ๋ฅผ ํ†ตํ•œ ํ™•์ธ (๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰ ์ค‘) +curl http://localhost:3000/api/content/events/1761634317010/images +``` + +## ๋””๋ฒ„๊น… + +๋ธŒ๋ผ์šฐ์ € ๊ฐœ๋ฐœ์ž ๋„๊ตฌ (F12) โ†’ Console ํƒญ์—์„œ ๋‹ค์Œ ๋กœ๊ทธ ํ™•์ธ: + +``` +๐Ÿ“ฅ Loading generated images for event: 1761634317010 +โœ… Images loaded from API: 6 [...] +๐Ÿ“ธ Processing image 1: { id: 1, style: 'SIMPLE', platform: 'INSTAGRAM', ... } + โœ… Selected as latest SIMPLE image +๐Ÿ“ธ Processing image 2: { id: 3, style: 'FANCY', platform: 'INSTAGRAM', ... } + โœ… Selected as latest FANCY image +๐Ÿ“ธ Processing image 3: { id: 5, style: 'TRENDY', platform: 'INSTAGRAM', ... } + โœ… Selected as latest TRENDY image +๐ŸŽจ Image map created with entries: { SIMPLE: 'YES โœ…', FANCY: 'YES โœ…', TRENDY: 'YES โœ…', totalSize: 3 } +โœ… ์ด๋ฏธ์ง€ ๋กœ๋“œ ์™„๋ฃŒ! ๋ฏธ๋ฆฌ๋ณด๊ธฐ ํ™”๋ฉด์œผ๋กœ ์ „ํ™˜ํ•ฉ๋‹ˆ๋‹ค. +๐Ÿ–ผ๏ธ Rendering SIMPLE: { hasImage: true, imageDataExists: true, cdnUrl: 'https://blob...' } +โœ… SIMPLE image loaded successfully +``` + +--- + +๋” ์ž์„ธํ•œ ๋‚ด์šฉ์€ `MOCK_DATA_SETUP.md` ์ฐธ์กฐ diff --git a/TEST_URLS.md b/TEST_URLS.md new file mode 100644 index 0000000..df456ed --- /dev/null +++ b/TEST_URLS.md @@ -0,0 +1,117 @@ +# ๐Ÿ”— AI ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ…Œ์ŠคํŠธ URL ๊ฐ€์ด๋“œ + +## โœ… ์˜ฌ๋ฐ”๋ฅธ URL + +### ContentPreview Step ์ง์ ‘ ์ ‘์† +``` +http://localhost:3000/events/create?event-creation.step=contentPreview +``` + +๋˜๋Š” ๊ฐ„๋‹จํ•˜๊ฒŒ: +``` +http://localhost:3000/events/create?step=contentPreview +``` + +### Mock ๋ฐ์ดํ„ฐ ์„ค์ • ํŽ˜์ด์ง€ +``` +http://localhost:3000/init-mock-data.html +``` + +## ๐Ÿ“ URL ํŒŒ๋ผ๋ฏธํ„ฐ ์„ค๋ช… + +- `event-creation.step=contentPreview` - Funnel์˜ step์„ contentPreview๋กœ ์„ค์ • +- `step=contentPreview` - ๊ฐ„๋‹จํ•œ ํ˜•์‹ (funnel id๊ฐ€ event-creation์ผ ๋•Œ) + +## ๐ŸŽฏ ์ „์ฒด ํ”Œ๋กœ์šฐ ํ…Œ์ŠคํŠธ URL + +### 1. ์‹œ์ž‘ (Objective Step) +``` +http://localhost:3000/events/create +``` + +### 2. Channel Step๊นŒ์ง€ ์ง„ํ–‰ ํ›„ +``` +http://localhost:3000/events/create?event-creation.step=channel +``` + +### 3. ContentPreview Step (์ด๋ฏธ์ง€ ์ƒ์„ฑ) +``` +http://localhost:3000/events/create?event-creation.step=contentPreview +``` + +## ๐Ÿ’ก ์ž๋™ Mock ๋ฐ์ดํ„ฐ ์ƒ์„ฑ + +์ด์ œ `contentPreview` ํŽ˜์ด์ง€์— ์ง์ ‘ ์ ‘์†ํ•˜๋ฉด: + +1. โœ… localStorage ํ™•์ธ +2. โœ… ๋ฐ์ดํ„ฐ ์—†์œผ๋ฉด ์ž๋™์œผ๋กœ Mock ๋ฐ์ดํ„ฐ ์ƒ์„ฑ +3. โœ… ์ฆ‰์‹œ AI ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹œ์ž‘ + +**๋” ์ด์ƒ ์ˆ˜๋™์œผ๋กœ Mock ๋ฐ์ดํ„ฐ๋ฅผ ์„ค์ •ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค!** + +## ๐Ÿงช ํ…Œ์ŠคํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค + +### ์‹œ๋‚˜๋ฆฌ์˜ค 1: ๊ฐ€์žฅ ๋น ๋ฅธ ํ…Œ์ŠคํŠธ +```bash +# 1. ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰ +npm run dev + +# 2. ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋ฐ”๋กœ ์ ‘์† +http://localhost:3000/events/create?event-creation.step=contentPreview +``` +โ†’ ์ž๋™์œผ๋กœ Mock ๋ฐ์ดํ„ฐ ์ƒ์„ฑ & ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹œ์ž‘! + +### ์‹œ๋‚˜๋ฆฌ์˜ค 2: ์ปค์Šคํ…€ ๋ฐ์ดํ„ฐ๋กœ ํ…Œ์ŠคํŠธ +```bash +# 1. Mock ๋ฐ์ดํ„ฐ ์„ค์ • ํŽ˜์ด์ง€ ์—ด๊ธฐ +http://localhost:3000/init-mock-data.html + +# 2. ์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ž…๋ ฅ ํ›„ ์ €์žฅ + +# 3. ContentPreview ์ ‘์† +http://localhost:3000/events/create?event-creation.step=contentPreview +``` + +### ์‹œ๋‚˜๋ฆฌ์˜ค 3: ์ „์ฒด ํ”Œ๋กœ์šฐ ํ…Œ์ŠคํŠธ +```bash +# 1. ์ฒ˜์Œ๋ถ€ํ„ฐ ์‹œ์ž‘ +http://localhost:3000/events/create + +# 2. Objective ์„ ํƒ + +# 3. Recommendation ํ™•์ธ + +# 4. Channel ์„ ํƒ (SNS, ์šฐ๋ฆฌ๋™๋„คTV, ์ง€๋‹ˆTV ์ค‘ ํ•˜๋‚˜) + +# 5. ์ž๋™์œผ๋กœ ContentPreview๋กœ ์ด๋™ํ•˜๋ฉฐ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹œ์ž‘ +``` + +## ๐Ÿ› ๋ฌธ์ œ ํ•ด๊ฒฐ + +### "์ด๋ฒคํŠธ ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค" ์—๋Ÿฌ +โ†’ ์ด์ œ ์ด ์—๋Ÿฌ๋Š” ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค! ์ž๋™์œผ๋กœ Mock ๋ฐ์ดํ„ฐ๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. + +### ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹คํŒจ +```bash +# Content API ์„œ๋ฒ„ ํ™•์ธ +curl http://localhost:8084/api/v1/content/events/7777/images + +# API ์„œ๋ฒ„๊ฐ€ ๊บผ์ ธ์žˆ๋‹ค๋ฉด ์‹คํ–‰ ํ•„์š” +``` + +### ๋‹ค๋ฅธ ์ด๋ฒคํŠธ ID๋กœ ํ…Œ์ŠคํŠธํ•˜๊ณ  ์‹ถ์„ ๋•Œ +```javascript +// ๋ธŒ๋ผ์šฐ์ € ์ฝ˜์†”์—์„œ +localStorage.setItem('eventCreationData', JSON.stringify({ + eventDraftId: 8888, // ๋‹ค๋ฅธ ID + eventTitle: "์ปคํ”ผ ํ• ์ธ ์ด๋ฒคํŠธ", + eventDescription: "์‹ ๋ฉ”๋‰ด ์ถœ์‹œ ๊ธฐ๋…", + industry: "์นดํŽ˜", + location: "ํ™๋Œ€", + trends: ["์ปคํ”ผ", "ํ• ์ธ"], + prize: "์•„๋ฉ”๋ฆฌ์นด๋…ธ 1์ž”" +})); + +// ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ +location.reload(); +``` diff --git a/next.config.js b/next.config.js index 7577db8..aabb89d 100644 --- a/next.config.js +++ b/next.config.js @@ -6,7 +6,7 @@ const nextConfig = { emotion: true, }, images: { - domains: ['localhost'], + domains: ['localhost', 'blobkteventstorage.blob.core.windows.net'], formats: ['image/webp', 'image/avif'], }, env: { diff --git a/public/init-mock-data.html b/public/init-mock-data.html new file mode 100644 index 0000000..9546666 --- /dev/null +++ b/public/init-mock-data.html @@ -0,0 +1,205 @@ + + + + + + Mock ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™” + + + +
+

๐ŸŽจ Mock ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™”

+

์ด๋ฒคํŠธ ์ƒ์„ฑ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ

+ +
+

๐Ÿ“‹ ์ €์žฅ๋  ๋ฐ์ดํ„ฐ

+
+ ์ด๋ฒคํŠธ ID: + 1761634317010 +
+
+ ์ œ๋ชฉ: + ๋งฅ์ฃผ ํŒŒํ‹ฐ ์ด๋ฒคํŠธ +
+
+ ์„ค๋ช…: + ๊ฐ•๋‚จ์—์„œ ์—ด๋ฆฌ๋Š” ์‹ ๋‚˜๋Š” ๋งฅ์ฃผ ํŒŒํ‹ฐ +
+
+ ์—…์ข…: + ์Œ์‹์  +
+
+ ์ง€์—ญ: + ๊ฐ•๋‚จ +
+
+ ํŠธ๋ Œ๋“œ: + ํŒŒํ‹ฐ, ๋งฅ์ฃผ, ์ƒ๋งฅ์ฃผ +
+
+ ๊ฒฝํ’ˆ: + ์ƒ๋งฅ์ฃผ 1์ž” +
+
+ + + +
+ โœ… Mock ๋ฐ์ดํ„ฐ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!
+ ์ด์ œ ์ด๋ฒคํŠธ ์ƒ์„ฑ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +
+ + +
+ + + + diff --git a/setup-mock-data.js b/setup-mock-data.js new file mode 100644 index 0000000..ccf8745 --- /dev/null +++ b/setup-mock-data.js @@ -0,0 +1,17 @@ +// Mock ๋ฐ์ดํ„ฐ๋ฅผ localStorage์— ์ €์žฅํ•˜๋Š” ์Šคํฌ๋ฆฝํŠธ + +const mockEventData = { + eventDraftId: 7777, + eventTitle: "๋งฅ์ฃผ ํŒŒํ‹ฐ ์ด๋ฒคํŠธ", + eventDescription: "๊ฐ•๋‚จ์—์„œ ์—ด๋ฆฌ๋Š” ์‹ ๋‚˜๋Š” ๋งฅ์ฃผ ํŒŒํ‹ฐ์— ์ฐธ์—ฌํ•˜์„ธ์š”!", + industry: "์Œ์‹์ ", + location: "๊ฐ•๋‚จ", + trends: ["ํŒŒํ‹ฐ", "๋งฅ์ฃผ", "์ƒ๋งฅ์ฃผ"], + prize: "์ƒ๋งฅ์ฃผ 1์ž”" +}; + +// localStorage์— ์ €์žฅ +localStorage.setItem('eventCreationData', JSON.stringify(mockEventData)); + +console.log('โœ… Mock ๋ฐ์ดํ„ฐ๊ฐ€ localStorage์— ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค:'); +console.log(JSON.stringify(mockEventData, null, 2)); diff --git a/src/app/(main)/events/create/page.tsx b/src/app/(main)/events/create/page.tsx index 1595442..a83b41d 100644 --- a/src/app/(main)/events/create/page.tsx +++ b/src/app/(main)/events/create/page.tsx @@ -15,12 +15,16 @@ export type BudgetLevel = 'low' | 'medium' | 'high'; export type EventMethod = 'online' | 'offline'; export interface EventData { + eventDraftId?: number; objective?: EventObjective; recommendation?: { budget: BudgetLevel; method: EventMethod; title: string; prize: string; + description?: string; + industry?: string; + location?: string; participationMethod: string; expectedParticipants: number; estimatedCost: number; @@ -28,6 +32,7 @@ export interface EventData { }; contentPreview?: { imageStyle: string; + images?: any[]; }; contentEdit?: { title: string; @@ -89,6 +94,18 @@ export default function EventCreatePage() { ); if (needsContent) { + // localStorage์— ์ด๋ฒคํŠธ ์ •๋ณด ์ €์žฅ + const eventData = { + eventDraftId: context.eventDraftId || Date.now(), // ์ž„์‹œ ID ์ƒ์„ฑ + eventTitle: context.recommendation?.title || '', + eventDescription: context.recommendation?.description || context.recommendation?.participationMethod || '', + industry: context.recommendation?.industry || '', + location: context.recommendation?.location || '', + trends: [], // ํ•„์š”์‹œ context์—์„œ ์ถ”๊ฐ€ + prize: context.recommendation?.prize || '', + }; + localStorage.setItem('eventCreationData', JSON.stringify(eventData)); + history.push('contentPreview', { ...context, channels }); } else { history.push('approval', { ...context, channels }); @@ -101,12 +118,10 @@ export default function EventCreatePage() { )} contentPreview={({ context, history }) => ( { + onNext={(imageStyle, images) => { history.push('contentEdit', { ...context, - contentPreview: { imageStyle }, + contentPreview: { imageStyle, images }, }); }} onSkip={() => { diff --git a/src/app/(main)/events/create/steps/ContentPreviewStep.tsx b/src/app/(main)/events/create/steps/ContentPreviewStep.tsx index 5a2f7df..a0fc4f7 100644 --- a/src/app/(main)/events/create/steps/ContentPreviewStep.tsx +++ b/src/app/(main)/events/create/steps/ContentPreviewStep.tsx @@ -12,8 +12,11 @@ import { IconButton, Dialog, Grid, + Alert, } from '@mui/material'; -import { ArrowBack, ZoomIn, Psychology } from '@mui/icons-material'; +import { ArrowBack, ZoomIn, Psychology, Refresh } from '@mui/icons-material'; +import { contentApi, ImageInfo } from '@/shared/api/contentApi'; +import Image from 'next/image'; // ๋””์ž์ธ ์‹œ์Šคํ…œ ์ƒ‰์ƒ const colors = { @@ -34,7 +37,7 @@ const colors = { }; interface ImageStyle { - id: string; + id: 'SIMPLE' | 'FANCY' | 'TRENDY'; name: string; gradient?: string; icon: string; @@ -43,19 +46,19 @@ interface ImageStyle { const imageStyles: ImageStyle[] = [ { - id: 'simple', + id: 'SIMPLE', name: '์Šคํƒ€์ผ 1: ์‹ฌํ”Œ', icon: 'celebration', }, { - id: 'fancy', + id: 'FANCY', name: '์Šคํƒ€์ผ 2: ํ™”๋ ค', gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', icon: 'auto_awesome', textColor: 'white', }, { - id: 'trendy', + id: 'TRENDY', name: '์Šคํƒ€์ผ 3: ํŠธ๋ Œ๋””', gradient: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)', icon: 'trending_up', @@ -64,50 +67,230 @@ const imageStyles: ImageStyle[] = [ ]; interface ContentPreviewStepProps { - title: string; - prize: string; - onNext: (imageStyle: string) => void; + onNext: (imageStyle: string, images: ImageInfo[]) => void; onSkip: () => void; onBack: () => void; } +interface EventCreationData { + eventDraftId: string; // Changed from number to string + eventTitle: string; + eventDescription: string; + industry: string; + location: string; + trends: string[]; + prize: string; +} + export default function ContentPreviewStep({ - title, - prize, onNext, onSkip, onBack, }: ContentPreviewStepProps) { const [loading, setLoading] = useState(true); - const [selectedStyle, setSelectedStyle] = useState(null); + const [selectedStyle, setSelectedStyle] = useState<'SIMPLE' | 'FANCY' | 'TRENDY' | null>(null); const [fullscreenOpen, setFullscreenOpen] = useState(false); - const [fullscreenStyle, setFullscreenStyle] = useState(null); + const [fullscreenImage, setFullscreenImage] = useState(null); + const [generatedImages, setGeneratedImages] = useState>(new Map()); + const [error, setError] = useState(null); + const [loadingProgress, setLoadingProgress] = useState(0); + const [loadingMessage, setLoadingMessage] = useState('์ด๋ฏธ์ง€ ์ƒ์„ฑ ์š”์ฒญ ์ค‘...'); + const [eventData, setEventData] = useState(null); useEffect(() => { - // AI ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ - const timer = setTimeout(() => { - setLoading(false); - }, 5000); + // localStorage์—์„œ ์ด๋ฒคํŠธ ๋ฐ์ดํ„ฐ ์ฝ๊ธฐ + const storedData = localStorage.getItem('eventCreationData'); + if (storedData) { + const data: EventCreationData = JSON.parse(storedData); + setEventData(data); - return () => clearTimeout(timer); + // ๋จผ์ € ์ด๋ฏธ์ง€ ์กฐํšŒ ์‹œ๋„ + loadImages(data).then((hasImages) => { + // ์ด๋ฏธ์ง€๊ฐ€ ์—†์œผ๋ฉด ์ž๋™์œผ๋กœ ์ƒ์„ฑ + if (!hasImages) { + console.log('๐Ÿ“ธ ์ด๋ฏธ์ง€๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์ž๋™์œผ๋กœ ์ƒ์„ฑ์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค...'); + handleGenerateImagesAuto(data); + } + }); + } else { + // Mock ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฉด ์ž๋™์œผ๋กœ ์„ค์ • + const mockData: EventCreationData = { + eventDraftId: "1761634317010", // Changed to string + eventTitle: "๋งฅ์ฃผ ํŒŒํ‹ฐ ์ด๋ฒคํŠธ", + eventDescription: "๊ฐ•๋‚จ์—์„œ ์—ด๋ฆฌ๋Š” ์‹ ๋‚˜๋Š” ๋งฅ์ฃผ ํŒŒํ‹ฐ์— ์ฐธ์—ฌํ•˜์„ธ์š”!", + industry: "์Œ์‹์ ", + location: "๊ฐ•๋‚จ", + trends: ["ํŒŒํ‹ฐ", "๋งฅ์ฃผ", "์ƒ๋งฅ์ฃผ"], + prize: "์ƒ๋งฅ์ฃผ 1์ž”" + }; + + console.log('โš ๏ธ localStorage์— ์ด๋ฒคํŠธ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. Mock ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.'); + localStorage.setItem('eventCreationData', JSON.stringify(mockData)); + setEventData(mockData); + loadImages(mockData); + } + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const handleStyleSelect = (styleId: string) => { + const loadImages = async (data: EventCreationData): Promise => { + try { + setError(null); + + console.log('๐Ÿ“ฅ Loading images for event:', data.eventDraftId); + const images = await contentApi.getImages(data.eventDraftId); + console.log('โœ… Images loaded from API:', images.length, images); + + if (!images || images.length === 0) { + console.warn('โš ๏ธ No images found.'); + return false; // ์ด๋ฏธ์ง€ ์—†์Œ + } + + const imageMap = new Map(); + + // ๊ฐ ์Šคํƒ€์ผ๋ณ„๋กœ ๊ฐ€์žฅ ์ตœ์‹  ์ด๋ฏธ์ง€๋งŒ ์„ ํƒ (createdAt ๊ธฐ์ค€) + images.forEach((image, index) => { + console.log(`๐Ÿ“ธ Processing image ${index + 1}:`, { + id: image.id, + eventId: image.eventId, + style: image.style, + platform: image.platform, + cdnUrl: image.cdnUrl?.substring(0, 50) + '...', + createdAt: image.createdAt, + }); + + if (image.platform === 'INSTAGRAM') { + const existing = imageMap.get(image.style); + if (!existing || new Date(image.createdAt) > new Date(existing.createdAt)) { + console.log(` โœ… Selected as latest ${image.style} image`); + imageMap.set(image.style, image); + } else { + console.log(` โญ๏ธ Skipped (older than existing ${image.style} image)`); + } + } else { + console.log(` โญ๏ธ Skipped (platform: ${image.platform})`); + } + }); + + console.log('๐ŸŽจ Image map created with entries:', { + SIMPLE: imageMap.has('SIMPLE') ? 'YES โœ…' : 'NO โŒ', + FANCY: imageMap.has('FANCY') ? 'YES โœ…' : 'NO โŒ', + TRENDY: imageMap.has('TRENDY') ? 'YES โœ…' : 'NO โŒ', + totalSize: imageMap.size, + }); + + console.log('๐Ÿ–ผ๏ธ Image map details:', Array.from(imageMap.entries()).map(([style, img]) => ({ + style, + id: img.id, + eventId: img.eventId, + cdnUrl: img.cdnUrl?.substring(0, 60) + '...', + }))); + + setGeneratedImages(imageMap); + console.log('โœ… Images loaded successfully!'); + return true; // ์ด๋ฏธ์ง€ ์žˆ์Œ + } catch (err) { + console.error('โŒ Load images error:', err); + // API ์—๋Ÿฌ๋Š” polling์—์„œ ๋ฌด์‹œ (๊ณ„์† ์‹œ๋„) + return false; + } + }; + + const handleStyleSelect = (styleId: 'SIMPLE' | 'FANCY' | 'TRENDY') => { setSelectedStyle(styleId); }; - const handlePreview = (style: ImageStyle, e: React.MouseEvent) => { + const handlePreview = (image: ImageInfo, e: React.MouseEvent) => { e.stopPropagation(); - setFullscreenStyle(style); + setFullscreenImage(image); setFullscreenOpen(true); }; const handleNext = () => { if (selectedStyle) { - onNext(selectedStyle); + const allImages = Array.from(generatedImages.values()); + onNext(selectedStyle, allImages); } }; + const handleGenerateImagesAuto = async (data: EventCreationData) => { + try { + setLoading(true); + setError(null); + setLoadingProgress(0); + setLoadingMessage('์ด๋ฏธ์ง€ ์ƒ์„ฑ ์š”์ฒญ ์ค‘...'); + + console.log('๐ŸŽจ Auto-generating images for event:', data.eventDraftId); + + // ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์š”์ฒญ (202 Accepted ์‘๋‹ต๋งŒ ํ™•์ธ) + await contentApi.generateImages({ + eventId: data.eventDraftId, + eventTitle: data.eventTitle, + eventDescription: data.eventDescription, + industry: data.industry, + location: data.location, + trends: data.trends, + styles: ['SIMPLE', 'FANCY', 'TRENDY'], + platforms: ['INSTAGRAM'], + }); + + console.log('โœ… Image generation request accepted (202)'); + console.log('โณ AI ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค‘... ์•ฝ 60์ดˆ ์†Œ์š”๋ฉ๋‹ˆ๋‹ค.'); + + setLoadingProgress(10); + setLoadingMessage('AI๊ฐ€ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์žˆ์–ด์š”...'); + + // ์ƒ์„ฑ ์™„๋ฃŒ๊นŒ์ง€ ๋Œ€๊ธฐ (polling) + let attempts = 0; + const maxAttempts = 30; // ์ตœ๋Œ€ 60์ดˆ (2์ดˆ * 30ํšŒ) + + const pollImages = async () => { + attempts++; + console.log(`๐Ÿ”„ ์ด๋ฏธ์ง€ ํ™•์ธ ์‹œ๋„ ${attempts}/${maxAttempts}...`); + + // ์ง„ํ–‰๋ฅ  ์—…๋ฐ์ดํŠธ (10% ~ 90%) + const progress = Math.min(10 + (attempts / maxAttempts) * 80, 90); + setLoadingProgress(progress); + + // ๋‹จ๊ณ„๋ณ„ ๋ฉ”์‹œ์ง€ ์—…๋ฐ์ดํŠธ + if (attempts < 10) { + setLoadingMessage('AI๊ฐ€ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์žˆ์–ด์š”...'); + } else if (attempts < 20) { + setLoadingMessage('์Šคํƒ€์ผ์„ ์ ์šฉํ•˜๊ณ  ์žˆ์–ด์š”...'); + } else { + setLoadingMessage('๊ฑฐ์˜ ์™„๋ฃŒ๋˜์—ˆ์–ด์š”...'); + } + + const hasImages = await loadImages(data); + + if (hasImages) { + console.log('โœ… ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์™„๋ฃŒ!'); + setLoadingProgress(100); + setLoadingMessage('์ด๋ฏธ์ง€ ์ƒ์„ฑ ์™„๋ฃŒ!'); + setTimeout(() => setLoading(false), 500); // 100% ์ž ๊น ๋ณด์—ฌ์ฃผ๊ธฐ + } else if (attempts < maxAttempts) { + // 2์ดˆ ํ›„ ๋‹ค์‹œ ์‹œ๋„ + setTimeout(pollImages, 2000); + } else { + console.warn('โš ๏ธ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹œ๊ฐ„ ์ดˆ๊ณผ. "์ด๋ฏธ์ง€ ์žฌ์ƒ์„ฑ" ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”.'); + setError('์ด๋ฏธ์ง€ ์ƒ์„ฑ์ด ์™„๋ฃŒ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์ž ์‹œ ํ›„ "์ด๋ฏธ์ง€ ์žฌ์ƒ์„ฑ" ๋ฒ„ํŠผ์„ ํด๋ฆญํ•ด์ฃผ์„ธ์š”.'); + setLoading(false); + } + }; + + // ์ฒซ ๋ฒˆ์งธ ํ™•์ธ์€ 5์ดˆ ํ›„ ์‹œ์ž‘ (์ƒ์„ฑ ์‹œ์ž‘ ์‹œ๊ฐ„ ๊ณ ๋ ค) + setTimeout(pollImages, 5000); + } catch (err) { + console.error('โŒ Image generation request error:', err); + setError('์ด๋ฏธ์ง€ ์ƒ์„ฑ ์š”์ฒญ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.'); + setLoading(false); + } + }; + + const handleGenerateImages = async () => { + if (!eventData) return; + handleGenerateImagesAuto(eventData); + }; + if (loading) { return ( @@ -121,7 +304,7 @@ export default function ContentPreviewStep({ - + {/* ๊ทธ๋ผ๋ฐ์ด์…˜ ์Šคํ”ผ๋„ˆ */} - - AI ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค‘ - + + {/* ์ง„ํ–‰๋ฅ  ๋ฐ” */} + + + + {loadingMessage} + + + {Math.round(loadingProgress)}% + + + + + + + + - ๋”ฅ๋Ÿฌ๋‹ ๋ชจ๋ธ์ด ์ด๋ฒคํŠธ์— ์–ด์šธ๋ฆฌ๋Š” -
- ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์žˆ์–ด์š”... -
- - ์˜ˆ์ƒ ์‹œ๊ฐ„: 5์ดˆ + {generatedImages.size > 0 ? ( + <> + ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€๋ฅผ ํ™•์ธํ•˜๊ณ  ์žˆ์–ด์š” +
+ ์ž ์‹œ๋งŒ ๊ธฐ๋‹ค๋ ค์ฃผ์„ธ์š”! + + ) : ( + <> + AI๊ฐ€ ์ด๋ฒคํŠธ์— ๋งž๋Š” ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์žˆ์–ด์š” +
+ ์•ฝ 60์ดˆ ์ •๋„ ์†Œ์š”๋ฉ๋‹ˆ๋‹ค + + )}
+ {error && ( + + {error} + + + )}
@@ -179,7 +414,18 @@ export default function ContentPreviewStep({ } return ( - + {/* Header */} @@ -191,11 +437,35 @@ export default function ContentPreviewStep({ + + {generatedImages.size > 0 && ( + + โœจ ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€๋ฅผ ํ™•์ธํ•˜๊ณ  ์Šคํƒ€์ผ์„ ์„ ํƒํ•˜์„ธ์š” + + )} + + + ์ด๋ฒคํŠธ์— ์–ด์šธ๋ฆฌ๋Š” ์Šคํƒ€์ผ์„ ์„ ํƒํ•˜์„ธ์š” - handleStyleSelect(e.target.value)}> + handleStyleSelect(e.target.value as 'SIMPLE' | 'FANCY' | 'TRENDY')}> {imageStyles.map((style) => ( @@ -237,46 +507,82 @@ export default function ContentPreviewStep({ sx={{ width: '100%', aspectRatio: '1 / 1', - background: style.gradient || colors.gray[100], - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - p: 6, - textAlign: 'center', + position: 'relative', + overflow: 'hidden', + bgcolor: colors.gray[100], }} > - - {style.icon} - - - {title} - - - {prize} - + {(() => { + const hasImage = generatedImages.has(style.id); + const imageData = generatedImages.get(style.id); + console.log(`๐Ÿ–ผ๏ธ Rendering ${style.id}:`, { + hasImage, + imageDataExists: !!imageData, + fullCdnUrl: imageData?.cdnUrl, + mapSize: generatedImages.size, + mapKeys: Array.from(generatedImages.keys()), + }); + return hasImage && imageData ? ( + {style.name} console.log(`โœ… ${style.id} image loaded successfully from:`, imageData.cdnUrl)} + onError={(e) => { + console.error(`โŒ ${style.id} image load error:`, e); + console.error(` Failed URL:`, imageData.cdnUrl); + }} + /> + ) : ( + + + {style.icon} + + + {eventData?.eventTitle || '์ด๋ฒคํŠธ'} + + + {eventData?.prize || '๊ฒฝํ’ˆ'} + + + ); + })()} {/* ํฌ๊ฒŒ๋ณด๊ธฐ ๋ฒ„ํŠผ */} @@ -284,7 +590,13 @@ export default function ContentPreviewStep({ + + + + +
+ โœ… localStorage์— ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!
+ ์ด์ œ ์ด๋ฒคํŠธ ์ƒ์„ฑ ํ”Œ๋กœ์šฐ์—์„œ channel โ†’ contentPreview๋กœ ์ด๋™ํ•˜๋ฉด
+ ์ž๋™์œผ๋กœ AI ์ด๋ฏธ์ง€ ์ƒ์„ฑ์ด ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค. +
+ +
+ ํ˜„์žฌ localStorage ๋ฐ์ดํ„ฐ: +
์—†์Œ
+
+ + + + +