refactor: AdminHeader 공통 컴포넌트를 모든 Admin 페이지에 적용

새로 생성된 파일:
- components/admin/AdminHeader.jsx (47줄)

수정된 파일 (10개):
- pages/pc/admin/AdminMembers.jsx
- pages/pc/admin/AdminMemberEdit.jsx
- pages/pc/admin/AdminAlbums.jsx
- pages/pc/admin/AdminAlbumForm.jsx
- pages/pc/admin/AdminAlbumPhotos.jsx
- pages/pc/admin/AdminSchedule.jsx
- pages/pc/admin/AdminScheduleForm.jsx
- pages/pc/admin/AdminScheduleBots.jsx
- pages/pc/admin/AdminScheduleCategory.jsx
- pages/pc/admin/AdminDashboard.jsx

각 파일에서:
- 중복 헤더 JSX 제거 (24줄 → 1줄)
- handleLogout 함수 제거 (4~6줄)
- LogOut import 제거

총 약 300줄의 중복 코드 제거
This commit is contained in:
caadiq 2026-01-09 23:18:48 +09:00
parent 7f9c53b53a
commit 97fb4b7964
9 changed files with 26 additions and 280 deletions

View file

@ -2,11 +2,12 @@ import { useState, useEffect, useRef } from 'react';
import { useNavigate, useParams, Link } from 'react-router-dom';
import { motion, AnimatePresence } from 'framer-motion';
import {
Save, Home, ChevronRight, LogOut, Music, Trash2, Plus, Image, Star,
Save, Home, ChevronRight, Music, Trash2, Plus, Image, Star,
ChevronDown
} from 'lucide-react';
import Toast from '../../../components/Toast';
import CustomDatePicker from '../../../components/admin/CustomDatePicker';
import AdminHeader from '../../../components/admin/AdminHeader';
import useToast from '../../../hooks/useToast';
//
@ -140,12 +141,6 @@ function AdminAlbumForm() {
}
}, [id, isEditMode, navigate]);
const handleLogout = () => {
localStorage.removeItem('adminToken');
localStorage.removeItem('adminUser');
navigate('/admin');
};
const handleInputChange = (e) => {
const { name, value } = e.target;
@ -283,30 +278,7 @@ function AdminAlbumForm() {
<Toast toast={toast} onClose={() => setToast(null)} />
{/* 헤더 */}
<header className="bg-white shadow-sm border-b border-gray-100">
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
<div className="flex items-center gap-4">
<Link to="/admin/dashboard" className="text-2xl font-bold text-primary hover:opacity-80 transition-opacity">
fromis_9
</Link>
<span className="px-3 py-1 bg-primary/10 text-primary text-sm font-medium rounded-full">
Admin
</span>
</div>
<div className="flex items-center gap-4">
<span className="text-gray-500 text-sm">
안녕하세요, <span className="text-gray-900 font-medium">{user?.username}</span>
</span>
<button
onClick={handleLogout}
className="flex items-center gap-2 px-4 py-2 text-gray-500 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
>
<LogOut size={18} />
<span>로그아웃</span>
</button>
</div>
</div>
</header>
<AdminHeader user={user} />
{/* 메인 콘텐츠 */}
<main className="max-w-4xl mx-auto px-6 py-8">

View file

@ -3,11 +3,12 @@ import { useNavigate, Link, useParams } from 'react-router-dom';
import { motion, AnimatePresence, Reorder } from 'framer-motion';
import {
Upload, Trash2, Image, X, Check, Plus,
Home, ChevronRight, LogOut, ArrowLeft, Grid, List,
Home, ChevronRight, ArrowLeft, Grid, List,
ZoomIn, AlertTriangle, GripVertical, Users, User, Users2,
Tag, FolderOpen, Save
} from 'lucide-react';
import Toast from '../../../components/Toast';
import AdminHeader from '../../../components/admin/AdminHeader';
import useToast from '../../../hooks/useToast';
import * as authApi from '../../../api/admin/auth';
import { getAlbum } from '../../../api/public/albums';
@ -206,11 +207,6 @@ function AdminAlbumPhotos() {
}
}, [photoType, photos, teasers]);
const handleLogout = () => {
authApi.logout();
navigate('/admin');
};
//
const handleFileSelect = (e) => {
const files = Array.from(e.target.files);
@ -631,30 +627,7 @@ function AdminAlbumPhotos() {
</AnimatePresence>
{/* 헤더 */}
<header className="bg-white shadow-sm border-b border-gray-100">
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
<div className="flex items-center gap-4">
<Link to="/admin/dashboard" className="text-2xl font-bold text-primary hover:opacity-80 transition-opacity">
fromis_9
</Link>
<span className="px-3 py-1 bg-primary/10 text-primary text-sm font-medium rounded-full">
Admin
</span>
</div>
<div className="flex items-center gap-4">
<span className="text-gray-500 text-sm">
안녕하세요, <span className="text-gray-900 font-medium">{user?.username}</span>
</span>
<button
onClick={handleLogout}
className="flex items-center gap-2 px-4 py-2 text-gray-500 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
>
<LogOut size={18} />
<span>로그아웃</span>
</button>
</div>
</div>
</header>
<AdminHeader user={user} />
{/* 메인 콘텐츠 */}
<main className="max-w-7xl mx-auto px-6 py-8">

View file

@ -3,10 +3,11 @@ import { useNavigate, Link } from 'react-router-dom';
import { motion, AnimatePresence } from 'framer-motion';
import {
Plus, Search, Edit2, Trash2, Image, Music,
Home, ChevronRight, LogOut, Calendar, AlertTriangle, X
Home, ChevronRight, Calendar, AlertTriangle, X
} from 'lucide-react';
import Toast from '../../../components/Toast';
import Tooltip from '../../../components/Tooltip';
import AdminHeader from '../../../components/admin/AdminHeader';
import useToast from '../../../hooks/useToast';
import * as authApi from '../../../api/admin/auth';
import { getAlbums } from '../../../api/public/albums';
@ -44,11 +45,6 @@ function AdminAlbums() {
}
};
const handleLogout = () => {
authApi.logout();
navigate('/admin');
};
const handleDelete = async () => {
if (!deleteDialog.album) return;
@ -145,30 +141,7 @@ function AdminAlbums() {
</AnimatePresence>
{/* 헤더 */}
<header className="bg-white shadow-sm border-b border-gray-100">
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
<div className="flex items-center gap-4">
<Link to="/admin/dashboard" className="text-2xl font-bold text-primary hover:opacity-80 transition-opacity">
fromis_9
</Link>
<span className="px-3 py-1 bg-primary/10 text-primary text-sm font-medium rounded-full">
Admin
</span>
</div>
<div className="flex items-center gap-4">
<span className="text-gray-500 text-sm">
안녕하세요, <span className="text-gray-900 font-medium">{user?.username}</span>
</span>
<button
onClick={handleLogout}
className="flex items-center gap-2 px-4 py-2 text-gray-500 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
>
<LogOut size={18} />
<span>로그아웃</span>
</button>
</div>
</div>
</header>
<AdminHeader user={user} />
{/* 메인 콘텐츠 */}
<main className="max-w-7xl mx-auto px-6 py-8">

View file

@ -2,9 +2,10 @@ import { useState, useEffect } from 'react';
import { useNavigate, Link } from 'react-router-dom';
import { motion } from 'framer-motion';
import {
Disc3, Calendar, Users, LogOut,
Disc3, Calendar, Users,
Home, ChevronRight
} from 'lucide-react';
import AdminHeader from '../../../components/admin/AdminHeader';
import * as authApi from '../../../api/admin/auth';
import { getMembers } from '../../../api/public/members';
import { getAlbums, getAlbum } from '../../../api/public/albums';
@ -107,11 +108,6 @@ function AdminDashboard() {
} catch (e) { console.error('일정 통계 오류:', e); }
};
const handleLogout = () => {
authApi.logout();
navigate('/admin');
};
//
const menuItems = [
{
@ -140,30 +136,7 @@ function AdminDashboard() {
return (
<div className="min-h-screen bg-gray-50">
{/* 헤더 */}
<header className="bg-white shadow-sm border-b border-gray-100">
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
<div className="flex items-center gap-4">
<Link to="/admin/dashboard" className="text-2xl font-bold text-primary hover:opacity-80 transition-opacity">
fromis_9
</Link>
<span className="px-3 py-1 bg-primary/10 text-primary text-sm font-medium rounded-full">
Admin
</span>
</div>
<div className="flex items-center gap-4">
<span className="text-gray-500 text-sm">
안녕하세요, <span className="text-gray-900 font-medium">{user?.username}</span>
</span>
<button
onClick={handleLogout}
className="flex items-center gap-2 px-4 py-2 text-gray-500 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
>
<LogOut size={18} />
<span>로그아웃</span>
</button>
</div>
</div>
</header>
<AdminHeader user={user} />
{/* 메인 콘텐츠 */}
<main className="max-w-7xl mx-auto px-6 py-8">

View file

@ -2,11 +2,12 @@ import { useState, useEffect, useRef } from 'react';
import { useNavigate, useParams, Link } from 'react-router-dom';
import { motion, AnimatePresence } from 'framer-motion';
import {
Save, Upload, LogOut,
Save, Upload,
Home, ChevronRight, User, Instagram, Calendar, Briefcase
} from 'lucide-react';
import Toast from '../../../components/Toast';
import CustomDatePicker from '../../../components/admin/CustomDatePicker';
import AdminHeader from '../../../components/admin/AdminHeader';
import useToast from '../../../hooks/useToast';
import * as authApi from '../../../api/admin/auth';
import * as membersApi from '../../../api/admin/members';
@ -59,11 +60,6 @@ function AdminMemberEdit() {
}
};
const handleLogout = () => {
authApi.logout();
navigate('/admin');
};
const handleImageChange = (e) => {
const file = e.target.files[0];
if (file) {
@ -108,30 +104,7 @@ function AdminMemberEdit() {
<Toast toast={toast} onClose={() => setToast(null)} />
{/* 헤더 */}
<header className="bg-white shadow-sm border-b border-gray-100">
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
<div className="flex items-center gap-4">
<Link to="/admin/dashboard" className="text-2xl font-bold text-primary hover:opacity-80 transition-opacity">
fromis_9
</Link>
<span className="px-3 py-1 bg-primary/10 text-primary text-sm font-medium rounded-full">
Admin
</span>
</div>
<div className="flex items-center gap-4">
<span className="text-gray-500 text-sm">
안녕하세요, <span className="text-gray-900 font-medium">{user?.username}</span>
</span>
<button
onClick={handleLogout}
className="flex items-center gap-2 px-4 py-2 text-gray-500 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
>
<LogOut size={18} />
<span>로그아웃</span>
</button>
</div>
</div>
</header>
<AdminHeader user={user} />
{/* 메인 콘텐츠 */}
<main className="max-w-4xl mx-auto px-6 py-8">

View file

@ -2,7 +2,7 @@ import { useState, useEffect, useRef, useMemo, memo, useDeferredValue } from 're
import { useNavigate, Link } from 'react-router-dom';
import { motion, AnimatePresence } from 'framer-motion';
import {
LogOut, Home, ChevronRight, Calendar, Plus, Edit2, Trash2,
Home, ChevronRight, Calendar, Plus, Edit2, Trash2,
ChevronLeft, Search, ChevronDown, AlertTriangle, Bot, Tag, ArrowLeft, ExternalLink, Clock, Link2
} from 'lucide-react';
import { useInfiniteQuery } from '@tanstack/react-query';
@ -10,6 +10,7 @@ import { useInView } from 'react-intersection-observer';
import Toast from '../../../components/Toast';
import Tooltip from '../../../components/Tooltip';
import AdminHeader from '../../../components/admin/AdminHeader';
import useScheduleStore from '../../../stores/useScheduleStore';
import useToast from '../../../hooks/useToast';
import { getTodayKST, formatDate } from '../../../utils/date';
@ -408,12 +409,6 @@ function AdminSchedule() {
return () => document.removeEventListener('mousedown', handleClickOutside);
}, [showYearMonthPicker, showCategoryTooltip]);
const handleLogout = () => {
localStorage.removeItem('adminToken');
localStorage.removeItem('adminUser');
navigate('/admin');
};
//
const prevMonth = () => {
setSlideDirection(-1);
@ -643,30 +638,7 @@ function AdminSchedule() {
</AnimatePresence>
{/* 헤더 */}
<header className="bg-white shadow-sm border-b border-gray-100">
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
<div className="flex items-center gap-4">
<Link to="/admin/dashboard" className="text-2xl font-bold text-primary hover:opacity-80 transition-opacity">
fromis_9
</Link>
<span className="px-3 py-1 bg-primary/10 text-primary text-sm font-medium rounded-full">
Admin
</span>
</div>
<div className="flex items-center gap-4">
<span className="text-gray-500 text-sm">
안녕하세요, <span className="text-gray-900 font-medium">{user?.username}</span>
</span>
<button
onClick={handleLogout}
className="flex items-center gap-2 px-4 py-2 text-gray-500 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
>
<LogOut size={18} />
<span>로그아웃</span>
</button>
</div>
</div>
</header>
<AdminHeader user={user} />
{/* 메인 콘텐츠 */}
<main className="max-w-7xl mx-auto px-6 py-8">

View file

@ -2,11 +2,12 @@ import { useState, useEffect } from 'react';
import { useNavigate, Link } from 'react-router-dom';
import { motion, AnimatePresence } from 'framer-motion';
import {
LogOut, Home, ChevronRight, Bot, Play, Square,
Home, ChevronRight, Bot, Play, Square,
Youtube, Calendar, Clock, CheckCircle, XCircle, RefreshCw, Download
} from 'lucide-react';
import Toast from '../../../components/Toast';
import Tooltip from '../../../components/Tooltip';
import AdminHeader from '../../../components/admin/AdminHeader';
import useToast from '../../../hooks/useToast';
import * as botsApi from '../../../api/admin/bots';
@ -70,12 +71,6 @@ function AdminScheduleBots() {
}
};
const handleLogout = () => {
localStorage.removeItem('adminToken');
localStorage.removeItem('adminUser');
navigate('/admin');
};
// /
const toggleBot = async (botId, currentStatus, botName) => {
try {
@ -193,30 +188,7 @@ function AdminScheduleBots() {
<Toast toast={toast} onClose={() => setToast(null)} />
{/* 헤더 */}
<header className="bg-white shadow-sm border-b border-gray-100">
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
<div className="flex items-center gap-4">
<Link to="/admin/dashboard" className="text-2xl font-bold text-primary hover:opacity-80 transition-opacity">
fromis_9
</Link>
<span className="px-3 py-1 bg-primary/10 text-primary text-sm font-medium rounded-full">
Admin
</span>
</div>
<div className="flex items-center gap-4">
<span className="text-gray-500 text-sm">
안녕하세요, <span className="text-gray-900 font-medium">{user?.username}</span>
</span>
<button
onClick={handleLogout}
className="flex items-center gap-2 px-4 py-2 text-gray-500 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
>
<LogOut size={18} />
<span>로그아웃</span>
</button>
</div>
</div>
</header>
<AdminHeader user={user} />
{/* 메인 콘텐츠 */}
<main className="max-w-7xl mx-auto px-6 py-8">

View file

@ -1,9 +1,10 @@
import { useState, useEffect } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { motion, AnimatePresence, Reorder } from 'framer-motion';
import { LogOut, Home, ChevronRight, Plus, Edit3, Trash2, GripVertical, X, AlertTriangle } from 'lucide-react';
import { Home, ChevronRight, Plus, Edit3, Trash2, GripVertical, X, AlertTriangle } from 'lucide-react';
import { HexColorPicker } from 'react-colorful';
import Toast from '../../../components/Toast';
import AdminHeader from '../../../components/admin/AdminHeader';
import useToast from '../../../hooks/useToast';
import * as authApi from '../../../api/admin/auth';
import * as categoriesApi from '../../../api/admin/categories';
@ -85,12 +86,6 @@ function AdminScheduleCategory() {
}
};
//
const handleLogout = () => {
authApi.logout();
navigate('/admin');
};
// (/)
const openModal = (category = null) => {
if (category) {
@ -189,30 +184,7 @@ function AdminScheduleCategory() {
<Toast toast={toast} onClose={() => setToast(null)} />
{/* 헤더 */}
<header className="bg-white shadow-sm border-b border-gray-100">
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
<div className="flex items-center gap-4">
<Link to="/admin/dashboard" className="text-2xl font-bold text-primary hover:opacity-80 transition-opacity">
fromis_9
</Link>
<span className="px-3 py-1 bg-primary/10 text-primary text-sm font-medium rounded-full">
Admin
</span>
</div>
<div className="flex items-center gap-4">
<span className="text-gray-500 text-sm">
안녕하세요, <span className="text-gray-900 font-medium">{user?.username}</span>
</span>
<button
onClick={handleLogout}
className="flex items-center gap-2 px-4 py-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
>
<LogOut size={18} />
로그아웃
</button>
</div>
</div>
</header>
<AdminHeader user={user} />
{/* 메인 콘텐츠 */}
<main className="max-w-4xl mx-auto px-6 py-8">

View file

@ -28,6 +28,7 @@ import Toast from "../../../components/Toast";
import Lightbox from "../../../components/common/Lightbox";
import CustomDatePicker from "../../../components/admin/CustomDatePicker";
import CustomTimePicker from "../../../components/admin/CustomTimePicker";
import AdminHeader from "../../../components/admin/AdminHeader";
import useToast from "../../../hooks/useToast";
import * as authApi from "../../../api/admin/auth";
import * as categoriesApi from "../../../api/admin/categories";
@ -236,11 +237,6 @@ function AdminScheduleForm() {
}
};
const handleLogout = () => {
authApi.logout();
navigate("/admin");
};
//
const toggleMember = (memberId) => {
const newMembers = formData.members.includes(memberId)
@ -709,37 +705,7 @@ function AdminScheduleForm() {
/>
{/* 헤더 */}
<header className="bg-white shadow-sm border-b border-gray-100">
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
<div className="flex items-center gap-4">
<Link
to="/admin/dashboard"
className="text-2xl font-bold text-primary hover:opacity-80 transition-opacity"
>
fromis_9
</Link>
<span className="px-3 py-1 bg-primary/10 text-primary text-sm font-medium rounded-full">
Admin
</span>
</div>
<div className="flex items-center gap-4">
<span className="text-gray-500 text-sm">
안녕하세요,{" "}
<span className="text-gray-900 font-medium">
{user?.username}
</span>
</span>
<button
onClick={handleLogout}
className="flex items-center gap-2 px-4 py-2 text-gray-500 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
>
<LogOut size={18} />
<span>로그아웃</span>
</button>
</div>
</div>
</header>
<AdminHeader user={user} />
{/* 메인 콘텐츠 */}
<main className="max-w-4xl mx-auto px-6 py-8">