diff --git a/backend/src/routes/tierlists.js b/backend/src/routes/tierlists.js
index d555e6a..7c2ceff 100644
--- a/backend/src/routes/tierlists.js
+++ b/backend/src/routes/tierlists.js
@@ -184,6 +184,8 @@ router.post('/thumbnail', requireAuth, thumbnailUpload.single('thumbnail'), asyn
router.post('/:id/template-request', requireAuth, async (req, res) => {
const schema = z.object({
type: z.enum(['create', 'update']),
+ requestTitle: z.string().trim().min(1).max(80),
+ requestDescription: z.string().trim().min(1).max(240),
})
const parsed = schema.safeParse(req.body)
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
@@ -197,9 +199,6 @@ router.post('/:id/template-request', requireAuth, async (req, res) => {
if (parsed.data.type === 'create') {
if (tierList.gameId !== FREEFORM_GAME_ID) return res.status(400).json({ error: 'freeform_required' })
- if (!(tierList.title || '').trim() || (tierList.title || '').trim() === FREEFORM_DEFAULT_TITLE) {
- return res.status(400).json({ error: 'title_required' })
- }
} else {
if (tierList.gameId === FREEFORM_GAME_ID) return res.status(400).json({ error: 'game_template_required' })
}
@@ -212,8 +211,8 @@ router.post('/:id/template-request', requireAuth, async (req, res) => {
sourceTierListId: tierList.id,
sourceGameId: tierList.gameId,
targetGameId: parsed.data.type === 'update' ? tierList.gameId : '',
- title: tierList.title,
- description: tierList.description || '',
+ title: parsed.data.requestTitle,
+ description: parsed.data.requestDescription,
thumbnailSrc: tierList.thumbnailSrc || '',
items: customItems,
})
diff --git a/docs/update.md b/docs/update.md
index 2a476c4..0a48b06 100644
--- a/docs/update.md
+++ b/docs/update.md
@@ -1,5 +1,9 @@
# 업데이트 로그
+## 2026-03-31 v1.2.60
+- 관리자 티어표 관리 카드에서 사용자가 입력한 설명을 제목 아래에 함께 노출해 요청 의도를 더 빨리 파악할 수 있게 함.
+- 템플릿 등록/업데이트 요청은 이제 에디터 모달에서 제목과 설명을 별도로 입력받고, 예시 문구와 함께 전송하도록 정리함.
+
## 2026-03-31 v1.2.59
- 관리자 아이템 상세 모달의 게임 선택을 전용 상태로 분리해 기본 선택값이 비어 있도록 바꾸고, 썸네일 아래에 배치해 정보/액션과 시각적으로 분리함.
- 커스텀 아이템이 실제로 사용 중인 게임 목록을 백엔드에서 함께 내려주고, 템플릿 요청 생성 폼에는 게임 ID와 게임 이름 라벨을 추가해 구분을 명확히 함.
diff --git a/frontend/src/views/AdminView.vue b/frontend/src/views/AdminView.vue
index 46cc143..eb1789a 100644
--- a/frontend/src/views/AdminView.vue
+++ b/frontend/src/views/AdminView.vue
@@ -1421,14 +1421,14 @@ async function saveFeaturedOrder() {
-
- 게임 ID
-
-
게임 이름
+
+ 게임 ID
+
+
@@ -1461,6 +1461,7 @@ async function saveFeaturedOrder() {
{{ tierList.title }}
+
{{ tierList.description }}
{{ tierList.gameName || tierList.gameId }} · {{ tierListAuthorDisplayName(tierList) }} · {{ tierListVisibilityLabel(tierList) }}
@@ -2950,6 +2951,15 @@ async function saveFeaturedOrder() {
font-size: 18px;
font-weight: 900;
}
+.tierAdminCard__desc {
+ margin-top: 6px;
+ color: rgba(255, 255, 255, 0.74);
+ line-height: 1.5;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
.tierAdminCard__meta {
margin-top: 4px;
opacity: 0.74;
diff --git a/frontend/src/views/TierEditorView.vue b/frontend/src/views/TierEditorView.vue
index 67fca66..e557476 100644
--- a/frontend/src/views/TierEditorView.vue
+++ b/frontend/src/views/TierEditorView.vue
@@ -43,6 +43,8 @@ const isExporting = ref(false)
const isSaveModalOpen = ref(false)
const isTemplateRequestModalOpen = ref(false)
const isTemplateUpdateModalOpen = ref(false)
+const templateRequestDraftTitle = ref('')
+const templateRequestDraftDescription = ref('')
const isDeleteModalOpen = ref(false)
const ownerId = ref('')
const authorName = ref('')
@@ -112,7 +114,8 @@ const templateRequestChecks = computed(() => [
passed: !!(title.value || '').trim() && (title.value || '').trim() !== (gameName.value || '').trim(),
},
])
-const canSubmitTemplateCreateRequest = computed(() => templateRequestChecks.value.every((item) => item.passed))
+const canSubmitTemplateCreateRequest = computed(() => templateRequestChecks.value.every((item) => item.passed) && !!templateRequestDraftTitle.value.trim() && !!templateRequestDraftDescription.value.trim())
+const canSubmitTemplateUpdateRequest = computed(() => !!templateRequestDraftTitle.value.trim() && !!templateRequestDraftDescription.value.trim())
const templateRequestTargetLabel = computed(() => (gameId.value === 'freeform' ? '새로운 템플릿' : (gameName.value || gameId.value || '선택한 게임')))
watch(error, (message) => {
@@ -510,20 +513,29 @@ function closeSaveModal() {
isSaveModalOpen.value = false
}
+function resetTemplateRequestDrafts() {
+ templateRequestDraftTitle.value = ''
+ templateRequestDraftDescription.value = ''
+}
+
function openTemplateRequestModal() {
+ resetTemplateRequestDrafts()
isTemplateRequestModalOpen.value = true
}
function closeTemplateRequestModal() {
isTemplateRequestModalOpen.value = false
+ resetTemplateRequestDrafts()
}
function openTemplateUpdateModal() {
+ resetTemplateRequestDrafts()
isTemplateUpdateModalOpen.value = true
}
function closeTemplateUpdateModal() {
isTemplateUpdateModalOpen.value = false
+ resetTemplateRequestDrafts()
}
function openDeleteModal() {
@@ -574,7 +586,11 @@ async function requestTemplate(type) {
try {
isRequestingTemplate.value = true
const persisted = await persistTierList({ showModal: false })
- await api.requestTierListTemplate(persisted.savedTierListId, { type })
+ await api.requestTierListTemplate(persisted.savedTierListId, {
+ type,
+ requestTitle: templateRequestDraftTitle.value.trim(),
+ requestDescription: templateRequestDraftDescription.value.trim(),
+ })
if (type === 'create') closeTemplateRequestModal()
if (type === 'update') closeTemplateUpdateModal()
toast.success(type === 'create' ? '템플릿 등록 요청을 보냈어요.' : '템플릿 업데이트 요청을 보냈어요.')
@@ -722,7 +738,18 @@ onUnmounted(() => {
- 제목만 명확하게 적어두면 관리자가 어떤 게임 템플릿 요청인지 빠르게 파악할 수 있어요. 여러 사용자가 비슷한 주제로 요청할 수 있으니 게임 이름을 구체적으로 적어주세요.
+ 제목과 설명을 함께 적어두면 관리자가 어떤 신규 템플릿인지 훨씬 빠르게 파악할 수 있어요.
+ 예시: 제목 `템플릿 등록 요청`, 설명 `여름 이벤트 한정 캐릭터 중심으로 새 게임 템플릿이 필요합니다.`
+
+
+
+ 요청 제목
+
+
+
+ 요청 설명
+
+
취소
@@ -741,10 +768,21 @@ onUnmounted(() => {
모두가 사용하는 기본 템플릿이니 개인적인 항목이 아닌 공통된 항목만 추가한 뒤 신청해주세요.
+ 예시: 제목 `템플릿 업데이트 요청`, 설명 `여름 이벤트 한정 캐릭터 추가`
+
+
+
+ 요청 제목
+
+
+
+ 요청 설명
+
+
요청 취소
-
+
{{ isRequestingTemplate ? '요청중...' : '예, 요청할게요' }}
@@ -1253,6 +1291,23 @@ onUnmounted(() => {
font-size: 13px;
line-height: 1.6;
opacity: 0.78;
+ white-space: pre-line;
+}
+.templateRequestDraft {
+ display: grid;
+ gap: 12px;
+}
+.templateRequestDraft__field {
+ display: grid;
+ gap: 6px;
+}
+.templateRequestDraft__label {
+ font-size: 12px;
+ color: rgba(255, 255, 255, 0.64);
+}
+.templateRequestDraft__textarea {
+ min-height: 92px;
+ resize: vertical;
}
.boardTools {
display: flex;