diff --git a/frontend/src/components/Checkbox.jsx b/frontend/src/components/Checkbox.jsx index a2619f7..27deb45 100644 --- a/frontend/src/components/Checkbox.jsx +++ b/frontend/src/components/Checkbox.jsx @@ -14,11 +14,17 @@ export default function Checkbox({ checked, onChange, disabled, className = '', disabled={disabled} tabIndex={tabIndex} onClick={(e) => { e.stopPropagation(); !disabled && onChange?.(!checked) }} - className={`${sizeCls} shrink-0 rounded-md border-2 flex items-center justify-center transition ${ - checked - ? 'border-emerald-500 bg-emerald-500 text-white' - : 'border-white/20 bg-gray-950 hover:border-white/40' - } ${disabled ? 'opacity-40 cursor-not-allowed' : 'cursor-pointer'} ${className}`} + className={`${sizeCls} shrink-0 rounded-md border-2 flex items-center justify-center ${ + disabled ? 'opacity-40 cursor-not-allowed' : 'cursor-pointer' + } ${className}`} + style={checked ? { + borderColor: 'var(--accent)', + background: 'var(--accent)', + color: 'var(--btn-primary-text)', + } : { + borderColor: 'var(--input-border)', + background: 'var(--input-bg)', + }} > {checked && ( diff --git a/frontend/src/features/admin/AdminBoss.jsx b/frontend/src/features/admin/AdminBoss.jsx index 524a3e2..427abfa 100644 --- a/frontend/src/features/admin/AdminBoss.jsx +++ b/frontend/src/features/admin/AdminBoss.jsx @@ -1,8 +1,16 @@ export default function AdminBoss() { return (
-

보스 수익 계산기 관리

-
+

보스 수익 계산기 관리

+
준비 중
diff --git a/frontend/src/features/admin/AdminFeaturePage.jsx b/frontend/src/features/admin/AdminFeaturePage.jsx index 4581b01..1358fa7 100644 --- a/frontend/src/features/admin/AdminFeaturePage.jsx +++ b/frontend/src/features/admin/AdminFeaturePage.jsx @@ -17,22 +17,29 @@ export default function AdminFeaturePage() { if (!Component) { return ( -
+
{menu && (
-

{menu.title}

-

{menu.description}

+

{menu.title}

+

{menu.description}

)} -
+
🛠️
-

이 기능에는 관리 페이지가 없습니다

-

+

이 기능에는 관리 페이지가 없습니다

+

features/{slug}/{slug.split('-').map((s) => s[0].toUpperCase() + s.slice(1)).join('')}Admin.jsx

메뉴 정보 편집 → @@ -44,7 +51,10 @@ export default function AdminFeaturePage() { return ( -
+
}> diff --git a/frontend/src/features/admin/AdminHome.jsx b/frontend/src/features/admin/AdminHome.jsx index edf29ca..f10aa54 100644 --- a/frontend/src/features/admin/AdminHome.jsx +++ b/frontend/src/features/admin/AdminHome.jsx @@ -1,4 +1,4 @@ -import { Link, useNavigate, useOutletContext } from 'react-router-dom' +import { Link, useNavigate } from 'react-router-dom' import { useQuery } from '@tanstack/react-query' import { api } from '../../api/client' @@ -72,7 +72,6 @@ function AddCard({ to, icon, label }) { } export default function AdminHome() { - const { handleLogout } = useOutletContext() || {} const { data: menus = [], isLoading: loading } = useQuery({ queryKey: ['admin', 'menus'], queryFn: () => api('/api/admin/menus').catch(() => []), @@ -157,18 +156,6 @@ export default function AdminHome() {
- {/* 로그아웃 */} - {handleLogout && ( -
- -
- )}
) } diff --git a/frontend/src/features/admin/AdminImages.jsx b/frontend/src/features/admin/AdminImages.jsx index 6464a1d..5b35875 100644 --- a/frontend/src/features/admin/AdminImages.jsx +++ b/frontend/src/features/admin/AdminImages.jsx @@ -482,7 +482,7 @@ export default function AdminImages() { } return ( -
+

이미지 관리

diff --git a/frontend/src/features/admin/AdminMenuForm.jsx b/frontend/src/features/admin/AdminMenuForm.jsx index 4ba4e9f..1b355cc 100644 --- a/frontend/src/features/admin/AdminMenuForm.jsx +++ b/frontend/src/features/admin/AdminMenuForm.jsx @@ -9,18 +9,23 @@ function Field({ label, hint, error, required, children }) { return (
-
{children} - {error &&
{error}
} + {error &&
{error}
}
) } -const inputCls = 'w-full rounded-lg border border-white/10 bg-gray-950 px-3 py-2 text-sm outline-none focus:border-emerald-500/50 transition' +const inputCls = 'w-full rounded-lg border px-3 py-2 text-sm outline-none focus:border-[var(--input-border-focus)] hover:border-[var(--input-border-hover)]' +const inputStyle = { + background: 'var(--input-bg)', + borderColor: 'var(--input-border)', + color: 'var(--text-strong)', +} export default function AdminMenuForm() { const navigate = useNavigate() @@ -33,20 +38,18 @@ export default function AdminMenuForm() { const [form, setForm] = useState({ title: '', description: '', - slug: '', // 사용자 입력 (앞 / 제외) + slug: '', image_id: null, - image: null, // 미리보기용 캐시 + image: null, }) const [errors, setErrors] = useState({}) - // 편집 모드일 때 기존 데이터 로드 const { data: menuData } = useQuery({ queryKey: ['admin', 'menus', id], queryFn: () => api(`/api/admin/menus/${id}`), enabled: isEdit, }) - // id 변경 또는 데이터 로드 시 폼 동기화 useEffect(() => { if (!isEdit) { setForm({ title: '', description: '', slug: '', image_id: null, image: null }) @@ -65,7 +68,6 @@ export default function AdminMenuForm() { const update = (patch) => setForm((prev) => ({ ...prev, ...patch })) - // slug에서 / 자동 제거 (붙여넣기 등 대비) const handleSlugChange = (value) => { update({ slug: value.replace(/^\/+/, '') }) } @@ -119,24 +121,55 @@ export default function AdminMenuForm() { }) return ( -
+
-

{isEdit ? '메뉴 항목 편집' : '메뉴 항목 추가'}

-

홈 화면에 표시되는 카드의 정보를 설정합니다

+

{isEdit ? '메뉴 항목 편집' : '메뉴 항목 추가'}

+

+ 홈 화면에 표시되는 카드의 정보를 설정합니다 +

-
+ {/* 미리보기 */} -
-
미리보기
-
+
+
미리보기
+
-
+
-

{form.title || '제목 없음'}

-

{form.description || '설명 없음'}

+

{form.title || '제목 없음'}

+

+ {form.description || '설명 없음'} +

@@ -149,6 +182,7 @@ export default function AdminMenuForm() { onChange={(e) => update({ title: e.target.value })} placeholder="예: 주간 보스 수익 계산기" className={inputCls} + style={inputStyle} /> @@ -159,26 +193,45 @@ export default function AdminMenuForm() { onChange={(e) => update({ description: e.target.value })} placeholder="예: 캐릭터별 보스 결정석 수익을 계산합니다" className={inputCls} + style={inputStyle} /> -
- / +
+ + / + handleSlugChange(e.target.value)} placeholder="boss-crystal" className="flex-1 min-w-0 bg-transparent px-3 py-2 text-sm outline-none" + style={{ color: 'var(--text-strong)' }} />
{form.slug.trim() && !errors.slug && ( -
+
전체 URL: - https://maple.caadiq.co.kr{fullUrl} + + https://maple.caadiq.co.kr{fullUrl} +
)} @@ -188,12 +241,16 @@ export default function AdminMenuForm() {
@@ -203,13 +260,14 @@ export default function AdminMenuForm() { ) : ( -
이미지 선택
+
이미지 선택
)}
@@ -220,7 +278,11 @@ export default function AdminMenuForm() { @@ -229,14 +291,24 @@ export default function AdminMenuForm() { diff --git a/frontend/src/features/admin/components/ImagePicker.jsx b/frontend/src/features/admin/components/ImagePicker.jsx index e0c5429..5fc7e1a 100644 --- a/frontend/src/features/admin/components/ImagePicker.jsx +++ b/frontend/src/features/admin/components/ImagePicker.jsx @@ -4,9 +4,6 @@ import { api } from '../../../api/client' const PAGE_SIZE = 24 -/** - * 업로드된 이미지 중 하나를 선택하는 모달 피커 - */ export default function ImagePicker({ open, onClose, onSelect, currentImageId }) { const [page, setPage] = useState(1) const [search, setSearch] = useState('') @@ -48,11 +45,31 @@ export default function ImagePicker({ open, onClose, onSelect, currentImageId }) if (!open) return null return ( -
-
e.stopPropagation()}> -
-

이미지 선택

- +
+
e.stopPropagation()} + > +
+

이미지 선택

+
{/* 검색 */} @@ -63,9 +80,14 @@ export default function ImagePicker({ open, onClose, onSelect, currentImageId }) value={search} onChange={(e) => setSearch(e.target.value)} placeholder="이미지 이름으로 검색..." - className="w-full rounded-lg border border-white/10 bg-gray-950 pl-10 pr-4 py-2.5 text-sm outline-none focus:border-emerald-500/50 transition" + className="w-full rounded-lg border pl-10 pr-4 py-2.5 text-sm outline-none focus:border-[var(--input-border-focus)] hover:border-[var(--input-border-hover)]" + style={{ + background: 'var(--input-bg)', + borderColor: 'var(--input-border)', + color: 'var(--text-strong)', + }} /> - 🔍 + 🔍
@@ -74,55 +96,84 @@ export default function ImagePicker({ open, onClose, onSelect, currentImageId }) {isLoading ? (
{Array.from({ length: 12 }).map((_, i) => ( -
+
))}
) : images.length === 0 ? ( -
+
{debouncedSearch ? '검색 결과가 없습니다' : '업로드된 이미지가 없습니다'}
) : (
- {images.map((image) => ( - - ))} + {images.map((image) => { + const isSelected = currentImageId === image.id + return ( + + ) + })}
)}
{/* 페이지네이션 + 액션 */} -
+
{totalPages > 1 ? (
- {page} / {totalPages} + {page} / {totalPages} @@ -133,7 +184,8 @@ export default function ImagePicker({ open, onClose, onSelect, currentImageId }) diff --git a/frontend/src/features/boss-crystal/admin/BossForm.jsx b/frontend/src/features/boss-crystal/admin/BossForm.jsx index e9a2e4f..c3a670c 100644 --- a/frontend/src/features/boss-crystal/admin/BossForm.jsx +++ b/frontend/src/features/boss-crystal/admin/BossForm.jsx @@ -5,6 +5,7 @@ import { api } from '../../../api/client' import ConfirmDialog from '../../../components/ConfirmDialog' import Checkbox from '../../../components/Checkbox' import Select from '../../../components/Select' +import { useAuthStore } from '../../../stores/auth' import { DIFFICULTIES, formatMeso, getDifficultyImageUrl } from './constants' const PARTY_OPTIONS = [1, 2, 3, 4, 5, 6].map((n) => ({ value: n, label: `${n}인` })) @@ -13,18 +14,23 @@ function Field({ label, hint, error, required, children }) { return (
-
{children} - {error &&
{error}
} + {error &&
{error}
}
) } -const inputCls = 'w-full rounded-lg border border-white/10 bg-gray-950 px-3 py-2 text-sm outline-none focus:border-emerald-500/50 transition' +const inputCls = 'w-full rounded-lg border px-3 py-2 text-sm outline-none focus:border-[var(--input-border-focus)] hover:border-[var(--input-border-hover)]' +const inputStyle = { + background: 'var(--input-bg)', + borderColor: 'var(--input-border)', + color: 'var(--text-strong)', +} function emptyDifficultyState() { const obj = {} @@ -134,7 +140,7 @@ export default function BossForm() { })) formData.append('difficulties', JSON.stringify(diffsPayload)) - const adminKey = localStorage.getItem('maple-admin-key') + const adminKey = useAuthStore.getState().apiKey const url = isEdit ? `/api/admin/boss-crystal/bosses/${id}` : '/api/admin/boss-crystal/bosses' @@ -174,13 +180,21 @@ export default function BossForm() { const displayImage = imagePreview || existingImageUrl return ( -
+
-

{isEdit ? '보스 편집' : '보스 추가'}

-

보스 이름과 난이도별 결정 정보를 입력합니다

+

{isEdit ? '보스 편집' : '보스 추가'}

+

보스 이름과 난이도별 결정 정보를 입력합니다

- + {/* 이름 + 최대 인원 */}
@@ -190,6 +204,7 @@ export default function BossForm() { onChange={(e) => setName(e.target.value)} placeholder="예: 검은 마법사" className={inputCls} + style={inputStyle} /> @@ -205,26 +220,32 @@ export default function BossForm() { {/* 이미지 */}