diff --git a/src/main/resources/templates/admin/new-product.html b/src/main/resources/templates/admin/new-product.html
index 6148dd5..0f3d744 100644
--- a/src/main/resources/templates/admin/new-product.html
+++ b/src/main/resources/templates/admin/new-product.html
@@ -280,19 +280,20 @@
상세 이미지
return headers;
};
- // 상태(분리)
+ // 상태(키만 저장)
const state = {thumbs: [], details: []};
- // 절대 URL 보정
+ // 절대 URL 보정: 렌더링 전용
function toAbsoluteUrl(s) {
if (!s) return s;
if (/^https?:\/\//i.test(s)) return s;
- if (s.startsWith('/')) return s;
+ // 키에 슬래시가 있어도 그대로 붙여서 사용
const PUBLIC_BASE = 'https://cherry-dev-assets.s3.ap-northeast-2.amazonaws.com/';
+ // 키를 안전하게 인코딩(경로 단위)
return PUBLIC_BASE + s.split('/').map(encodeURIComponent).join('/');
}
- // 업로드
+ // 업로드: 서버가 주는 fileName(키)을 그대로 반환
async function uploadImage(file) {
const fd = new FormData();
fd.append('image', file);
@@ -300,7 +301,7 @@ 상세 이미지
if (!res.ok) throw new Error('업로드 실패');
const j = await res.json();
if (!j.fileName) throw new Error('응답에 fileName 없음');
- return toAbsoluteUrl(j.fileName);
+ return j.fileName;
}
// 공용: 개수/버튼 상태
@@ -315,19 +316,20 @@ 상세 이미지
}
}
- // 렌더(리스트 + hidden + DnD + 삭제)
+ // 렌더(리스트 + hidden + DnD + 삭제) — items에는 "키"가 들어있음
function renderGrid({items, gridEl, hiddenEl, countEl, uploadBtn, showFirstBadge}) {
gridEl.innerHTML = '';
- items.forEach((url, idx) => {
+ items.forEach((key, idx) => {
+ const absUrl = toAbsoluteUrl(key);
const card = document.createElement('div');
card.className = 'thumb';
card.draggable = true;
card.dataset.index = String(idx);
card.innerHTML = `
- ${showFirstBadge && idx === 0 ? '썸네일' : ''}
-
-
- `;
+ ${showFirstBadge && idx === 0 ? '썸네일' : ''}
+
+
+ `;
// 삭제
card.querySelector('.btn-del').addEventListener('click', (e) => {
@@ -363,11 +365,11 @@ 상세 이미지
// hidden inputs (서버 폴백)
hiddenEl.innerHTML = '';
- items.forEach(url => {
+ items.forEach(key => {
const i = document.createElement('input');
i.type = 'hidden';
i.name = showFirstBadge ? 'thumbnailImages' : 'detailImages';
- i.value = url;
+ i.value = key;
hiddenEl.appendChild(i);
});
@@ -384,8 +386,8 @@ 상세 이미지
}
try {
for (const f of list) {
- const url = await uploadImage(f);
- items.push(url);
+ const key = await uploadImage(f);
+ items.push(key);
}
render();
} catch (e) {
@@ -461,7 +463,7 @@ 상세 이미지
rerenderDetail();
});
- // ====== 색상 칩 (이벤트 위임) ======
+ // ====== 색상 칩 ======
const colorPills = document.getElementById('colorPills');
colorPills.addEventListener('click', (e) => {
const pill = e.target.closest('.pill');
@@ -470,13 +472,12 @@ 상세 이미지
input.checked = !input.checked;
pill.classList.toggle('active', input.checked);
});
- // 초기 동기화
colorPills.querySelectorAll('.pill').forEach(p => {
const input = p.querySelector('input[type="checkbox"]');
p.classList.toggle('active', input.checked);
});
- // ====== 저장(JSON) ======
+ // ====== 저장(JSON) — payload에는 키만 담김 ======
const form = document.getElementById('productForm');
form.addEventListener('submit', async (e) => {
e.preventDefault();
@@ -484,7 +485,7 @@ 상세 이미지
const name = form.querySelector('#name')?.value?.trim();
const brand = form.querySelector('#brand')?.value;
const price = Number(form.querySelector('#dailyRentalPrice')?.value);
- const launchedAt = form.querySelector('#launchedAt')?.value?.trim(); // type=date → YYYY-MM-DD
+ const launchedAt = form.querySelector('#launchedAt')?.value?.trim(); // YYYY-MM-DD
const colors = Array.from(form.querySelectorAll('input[name="colors"]:checked')).map(el => el.value);
if (!name) return alert('상품명은 필수입니다.');
diff --git a/src/main/resources/templates/admin/product-detail.html b/src/main/resources/templates/admin/product-detail.html
index df6c2c8..896439d 100644
--- a/src/main/resources/templates/admin/product-detail.html
+++ b/src/main/resources/templates/admin/product-detail.html
@@ -4,6 +4,11 @@
Cherry 관리자페이지 - 제품 상세
+
+
+
+
+