diff --git a/backend/src/routes/admin.js b/backend/src/routes/admin.js index 9ae03b9..95df30a 100644 --- a/backend/src/routes/admin.js +++ b/backend/src/routes/admin.js @@ -93,13 +93,21 @@ function canManageAdminRole(actingUser, primaryAdmin) { } router.post('/games', requireAdmin, async (req, res) => { - const schema = z.object({ id: z.string().min(1), name: z.string().min(1).max(60) }) + const schema = z.object({ + id: z.string().min(1), + name: z.string().min(1).max(60), + thumbnailSrc: z.string().max(255).optional().default(''), + }) const parsed = schema.safeParse(req.body) if (!parsed.success) return res.status(400).json({ error: 'bad_request' }) const exists = await findGameById(parsed.data.id) if (exists) return res.status(409).json({ error: 'game_id_taken' }) const game = await createGame({ id: parsed.data.id, name: parsed.data.name }) - res.json({ game }) + if (parsed.data.thumbnailSrc) { + const copiedThumb = await copyUploadIntoGameAsset(parsed.data.thumbnailSrc) + await updateGameThumbnail(game.id, copiedThumb) + } + res.json({ game: await findGameById(game.id) }) }) router.patch('/games/display-order', requireAdmin, async (req, res) => { diff --git a/docs/history.md b/docs/history.md index 3dab20e..fa5a00d 100644 --- a/docs/history.md +++ b/docs/history.md @@ -1,5 +1,9 @@ # 의사결정 이력 +## 2026-04-02 v1.3.55 +- 관리자 요청/업로드 배지는 문구만 다르면 빠르게 구분하기 어려우므로, 같은 `pill` 구조를 유지하되 색으로도 역할을 나누는 편이 운영 판단에 더 적합하다고 정리했다. +- 신규 템플릿 요청으로 새 게임을 만들 때는 아이템만 가져오고 썸네일이 비어 있으면 식별성이 떨어지므로, 요청 썸네일도 기본값으로 함께 승계하는 편이 맞다고 판단했다. + ## 2026-04-02 v1.3.54 - 관리자 요청 카드는 운영자가 이미 흐름을 알고 있다는 전제에서, 설명형 힌트보다 즉시 판단에 필요한 메타와 액션만 남기는 편이 더 적합하다고 정리했다. - 요청 종류 표시는 중복 텍스트보다 오른쪽 상단의 짧은 상태 배지 하나로 고정하고, 하단 액션 줄은 `보조 링크는 왼쪽 / 실제 처리 버튼은 오른쪽` 구조가 더 읽기 쉽다고 판단했다. diff --git a/docs/todo.md b/docs/todo.md index 3beec1a..ecc8225 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -6,6 +6,7 @@ - 관리자 화면은 섹션 경로 분리까지 끝났으므로, 다음 단계에서는 `AdminView.vue`를 실제 레이아웃 뷰와 섹션별 라우트 컴포넌트로 더 쪼갤지 결정한다. - 관리자 공통 스타일은 `adminUiScope` 기준으로 다시 묶었으므로, 다음 단계에서는 각 섹션을 별도 파일로 완전히 분리할 때 스타일도 `admin.css` 또는 섹션별 스타일로 옮길지 결정한다. - 관리자 요청 카드 밀도는 줄였으므로, 다음 단계에서는 전체 티어표 카드와 요청 카드의 상단/하단 액션 정렬을 한 번 더 통일할지 비교 QA한다. +- 신규 템플릿 요청 썸네일 기본 승계는 붙였으므로, 다음 단계에서는 요청 아이템 반영 후 `처리 완료`까지의 관리자 흐름을 실제 데이터로 한 번 더 QA한다. - 관리자 게임 아이템 순서 저장은 추가됐으므로, 다음 단계에서는 새 아이템 추가 직후 `자동 맨 앞 배치`와 `관리자 수동 고정 순서`의 우선순위를 실제 운영 흐름 기준으로 한 번 더 QA한다. - 관리자 템플릿 요청 미리보기는 실제 완성본 iframe 방식과의 체감 차이를 마지막으로 한 번 더 QA한다. - 라이트모드/다크모드 2차 보정까지 반영했으므로, 남은 작업은 전체 화면을 실제 사용 흐름으로 돌려 보며 대비·명도·아이콘 가독성을 미세하게 QA하는 최종 테마 점검 단계로 가져간다. diff --git a/docs/update.md b/docs/update.md index 3cd19a6..8ca9718 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,10 @@ # 업데이트 로그 +## 2026-04-02 v1.3.55 +- 관리자 요청 카드 오른쪽 상단의 `신규 템플릿 / 보유 템플릿` 배지는 서로 다른 색상으로 분리해, 카드 타입을 텍스트보다 더 빠르게 구분할 수 있게 조정함. +- 게임 관리의 기본 아이템 추가 미리보기에서도 `요청 아이템 / 직접 추가 파일` 배지를 서로 다른 색상으로 구분해, 요청 반영분과 직접 업로드분이 한눈에 섞이지 않도록 정리함. +- 신규 템플릿 요청에서 `새 게임 만들기`를 진행할 때는 요청 티어표 대표 썸네일도 함께 새 게임 썸네일로 복사되도록 보강해, 관리자가 이후 수정하더라도 초기 식별용 썸네일은 바로 이어받을 수 있게 함. + ## 2026-04-02 v1.3.54 - 관리자 `티어표 관리` 요청 카드에서는 사용법 힌트 문구와 중복 타입 텍스트를 제거해, 카드 본문이 관리 정보만 더 빠르게 읽히도록 정리함. - `신규 템플릿 / 보유 템플릿` 구분은 카드 오른쪽 상단의 별도 배지로 옮기고, 기존 `추가 아이템 / 확인함 여부` 배지는 그대로 유지해 정보 계층을 더 단순하게 맞춤. diff --git a/frontend/src/components/admin/AdminGamesSection.vue b/frontend/src/components/admin/AdminGamesSection.vue index a6489c7..1e31671 100644 --- a/frontend/src/components/admin/AdminGamesSection.vue +++ b/frontend/src/components/admin/AdminGamesSection.vue @@ -119,7 +119,9 @@ const props = defineProps({
{{ draft.sourceName }}
- {{ draft.kind === 'request' ? '요청 아이템' : '직접 추가 파일' }} + + {{ draft.kind === 'request' ? '요청 아이템' : '직접 추가 파일' }} +
diff --git a/frontend/src/components/admin/AdminTierlistsSection.vue b/frontend/src/components/admin/AdminTierlistsSection.vue index b94569c..e651745 100644 --- a/frontend/src/components/admin/AdminTierlistsSection.vue +++ b/frontend/src/components/admin/AdminTierlistsSection.vue @@ -64,7 +64,10 @@ const props = defineProps({
- + {{ request.type === 'create' ? '신규 템플릿' : '보유 템플릿' }}
{{ request.sourceTierListTitle }}
diff --git a/frontend/src/composables/useAdminGameManager.js b/frontend/src/composables/useAdminGameManager.js index 75154d5..363c0b6 100644 --- a/frontend/src/composables/useAdminGameManager.js +++ b/frontend/src/composables/useAdminGameManager.js @@ -135,7 +135,11 @@ export function useAdminGameManager({ method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ id: newGameId.value.trim(), name: newGameName.value.trim() }), + body: JSON.stringify({ + id: newGameId.value.trim(), + name: newGameName.value.trim(), + thumbnailSrc: activeTemplateRequest.value?.type === 'create' ? (activeTemplateRequest.value?.thumbnailSrc || '') : '', + }), }) if (!res.ok) throw new Error('failed') diff --git a/frontend/src/composables/useAdminTemplateRequests.js b/frontend/src/composables/useAdminTemplateRequests.js index 4a81782..8a8384f 100644 --- a/frontend/src/composables/useAdminTemplateRequests.js +++ b/frontend/src/composables/useAdminTemplateRequests.js @@ -18,6 +18,7 @@ export function useAdminTemplateRequests({ id: request.id, type: request.type, status: request.status, + thumbnailSrc: request.thumbnailSrc || '', draftGameId: request.draftGameId || '', draftGameName: request.draftGameName || '', sourceTierListId: request.sourceTierListId || '', diff --git a/frontend/src/views/AdminView.vue b/frontend/src/views/AdminView.vue index a832291..cc440f1 100644 --- a/frontend/src/views/AdminView.vue +++ b/frontend/src/views/AdminView.vue @@ -2764,6 +2764,26 @@ function userAvatarFallback(user) { .adminUiScope .pill--soft { background: rgba(255, 255, 255, 0.08); } +.adminUiScope .pill--create { + border-color: rgba(56, 189, 248, 0.36); + background: rgba(56, 189, 248, 0.16); + color: rgba(224, 242, 254, 0.98); +} +.adminUiScope .pill--owned { + border-color: rgba(167, 139, 250, 0.34); + background: rgba(167, 139, 250, 0.14); + color: rgba(243, 232, 255, 0.98); +} +.adminUiScope .pill--requestItem { + border-color: rgba(250, 204, 21, 0.34); + background: rgba(250, 204, 21, 0.14); + color: rgba(254, 249, 195, 0.98); +} +.adminUiScope .pill--directFile { + border-color: rgba(52, 211, 153, 0.34); + background: rgba(52, 211, 153, 0.14); + color: rgba(209, 250, 229, 0.98); +} .adminUiScope .requestWorkspace { display: grid; gap: 14px;