refactor: useToast 커스텀 훅으로 Toast 로직 통합
- hooks/useToast.js 생성 (44줄) - 적용 파일 (9개): - AdminMembers.jsx - AdminMemberEdit.jsx - AdminAlbums.jsx - AdminAlbumForm.jsx - AdminAlbumPhotos.jsx - AdminSchedule.jsx - AdminScheduleForm.jsx - AdminScheduleBots.jsx - AdminScheduleCategory.jsx - 각 파일에서 중복된 toast useState/useEffect 제거 - showSuccess/showError 편의 메서드 활용 총 약 70줄의 중복 코드 제거
This commit is contained in:
parent
3367f4806d
commit
0b00055773
10 changed files with 81 additions and 86 deletions
56
frontend/src/hooks/useToast.js
Normal file
56
frontend/src/hooks/useToast.js
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
/**
|
||||||
|
* Toast 상태 관리 커스텀 훅
|
||||||
|
* 자동 숨김 타이머 및 상태 관리를 제공
|
||||||
|
*/
|
||||||
|
import { useState, useEffect, useCallback } from "react";
|
||||||
|
|
||||||
|
function useToast(duration = 3000) {
|
||||||
|
const [toast, setToast] = useState(null);
|
||||||
|
|
||||||
|
// Toast 자동 숨김
|
||||||
|
useEffect(() => {
|
||||||
|
if (toast) {
|
||||||
|
const timer = setTimeout(() => setToast(null), duration);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}
|
||||||
|
}, [toast, duration]);
|
||||||
|
|
||||||
|
// Toast 표시 함수
|
||||||
|
const showToast = useCallback((message, type = "info") => {
|
||||||
|
setToast({ message, type });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 편의 메서드
|
||||||
|
const showSuccess = useCallback(
|
||||||
|
(message) => showToast(message, "success"),
|
||||||
|
[showToast]
|
||||||
|
);
|
||||||
|
const showError = useCallback(
|
||||||
|
(message) => showToast(message, "error"),
|
||||||
|
[showToast]
|
||||||
|
);
|
||||||
|
const showWarning = useCallback(
|
||||||
|
(message) => showToast(message, "warning"),
|
||||||
|
[showToast]
|
||||||
|
);
|
||||||
|
const showInfo = useCallback(
|
||||||
|
(message) => showToast(message, "info"),
|
||||||
|
[showToast]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Toast 숨김 함수
|
||||||
|
const hideToast = useCallback(() => setToast(null), []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
toast,
|
||||||
|
setToast,
|
||||||
|
showToast,
|
||||||
|
showSuccess,
|
||||||
|
showError,
|
||||||
|
showWarning,
|
||||||
|
showInfo,
|
||||||
|
hideToast,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useToast;
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import Toast from '../../../components/Toast';
|
import Toast from '../../../components/Toast';
|
||||||
import CustomDatePicker from '../../../components/admin/CustomDatePicker';
|
import CustomDatePicker from '../../../components/admin/CustomDatePicker';
|
||||||
|
import useToast from '../../../hooks/useToast';
|
||||||
|
|
||||||
// 커스텀 드롭다운 컴포넌트
|
// 커스텀 드롭다운 컴포넌트
|
||||||
function CustomSelect({ value, onChange, options, placeholder }) {
|
function CustomSelect({ value, onChange, options, placeholder }) {
|
||||||
|
|
@ -83,15 +84,7 @@ function AdminAlbumForm() {
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [coverPreview, setCoverPreview] = useState(null);
|
const [coverPreview, setCoverPreview] = useState(null);
|
||||||
const [coverFile, setCoverFile] = useState(null);
|
const [coverFile, setCoverFile] = useState(null);
|
||||||
const [toast, setToast] = useState(null);
|
const { toast, setToast } = useToast();
|
||||||
|
|
||||||
// Toast 자동 숨김
|
|
||||||
useEffect(() => {
|
|
||||||
if (toast) {
|
|
||||||
const timer = setTimeout(() => setToast(null), 3000);
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}
|
|
||||||
}, [toast]);
|
|
||||||
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
title: '',
|
title: '',
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import {
|
||||||
Tag, FolderOpen, Save
|
Tag, FolderOpen, Save
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import Toast from '../../../components/Toast';
|
import Toast from '../../../components/Toast';
|
||||||
|
import useToast from '../../../hooks/useToast';
|
||||||
import * as authApi from '../../../api/admin/auth';
|
import * as authApi from '../../../api/admin/auth';
|
||||||
import { getAlbum } from '../../../api/public/albums';
|
import { getAlbum } from '../../../api/public/albums';
|
||||||
import { getMembers } from '../../../api/public/members';
|
import { getMembers } from '../../../api/public/members';
|
||||||
|
|
@ -24,7 +25,7 @@ function AdminAlbumPhotos() {
|
||||||
const [teasers, setTeasers] = useState([]); // 티저 이미지
|
const [teasers, setTeasers] = useState([]); // 티저 이미지
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [user, setUser] = useState(null);
|
const [user, setUser] = useState(null);
|
||||||
const [toast, setToast] = useState(null);
|
const { toast, setToast } = useToast();
|
||||||
const [selectedPhotos, setSelectedPhotos] = useState([]);
|
const [selectedPhotos, setSelectedPhotos] = useState([]);
|
||||||
const [uploading, setUploading] = useState(false);
|
const [uploading, setUploading] = useState(false);
|
||||||
const [uploadProgress, setUploadProgress] = useState(0);
|
const [uploadProgress, setUploadProgress] = useState(0);
|
||||||
|
|
@ -139,14 +140,6 @@ function AdminAlbumPhotos() {
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Toast 자동 숨김
|
|
||||||
useEffect(() => {
|
|
||||||
if (toast) {
|
|
||||||
const timer = setTimeout(() => setToast(null), 3000);
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}
|
|
||||||
}, [toast]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 로그인 확인
|
// 로그인 확인
|
||||||
if (!authApi.hasToken()) {
|
if (!authApi.hasToken()) {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import Toast from '../../../components/Toast';
|
import Toast from '../../../components/Toast';
|
||||||
import Tooltip from '../../../components/Tooltip';
|
import Tooltip from '../../../components/Tooltip';
|
||||||
|
import useToast from '../../../hooks/useToast';
|
||||||
import * as authApi from '../../../api/admin/auth';
|
import * as authApi from '../../../api/admin/auth';
|
||||||
import { getAlbums } from '../../../api/public/albums';
|
import { getAlbums } from '../../../api/public/albums';
|
||||||
import * as albumsApi from '../../../api/admin/albums';
|
import * as albumsApi from '../../../api/admin/albums';
|
||||||
|
|
@ -17,18 +18,10 @@ function AdminAlbums() {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [user, setUser] = useState(null);
|
const [user, setUser] = useState(null);
|
||||||
const [toast, setToast] = useState(null);
|
const { toast, setToast } = useToast();
|
||||||
const [deleteDialog, setDeleteDialog] = useState({ show: false, album: null });
|
const [deleteDialog, setDeleteDialog] = useState({ show: false, album: null });
|
||||||
const [deleting, setDeleting] = useState(false);
|
const [deleting, setDeleting] = useState(false);
|
||||||
|
|
||||||
// Toast 자동 숨김
|
|
||||||
useEffect(() => {
|
|
||||||
if (toast) {
|
|
||||||
const timer = setTimeout(() => setToast(null), 3000);
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}
|
|
||||||
}, [toast]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 로그인 확인
|
// 로그인 확인
|
||||||
if (!authApi.hasToken()) {
|
if (!authApi.hasToken()) {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import Toast from '../../../components/Toast';
|
import Toast from '../../../components/Toast';
|
||||||
import CustomDatePicker from '../../../components/admin/CustomDatePicker';
|
import CustomDatePicker from '../../../components/admin/CustomDatePicker';
|
||||||
|
import useToast from '../../../hooks/useToast';
|
||||||
import * as authApi from '../../../api/admin/auth';
|
import * as authApi from '../../../api/admin/auth';
|
||||||
import * as membersApi from '../../../api/admin/members';
|
import * as membersApi from '../../../api/admin/members';
|
||||||
|
|
||||||
|
|
@ -16,7 +17,7 @@ function AdminMemberEdit() {
|
||||||
const [user, setUser] = useState(null);
|
const [user, setUser] = useState(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [toast, setToast] = useState(null);
|
const { toast, setToast } = useToast();
|
||||||
const [imagePreview, setImagePreview] = useState(null);
|
const [imagePreview, setImagePreview] = useState(null);
|
||||||
const [imageFile, setImageFile] = useState(null);
|
const [imageFile, setImageFile] = useState(null);
|
||||||
|
|
||||||
|
|
@ -28,14 +29,6 @@ function AdminMemberEdit() {
|
||||||
is_former: false
|
is_former: false
|
||||||
});
|
});
|
||||||
|
|
||||||
// Toast 자동 숨김
|
|
||||||
useEffect(() => {
|
|
||||||
if (toast) {
|
|
||||||
const timer = setTimeout(() => setToast(null), 3000);
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}
|
|
||||||
}, [toast]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 로그인 확인
|
// 로그인 확인
|
||||||
if (!authApi.hasToken()) {
|
if (!authApi.hasToken()) {
|
||||||
|
|
|
||||||
|
|
@ -6,21 +6,14 @@ import {
|
||||||
Home, ChevronRight, Users, User
|
Home, ChevronRight, Users, User
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import Toast from '../../../components/Toast';
|
import Toast from '../../../components/Toast';
|
||||||
|
import useToast from '../../../hooks/useToast';
|
||||||
|
|
||||||
function AdminMembers() {
|
function AdminMembers() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [members, setMembers] = useState([]);
|
const [members, setMembers] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [user, setUser] = useState(null);
|
const [user, setUser] = useState(null);
|
||||||
const [toast, setToast] = useState(null);
|
const { toast, setToast } = useToast();
|
||||||
|
|
||||||
// Toast 자동 숨김
|
|
||||||
useEffect(() => {
|
|
||||||
if (toast) {
|
|
||||||
const timer = setTimeout(() => setToast(null), 3000);
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}
|
|
||||||
}, [toast]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 로그인 확인
|
// 로그인 확인
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { useInView } from 'react-intersection-observer';
|
||||||
import Toast from '../../../components/Toast';
|
import Toast from '../../../components/Toast';
|
||||||
import Tooltip from '../../../components/Tooltip';
|
import Tooltip from '../../../components/Tooltip';
|
||||||
import useScheduleStore from '../../../stores/useScheduleStore';
|
import useScheduleStore from '../../../stores/useScheduleStore';
|
||||||
|
import useToast from '../../../hooks/useToast';
|
||||||
import { getTodayKST, formatDate } from '../../../utils/date';
|
import { getTodayKST, formatDate } from '../../../utils/date';
|
||||||
import * as schedulesApi from '../../../api/admin/schedules';
|
import * as schedulesApi from '../../../api/admin/schedules';
|
||||||
import * as categoriesApi from '../../../api/admin/categories';
|
import * as categoriesApi from '../../../api/admin/categories';
|
||||||
|
|
@ -138,7 +139,7 @@ function AdminSchedule() {
|
||||||
// 로컬 상태 (페이지 이동 시 유지할 필요 없는 것들)
|
// 로컬 상태 (페이지 이동 시 유지할 필요 없는 것들)
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [user, setUser] = useState(null);
|
const [user, setUser] = useState(null);
|
||||||
const [toast, setToast] = useState(null);
|
const { toast, setToast } = useToast();
|
||||||
const scrollContainerRef = useRef(null);
|
const scrollContainerRef = useRef(null);
|
||||||
const SEARCH_LIMIT = 5; // 테스트용 5개
|
const SEARCH_LIMIT = 5; // 테스트용 5개
|
||||||
|
|
||||||
|
|
@ -304,14 +305,6 @@ function AdminSchedule() {
|
||||||
return cat?.color || '#4A7C59';
|
return cat?.color || '#4A7C59';
|
||||||
};
|
};
|
||||||
|
|
||||||
// Toast 자동 숨김
|
|
||||||
useEffect(() => {
|
|
||||||
if (toast) {
|
|
||||||
const timer = setTimeout(() => setToast(null), 3000);
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}
|
|
||||||
}, [toast]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const token = localStorage.getItem('adminToken');
|
const token = localStorage.getItem('adminToken');
|
||||||
const userData = localStorage.getItem('adminUser');
|
const userData = localStorage.getItem('adminUser');
|
||||||
|
|
|
||||||
|
|
@ -7,26 +7,19 @@ import {
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import Toast from '../../../components/Toast';
|
import Toast from '../../../components/Toast';
|
||||||
import Tooltip from '../../../components/Tooltip';
|
import Tooltip from '../../../components/Tooltip';
|
||||||
|
import useToast from '../../../hooks/useToast';
|
||||||
import * as botsApi from '../../../api/admin/bots';
|
import * as botsApi from '../../../api/admin/bots';
|
||||||
|
|
||||||
function AdminScheduleBots() {
|
function AdminScheduleBots() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [user, setUser] = useState(null);
|
const [user, setUser] = useState(null);
|
||||||
const [toast, setToast] = useState(null);
|
const { toast, setToast } = useToast();
|
||||||
const [bots, setBots] = useState([]);
|
const [bots, setBots] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [isInitialLoad, setIsInitialLoad] = useState(true); // 첫 로드 여부 (애니메이션용)
|
const [isInitialLoad, setIsInitialLoad] = useState(true); // 첫 로드 여부 (애니메이션용)
|
||||||
const [syncing, setSyncing] = useState(null); // 동기화 중인 봇 ID
|
const [syncing, setSyncing] = useState(null); // 동기화 중인 봇 ID
|
||||||
const [quotaWarning, setQuotaWarning] = useState(null); // 할당량 경고 상태
|
const [quotaWarning, setQuotaWarning] = useState(null); // 할당량 경고 상태
|
||||||
|
|
||||||
// Toast 자동 숨김
|
|
||||||
useEffect(() => {
|
|
||||||
if (toast) {
|
|
||||||
const timer = setTimeout(() => setToast(null), 3000);
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}
|
|
||||||
}, [toast]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const token = localStorage.getItem('adminToken');
|
const token = localStorage.getItem('adminToken');
|
||||||
const userData = localStorage.getItem('adminUser');
|
const userData = localStorage.getItem('adminUser');
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { motion, AnimatePresence, Reorder } from 'framer-motion';
|
||||||
import { LogOut, Home, ChevronRight, Plus, Edit3, Trash2, GripVertical, X, AlertTriangle } from 'lucide-react';
|
import { LogOut, Home, ChevronRight, Plus, Edit3, Trash2, GripVertical, X, AlertTriangle } from 'lucide-react';
|
||||||
import { HexColorPicker } from 'react-colorful';
|
import { HexColorPicker } from 'react-colorful';
|
||||||
import Toast from '../../../components/Toast';
|
import Toast from '../../../components/Toast';
|
||||||
|
import useToast from '../../../hooks/useToast';
|
||||||
import * as authApi from '../../../api/admin/auth';
|
import * as authApi from '../../../api/admin/auth';
|
||||||
import * as categoriesApi from '../../../api/admin/categories';
|
import * as categoriesApi from '../../../api/admin/categories';
|
||||||
|
|
||||||
|
|
@ -38,13 +39,7 @@ function AdminScheduleCategory() {
|
||||||
const [user, setUser] = useState(null);
|
const [user, setUser] = useState(null);
|
||||||
const [categories, setCategories] = useState([]);
|
const [categories, setCategories] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [toast, setToast] = useState(null);
|
const { toast, setToast, showSuccess, showError } = useToast();
|
||||||
|
|
||||||
// 토스트 표시 (3초 후 자동 닫힘)
|
|
||||||
const showToast = (type, message) => {
|
|
||||||
setToast({ type, message });
|
|
||||||
setTimeout(() => setToast(null), 3000);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 모달 상태
|
// 모달 상태
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
|
|
@ -84,7 +79,7 @@ function AdminScheduleCategory() {
|
||||||
setCategories(data);
|
setCategories(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('카테고리 조회 오류:', error);
|
console.error('카테고리 조회 오류:', error);
|
||||||
showToast('error', '카테고리를 불러오는데 실패했습니다.');
|
showError('카테고리를 불러오는데 실패했습니다.');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -112,7 +107,7 @@ function AdminScheduleCategory() {
|
||||||
// 카테고리 저장
|
// 카테고리 저장
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
if (!formData.name.trim()) {
|
if (!formData.name.trim()) {
|
||||||
showToast('error', '카테고리 이름을 입력해주세요.');
|
showError('카테고리 이름을 입력해주세요.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,7 +117,7 @@ function AdminScheduleCategory() {
|
||||||
&& cat.id !== editingCategory?.id
|
&& cat.id !== editingCategory?.id
|
||||||
);
|
);
|
||||||
if (isDuplicate) {
|
if (isDuplicate) {
|
||||||
showToast('error', '이미 존재하는 카테고리입니다.');
|
showError('이미 존재하는 카테고리입니다.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -132,12 +127,12 @@ function AdminScheduleCategory() {
|
||||||
} else {
|
} else {
|
||||||
await categoriesApi.createCategory(formData);
|
await categoriesApi.createCategory(formData);
|
||||||
}
|
}
|
||||||
showToast('success', editingCategory ? '카테고리가 수정되었습니다.' : '카테고리가 추가되었습니다.');
|
showSuccess(editingCategory ? '카테고리가 수정되었습니다.' : '카테고리가 추가되었습니다.');
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
fetchCategories();
|
fetchCategories();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('저장 오류:', error);
|
console.error('저장 오류:', error);
|
||||||
showToast('error', error.message || '저장에 실패했습니다.');
|
showError(error.message || '저장에 실패했습니다.');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -153,13 +148,13 @@ function AdminScheduleCategory() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await categoriesApi.deleteCategory(deleteTarget.id);
|
await categoriesApi.deleteCategory(deleteTarget.id);
|
||||||
showToast('success', '카테고리가 삭제되었습니다.');
|
showSuccess('카테고리가 삭제되었습니다.');
|
||||||
setDeleteDialogOpen(false);
|
setDeleteDialogOpen(false);
|
||||||
setDeleteTarget(null);
|
setDeleteTarget(null);
|
||||||
fetchCategories();
|
fetchCategories();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('삭제 오류:', error);
|
console.error('삭제 오류:', error);
|
||||||
showToast('error', error.message || '삭제에 실패했습니다.');
|
showError(error.message || '삭제에 실패했습니다.');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import {
|
||||||
import Toast from "../../../components/Toast";
|
import Toast from "../../../components/Toast";
|
||||||
import Lightbox from "../../../components/common/Lightbox";
|
import Lightbox from "../../../components/common/Lightbox";
|
||||||
import CustomDatePicker from "../../../components/admin/CustomDatePicker";
|
import CustomDatePicker from "../../../components/admin/CustomDatePicker";
|
||||||
|
import useToast from "../../../hooks/useToast";
|
||||||
import * as authApi from "../../../api/admin/auth";
|
import * as authApi from "../../../api/admin/auth";
|
||||||
import * as categoriesApi from "../../../api/admin/categories";
|
import * as categoriesApi from "../../../api/admin/categories";
|
||||||
import * as schedulesApi from "../../../api/admin/schedules";
|
import * as schedulesApi from "../../../api/admin/schedules";
|
||||||
|
|
@ -401,7 +402,7 @@ function AdminScheduleForm() {
|
||||||
const isEditMode = !!id;
|
const isEditMode = !!id;
|
||||||
|
|
||||||
const [user, setUser] = useState(null);
|
const [user, setUser] = useState(null);
|
||||||
const [toast, setToast] = useState(null);
|
const { toast, setToast } = useToast();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [members, setMembers] = useState([]);
|
const [members, setMembers] = useState([]);
|
||||||
|
|
||||||
|
|
@ -507,14 +508,6 @@ function AdminScheduleForm() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Toast 자동 숨김
|
|
||||||
useEffect(() => {
|
|
||||||
if (toast) {
|
|
||||||
const timer = setTimeout(() => setToast(null), 3000);
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}
|
|
||||||
}, [toast]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!authApi.hasToken()) {
|
if (!authApi.hasToken()) {
|
||||||
navigate("/admin");
|
navigate("/admin");
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue