feat: show warning toast when attempting navigation on last or first chapter

main
I Luk Kim 3 weeks ago
parent 338ab8a16c
commit 0f98717c8f

@ -564,6 +564,17 @@ kbd {
animation: toastFadeIn 0.35s cubic-bezier(0.16, 1, 0.3, 1) forwards; animation: toastFadeIn 0.35s cubic-bezier(0.16, 1, 0.3, 1) forwards;
} }
.chapter-toast.warning-toast {
border-color: rgba(245, 158, 11, 0.4);
background: rgba(30, 25, 15, 0.92);
}
.toast-warning-text {
color: #fbbf24;
font-size: 14px;
font-weight: 600;
}
.toast-badge { .toast-badge {
background: linear-gradient(135deg, #7c3aed, #4f46e5); background: linear-gradient(135deg, #7c3aed, #4f46e5);
color: #fff; color: #fff;

@ -83,19 +83,31 @@ export function MangaViewer({
const [dismissNavError, setDismissNavError] = useState(false); const [dismissNavError, setDismissNavError] = useState(false);
const [boundaryWarning, setBoundaryWarning] = useState<'first' | 'last' | null>(null); const [boundaryWarning, setBoundaryWarning] = useState<'first' | 'last' | null>(null);
const [dimMap, setDimMap] = useState<Record<string, { width: number; height: number }>>({}); const [dimMap, setDimMap] = useState<Record<string, { width: number; height: number }>>({});
const [toast, setToast] = useState<{ title: string; episode: string | null } | null>(null); const [toast, setToast] = useState<{ title?: string; episode?: string | null; text?: string; isWarning?: boolean } | null>(null);
const toolbarTimer = useRef<ReturnType<typeof setTimeout> | null>(null); const toolbarTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
const toastTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const showNoticeToast = useCallback((text: string, isWarning = true) => {
setToast({ text, isWarning });
if (toastTimer.current) clearTimeout(toastTimer.current);
toastTimer.current = setTimeout(() => {
setToast(null);
}, 2800);
}, []);
// Trigger Chapter Notification Toast on chapter load/change // Trigger Chapter Notification Toast on chapter load/change
useEffect(() => { useEffect(() => {
if (!chapter.title) return; if (!chapter.title) return;
const parsed = parseTitleAndEpisode(chapter.title); const parsed = parseTitleAndEpisode(chapter.title);
setToast(parsed); setToast({ title: parsed.title, episode: parsed.episode, isWarning: false });
const timer = setTimeout(() => { if (toastTimer.current) clearTimeout(toastTimer.current);
toastTimer.current = setTimeout(() => {
setToast(null); setToast(null);
}, 2800); }, 2800);
return () => clearTimeout(timer); return () => {
if (toastTimer.current) clearTimeout(toastTimer.current);
};
}, [currentUrl, chapter.title]); }, [currentUrl, chapter.title]);
useEffect(() => { useEffect(() => {
@ -191,8 +203,10 @@ export function MangaViewer({
} else { } else {
setBoundaryWarning('last'); setBoundaryWarning('last');
} }
} else {
showNoticeToast('마지막 화입니다.', true);
} }
}, [slotIndex, totalSlots, chapter.next_chapter, boundaryWarning, onNavigate]); }, [slotIndex, totalSlots, chapter.next_chapter, boundaryWarning, onNavigate, showNoticeToast]);
const goPrev = useCallback(() => { const goPrev = useCallback(() => {
if (slotIndex > 0) { if (slotIndex > 0) {
@ -204,8 +218,10 @@ export function MangaViewer({
} else { } else {
setBoundaryWarning('first'); setBoundaryWarning('first');
} }
} else {
showNoticeToast('첫 번째 화입니다.', true);
} }
}, [slotIndex, chapter.prev_chapter, boundaryWarning, onNavigate]); }, [slotIndex, chapter.prev_chapter, boundaryWarning, onNavigate, showNoticeToast]);
const toggleFullscreen = useCallback(async () => { const toggleFullscreen = useCallback(async () => {
try { try {
@ -340,11 +356,17 @@ export function MangaViewer({
</div> </div>
)} )}
{/* Chapter Transition Toast */} {/* Chapter Transition / Warning Toast */}
{toast && ( {toast && (
<div className="chapter-toast"> <div className={`chapter-toast ${toast.isWarning ? 'warning-toast' : ''}`}>
{toast.episode && <span className="toast-badge">{toast.episode}</span>} {toast.isWarning ? (
<span className="toast-title">{toast.title}</span> <span className="toast-warning-text"> {toast.text}</span>
) : (
<>
{toast.episode && <span className="toast-badge">{toast.episode}</span>}
{toast.title && <span className="toast-title">{toast.title}</span>}
</>
)}
</div> </div>
)} )}

Loading…
Cancel
Save