refactor: 어드민 페이지 인증 로직을 useAdminAuth 훅으로 통합

- 8개 어드민 페이지에서 중복된 인증 코드 제거
- localStorage 직접 접근 → useAdminAuth 훅 사용
- authApi.hasToken()/verifyToken() 호출 제거
- 일관된 인증 상태 관리 (5분 캐시, 자동 리다이렉트)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
caadiq 2026-01-16 23:10:30 +09:00
parent d44537d870
commit 6462949bc7
8 changed files with 78 additions and 145 deletions

View file

@ -8,6 +8,7 @@ import {
import Toast from '../../../components/Toast';
import CustomDatePicker from '../../../components/admin/CustomDatePicker';
import AdminLayout from '../../../components/admin/AdminLayout';
import useAdminAuth from '../../../hooks/useAdminAuth';
import useToast from '../../../hooks/useToast';
//
@ -79,8 +80,8 @@ function AdminAlbumForm() {
const { id } = useParams();
const isEditMode = !!id;
const coverInputRef = useRef(null);
const { user, isAuthenticated } = useAdminAuth();
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false);
const [coverPreview, setCoverPreview] = useState(null);
@ -101,18 +102,10 @@ function AdminAlbumForm() {
const [tracks, setTracks] = useState([]);
//
useEffect(() => {
const token = localStorage.getItem('adminToken');
const userData = localStorage.getItem('adminUser');
if (!isAuthenticated || !isEditMode) return;
if (!token || !userData) {
navigate('/admin');
return;
}
setUser(JSON.parse(userData));
if (isEditMode) {
setLoading(true);
fetch(`/api/albums/${id}`)
.then(res => res.json())
@ -138,8 +131,7 @@ function AdminAlbumForm() {
console.error('앨범 로드 오류:', error);
setLoading(false);
});
}
}, [id, isEditMode, navigate]);
}, [id, isEditMode, isAuthenticated]);
const handleInputChange = (e) => {
const { name, value } = e.target;

View file

@ -10,8 +10,8 @@ import {
import Toast from '../../../components/Toast';
import AdminLayout from '../../../components/admin/AdminLayout';
import ConfirmDialog from '../../../components/admin/ConfirmDialog';
import useAdminAuth from '../../../hooks/useAdminAuth';
import useToast from '../../../hooks/useToast';
import * as authApi from '../../../api/admin/auth';
import { getAlbum } from '../../../api/public/albums';
import { getMembers } from '../../../api/public/members';
import * as albumsApi from '../../../api/admin/albums';
@ -22,11 +22,11 @@ function AdminAlbumPhotos() {
const fileInputRef = useRef(null);
const photoListRef = useRef(null); // ref
const { user, isAuthenticated } = useAdminAuth();
const [album, setAlbum] = useState(null);
const [photos, setPhotos] = useState([]);
const [teasers, setTeasers] = useState([]); //
const [loading, setLoading] = useState(true);
const [user, setUser] = useState(null);
const { toast, setToast } = useToast();
const [selectedPhotos, setSelectedPhotos] = useState([]);
const [uploading, setUploading] = useState(false);
@ -143,15 +143,10 @@ function AdminAlbumPhotos() {
};
useEffect(() => {
//
if (!authApi.hasToken()) {
navigate('/admin');
return;
}
setUser(authApi.getCurrentUser());
if (isAuthenticated) {
fetchAlbumData();
}, [navigate, albumId]);
}
}, [isAuthenticated, albumId]);
const fetchAlbumData = async () => {
try {

View file

@ -1,40 +1,32 @@
import { useState, useEffect } from 'react';
import { useNavigate, Link } from 'react-router-dom';
import { motion, AnimatePresence } from 'framer-motion';
import {
Plus, Search, Edit2, Trash2, Image, Music,
Home, ChevronRight, Calendar, X
} from 'lucide-react';
import { motion } from 'framer-motion';
import { Plus, Search, Edit2, Trash2, Image, Music, Home, ChevronRight, Calendar } from 'lucide-react';
import Toast from '../../../components/Toast';
import Tooltip from '../../../components/Tooltip';
import AdminLayout from '../../../components/admin/AdminLayout';
import ConfirmDialog from '../../../components/admin/ConfirmDialog';
import useAdminAuth from '../../../hooks/useAdminAuth';
import useToast from '../../../hooks/useToast';
import * as authApi from '../../../api/admin/auth';
import { getAlbums } from '../../../api/public/albums';
import * as albumsApi from '../../../api/admin/albums';
function AdminAlbums() {
const navigate = useNavigate();
const { user, isAuthenticated } = useAdminAuth();
const { toast, setToast } = useToast();
const [albums, setAlbums] = useState([]);
const [loading, setLoading] = useState(true);
const [searchQuery, setSearchQuery] = useState('');
const [user, setUser] = useState(null);
const { toast, setToast } = useToast();
const [deleteDialog, setDeleteDialog] = useState({ show: false, album: null });
const [deleting, setDeleting] = useState(false);
useEffect(() => {
//
if (!authApi.hasToken()) {
navigate('/admin');
return;
}
setUser(authApi.getCurrentUser());
if (isAuthenticated) {
fetchAlbums();
}, [navigate]);
}
}, [isAuthenticated]);
const fetchAlbums = async () => {
try {

View file

@ -1,12 +1,9 @@
import { useState, useEffect } from 'react';
import { useNavigate, Link } from 'react-router-dom';
import { Link } from 'react-router-dom';
import { motion } from 'framer-motion';
import {
Disc3, Calendar, Users,
Home, ChevronRight
} from 'lucide-react';
import { Disc3, Calendar, Users, Home, ChevronRight } from 'lucide-react';
import AdminLayout from '../../../components/admin/AdminLayout';
import * as authApi from '../../../api/admin/auth';
import useAdminAuth from '../../../hooks/useAdminAuth';
import { getMembers } from '../../../api/public/members';
import { getAlbums, getAlbum } from '../../../api/public/albums';
import { getSchedules } from '../../../api/public/schedules';
@ -43,8 +40,7 @@ function AnimatedNumber({ value }) {
}
function AdminDashboard() {
const navigate = useNavigate();
const [user, setUser] = useState(null);
const { user, isAuthenticated } = useAdminAuth();
const [stats, setStats] = useState({
albums: 0,
photos: 0,
@ -53,24 +49,10 @@ function AdminDashboard() {
});
useEffect(() => {
//
if (!authApi.hasToken()) {
navigate('/admin');
return;
}
setUser(authApi.getCurrentUser());
//
authApi.verifyToken()
.catch(() => {
authApi.logout();
navigate('/admin');
});
//
if (isAuthenticated) {
fetchStats();
}, [navigate]);
}
}, [isAuthenticated]);
const fetchStats = async () => {
//

View file

@ -14,6 +14,7 @@ import Tooltip from '../../../components/Tooltip';
import AdminLayout from '../../../components/admin/AdminLayout';
import ConfirmDialog from '../../../components/admin/ConfirmDialog';
import useScheduleStore from '../../../stores/useScheduleStore';
import useAdminAuth from '../../../hooks/useAdminAuth';
import useToast from '../../../hooks/useToast';
import { getTodayKST, formatDate } from '../../../utils/date';
import * as schedulesApi from '../../../api/admin/schedules';
@ -151,9 +152,10 @@ function AdminSchedule() {
scrollPosition, setScrollPosition,
} = useScheduleStore();
const { user, isAuthenticated } = useAdminAuth();
// ( )
const [loading, setLoading] = useState(false);
const [user, setUser] = useState(null);
const { toast, setToast } = useToast();
const scrollContainerRef = useRef(null);
const searchContainerRef = useRef(null); // ( )
@ -363,15 +365,7 @@ function AdminSchedule() {
};
useEffect(() => {
const token = localStorage.getItem('adminToken');
const userData = localStorage.getItem('adminUser');
if (!token || !userData) {
navigate('/admin');
return;
}
setUser(JSON.parse(userData));
if (!isAuthenticated) return;
//
fetchCategories();
@ -382,7 +376,7 @@ function AdminSchedule() {
setToast(JSON.parse(savedToast));
sessionStorage.removeItem('scheduleToast');
}
}, [navigate]);
}, [isAuthenticated]);
//

View file

@ -8,6 +8,7 @@ import {
import Toast from '../../../components/Toast';
import Tooltip from '../../../components/Tooltip';
import AdminLayout from '../../../components/admin/AdminLayout';
import useAdminAuth from '../../../hooks/useAdminAuth';
import useToast from '../../../hooks/useToast';
import * as botsApi from '../../../api/admin/bots';
@ -43,7 +44,7 @@ const MeilisearchIcon = ({ size = 20 }) => (
function AdminScheduleBots() {
const navigate = useNavigate();
const [user, setUser] = useState(null);
const { user, isAuthenticated } = useAdminAuth();
const { toast, setToast } = useToast();
const [bots, setBots] = useState([]);
const [loading, setLoading] = useState(true);
@ -52,18 +53,11 @@ function AdminScheduleBots() {
const [quotaWarning, setQuotaWarning] = useState(null); //
useEffect(() => {
const token = localStorage.getItem('adminToken');
const userData = localStorage.getItem('adminUser');
if (!token || !userData) {
navigate('/admin');
return;
}
setUser(JSON.parse(userData));
if (isAuthenticated) {
fetchBots();
fetchQuotaWarning();
}, [navigate]);
}
}, [isAuthenticated]);
//
const fetchBots = async () => {

View file

@ -6,8 +6,8 @@ import { HexColorPicker } from 'react-colorful';
import Toast from '../../../components/Toast';
import AdminLayout from '../../../components/admin/AdminLayout';
import ConfirmDialog from '../../../components/admin/ConfirmDialog';
import useAdminAuth from '../../../hooks/useAdminAuth';
import useToast from '../../../hooks/useToast';
import * as authApi from '../../../api/admin/auth';
import * as categoriesApi from '../../../api/admin/categories';
// (8)
@ -38,7 +38,7 @@ const getColorStyle = (colorValue) => {
function AdminScheduleCategory() {
const navigate = useNavigate();
const [user, setUser] = useState(null);
const { user, isAuthenticated } = useAdminAuth();
const [categories, setCategories] = useState([]);
const [loading, setLoading] = useState(true);
const { toast, setToast, showSuccess, showError } = useToast();
@ -55,24 +55,12 @@ function AdminScheduleCategory() {
//
const [colorPickerOpen, setColorPickerOpen] = useState(false);
//
//
useEffect(() => {
if (!authApi.hasToken()) {
navigate('/admin');
return;
}
authApi.verifyToken()
.then(data => {
if (data.valid) {
setUser(data.user);
if (isAuthenticated) {
fetchCategories();
} else {
navigate('/admin');
}
})
.catch(() => navigate('/admin'));
}, [navigate]);
}, [isAuthenticated]);
//
const fetchCategories = async () => {

View file

@ -28,8 +28,8 @@ import CustomDatePicker from "../../../components/admin/CustomDatePicker";
import CustomTimePicker from "../../../components/admin/CustomTimePicker";
import AdminLayout from "../../../components/admin/AdminLayout";
import ConfirmDialog from "../../../components/admin/ConfirmDialog";
import useAdminAuth from "../../../hooks/useAdminAuth";
import useToast from "../../../hooks/useToast";
import * as authApi from "../../../api/admin/auth";
import * as categoriesApi from "../../../api/admin/categories";
import * as schedulesApi from "../../../api/admin/schedules";
import { getMembers } from "../../../api/public/members";
@ -38,8 +38,8 @@ function AdminScheduleForm() {
const navigate = useNavigate();
const { id } = useParams();
const isEditMode = !!id;
const { user, isAuthenticated } = useAdminAuth();
const [user, setUser] = useState(null);
const { toast, setToast } = useToast();
const [loading, setLoading] = useState(false);
const [members, setMembers] = useState([]);
@ -147,12 +147,8 @@ function AdminScheduleForm() {
};
useEffect(() => {
if (!authApi.hasToken()) {
navigate("/admin");
return;
}
if (!isAuthenticated) return;
setUser(authApi.getCurrentUser());
fetchMembers();
fetchCategories();
@ -160,7 +156,7 @@ function AdminScheduleForm() {
if (isEditMode && id) {
fetchSchedule();
}
}, [navigate, isEditMode, id]);
}, [isAuthenticated, isEditMode, id]);
// ( )
const fetchSchedule = async () => {