릴리스: v1.2.60 템플릿 요청 제목 설명 분리
This commit is contained in:
@@ -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,
|
||||
})
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# 업데이트 로그
|
||||
|
||||
## 2026-03-31 v1.2.60
|
||||
- 관리자 티어표 관리 카드에서 사용자가 입력한 설명을 제목 아래에 함께 노출해 요청 의도를 더 빨리 파악할 수 있게 함.
|
||||
- 템플릿 등록/업데이트 요청은 이제 에디터 모달에서 제목과 설명을 별도로 입력받고, 예시 문구와 함께 전송하도록 정리함.
|
||||
|
||||
## 2026-03-31 v1.2.59
|
||||
- 관리자 아이템 상세 모달의 게임 선택을 전용 상태로 분리해 기본 선택값이 비어 있도록 바꾸고, 썸네일 아래에 배치해 정보/액션과 시각적으로 분리함.
|
||||
- 커스텀 아이템이 실제로 사용 중인 게임 목록을 백엔드에서 함께 내려주고, 템플릿 요청 생성 폼에는 게임 ID와 게임 이름 라벨을 추가해 구분을 명확히 함.
|
||||
|
||||
@@ -1421,14 +1421,14 @@ async function saveFeaturedOrder() {
|
||||
</div>
|
||||
|
||||
<div v-if="request.type === 'create'" class="templateRequestCard__form">
|
||||
<label class="templateRequestField">
|
||||
<span class="templateRequestField__label">게임 ID</span>
|
||||
<input v-model="request.draftGameId" class="input" placeholder="새 게임 ID" />
|
||||
</label>
|
||||
<label class="templateRequestField">
|
||||
<span class="templateRequestField__label">게임 이름</span>
|
||||
<input v-model="request.draftGameName" class="input" placeholder="새 게임 이름" />
|
||||
</label>
|
||||
<label class="templateRequestField">
|
||||
<span class="templateRequestField__label">게임 ID</span>
|
||||
<input v-model="request.draftGameId" class="input" placeholder="새 게임 ID" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="templateRequestCard__actions">
|
||||
@@ -1461,6 +1461,7 @@ async function saveFeaturedOrder() {
|
||||
<div class="tierAdminCard__head">
|
||||
<div>
|
||||
<div class="tierAdminCard__title">{{ tierList.title }}</div>
|
||||
<div v-if="tierList.description" class="tierAdminCard__desc">{{ tierList.description }}</div>
|
||||
<div class="tierAdminCard__meta">
|
||||
{{ tierList.gameName || tierList.gameId }} · {{ tierListAuthorDisplayName(tierList) }} · {{ tierListVisibilityLabel(tierList) }}
|
||||
</div>
|
||||
@@ -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;
|
||||
|
||||
@@ -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(() => {
|
||||
</div>
|
||||
</div>
|
||||
<div class="requestChecklist__hint">
|
||||
제목만 명확하게 적어두면 관리자가 어떤 게임 템플릿 요청인지 빠르게 파악할 수 있어요. 여러 사용자가 비슷한 주제로 요청할 수 있으니 게임 이름을 구체적으로 적어주세요.
|
||||
제목과 설명을 함께 적어두면 관리자가 어떤 신규 템플릿인지 훨씬 빠르게 파악할 수 있어요.
|
||||
예시: 제목 `템플릿 등록 요청`, 설명 `여름 이벤트 한정 캐릭터 중심으로 새 게임 템플릿이 필요합니다.`
|
||||
</div>
|
||||
<div class="templateRequestDraft">
|
||||
<label class="templateRequestDraft__field">
|
||||
<span class="templateRequestDraft__label">요청 제목</span>
|
||||
<input v-model="templateRequestDraftTitle" class="input" maxlength="80" placeholder="예: 템플릿 등록 요청" />
|
||||
</label>
|
||||
<label class="templateRequestDraft__field">
|
||||
<span class="templateRequestDraft__label">요청 설명</span>
|
||||
<textarea v-model="templateRequestDraftDescription" class="textarea templateRequestDraft__textarea" maxlength="240" placeholder="예: 여름 이벤트 한정 캐릭터 추가용으로 신규 템플릿이 필요합니다." />
|
||||
</label>
|
||||
</div>
|
||||
<div class="modalCard__actions">
|
||||
<button class="btn btn--ghost" @click="closeTemplateRequestModal">취소</button>
|
||||
@@ -741,10 +768,21 @@ onUnmounted(() => {
|
||||
</div>
|
||||
<div class="modalCard__note">
|
||||
모두가 사용하는 기본 템플릿이니 개인적인 항목이 아닌 공통된 항목만 추가한 뒤 신청해주세요.
|
||||
예시: 제목 `템플릿 업데이트 요청`, 설명 `여름 이벤트 한정 캐릭터 추가`
|
||||
</div>
|
||||
<div class="templateRequestDraft">
|
||||
<label class="templateRequestDraft__field">
|
||||
<span class="templateRequestDraft__label">요청 제목</span>
|
||||
<input v-model="templateRequestDraftTitle" class="input" maxlength="80" placeholder="예: 템플릿 업데이트 요청" />
|
||||
</label>
|
||||
<label class="templateRequestDraft__field">
|
||||
<span class="templateRequestDraft__label">요청 설명</span>
|
||||
<textarea v-model="templateRequestDraftDescription" class="textarea templateRequestDraft__textarea" maxlength="240" placeholder="예: 여름 이벤트 한정 캐릭터 추가" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="modalCard__actions">
|
||||
<button class="btn btn--ghost" @click="closeTemplateUpdateModal">요청 취소</button>
|
||||
<button class="btn btn--save" :disabled="isRequestingTemplate" @click="requestTemplate('update')">
|
||||
<button class="btn btn--save" :disabled="!canSubmitTemplateUpdateRequest || isRequestingTemplate" @click="requestTemplate('update')">
|
||||
{{ isRequestingTemplate ? '요청중...' : '예, 요청할게요' }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user