import { useEffect, useState } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import './ComicLanding.css'; interface Chapter { slug: string; chapter: string; date: string; views: string; } interface ComicData { slug: string; title: string; indonesiaTitle: string; type: string; author: string; status: string; genre: string[]; synopsis: string; thumbnailUrl: string; chapters: Chapter[]; } export function ComicLanding() { const { sessionId } = useParams(); const navigate = useNavigate(); const [isAuth, setIsAuth] = useState(false); const [password, setPassword] = useState(''); const [comicData, setComicData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [searchQuery, setSearchQuery] = useState(''); const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc'); const [loadingChapter, setLoadingChapter] = useState(false); const [isSynopsisExpanded, setIsSynopsisExpanded] = useState(false); const [isChaptersExpanded, setIsChaptersExpanded] = useState(false); const [ws, setWs] = useState(null); const SYNOPSIS_CHAR_LIMIT = 200; const CHAPTERS_INITIAL_DISPLAY = 20; useEffect(() => { const storedSession = sessionStorage.getItem(`comic_${sessionId}`); if (storedSession) { const session = JSON.parse(storedSession); const now = Date.now(); if (now < session.expiresAt) { setIsAuth(true); setPassword(session.password); setLoading(false); return; } else { sessionStorage.removeItem(`comic_${sessionId}`); } } const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}/ws`; const websocket = new WebSocket(wsUrl); websocket.onopen = () => { websocket.send(JSON.stringify({ type: 'validate_comic_password', sessionId })); }; websocket.onmessage = (event) => { const message = JSON.parse(event.data); if (message.type === 'password_validation_response') { if (!message.success) { setError(message.error || 'Session tidak valid'); setLoading(false); } else { setLoading(false); } } if (message.type === 'session_force_closed') { setError('Session dihapus oleh admin. Silakan buat session baru.'); sessionStorage.removeItem(`comic_${sessionId}`); setTimeout(() => { navigate('/'); }, 3000); } if (message.type === 'comic_data_response') { if (message.success) { setComicData(message.data); setIsAuth(true); setPassword((currentPassword) => { sessionStorage.setItem(`comic_${sessionId}`, JSON.stringify({ password: currentPassword, expiresAt: message.expiresAt })); return currentPassword; }); setLoadingChapter(false); } else { setError(message.error || 'Gagal memuat data'); setLoadingChapter(false); } } if (message.type === 'chapter_data_response') { setLoadingChapter(false); if (message.success) { setPassword((currentPassword) => { navigate(`/read/${sessionId}_chapter`, { state: { chapterData: message.data, password: currentPassword, sessionId: sessionId } }); return currentPassword; }); } else { setError(message.error || 'Gagal memuat chapter'); setTimeout(() => setError(''), 3000); } } }; websocket.onerror = () => { setError('Koneksi WebSocket gagal'); setLoading(false); }; setWs(websocket); return () => { if (websocket.readyState === WebSocket.OPEN || websocket.readyState === WebSocket.CONNECTING) { websocket.close(); } }; }, [sessionId, navigate]); useEffect(() => { const storedSession = sessionStorage.getItem(`comic_${sessionId}`); if (isAuth && !comicData && password) { if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'request_comic_data', sessionId, password })); } else if (!ws && storedSession) { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}/ws`; const websocket = new WebSocket(wsUrl); websocket.onopen = () => { websocket.send(JSON.stringify({ type: 'request_comic_data', sessionId, password })); }; websocket.onmessage = (event) => { const message = JSON.parse(event.data); if (message.type === 'comic_data_response') { if (message.success) { setComicData(message.data); } else { setError(message.error || 'Gagal memuat data'); } } }; setWs(websocket); } } }, [isAuth, ws, comicData, sessionId, password]); const handlePasswordSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!password.trim()) { setError('Password tidak boleh kosong'); return; } if (ws) { ws.send(JSON.stringify({ type: 'request_comic_data', sessionId, password: password.trim() })); } }; const handleReadChapter = (chapterSlug: string) => { if (!ws) { setError('WebSocket tidak terhubung'); return; } if (!password) { setError('Password tidak tersedia'); return; } setLoadingChapter(true); ws.send(JSON.stringify({ type: 'request_chapter_data', sessionId, password, chapterSlug })); }; const filteredChapters = comicData?.chapters.filter(ch => ch.chapter.toLowerCase().includes(searchQuery.toLowerCase()) ) || []; const sortedChapters = [...filteredChapters].sort((a, b) => { const numA = parseFloat(a.chapter.match(/[\d.]+/)?.[0] || '0'); const numB = parseFloat(b.chapter.match(/[\d.]+/)?.[0] || '0'); return sortOrder === 'asc' ? numA - numB : numB - numA; }); const shouldTruncateSynopsis = comicData && comicData.synopsis.length > SYNOPSIS_CHAR_LIMIT; const displayedSynopsis = comicData && shouldTruncateSynopsis && !isSynopsisExpanded ? comicData.synopsis.slice(0, SYNOPSIS_CHAR_LIMIT) + '...' : comicData?.synopsis; const shouldShowMoreChapters = sortedChapters.length > CHAPTERS_INITIAL_DISPLAY; const displayedChapters = isChaptersExpanded || searchQuery ? sortedChapters : sortedChapters.slice(0, CHAPTERS_INITIAL_DISPLAY); if (loading) { return (

Loading...

); } if (error && !isAuth) { return (
⚠️

Oops!

{error}

); } if (!isAuth) { return (
🔒

Protected Content

Masukkan password untuk melanjutkan

setPassword(e.target.value)} className="password-input" autoFocus />
{error &&
{error}
}

Password adalah yang kamu set di bot WhatsApp

); } if (!comicData) { return (

Memuat data comic...

); } return (
{loadingChapter && (

Loading chapter...

)} {error && isAuth && (
{error}
)}
{comicData.title}
{comicData.type}

{comicData.title}

{comicData.indonesiaTitle && (

{comicData.indonesiaTitle}

)}
Author: {comicData.author}
Status: {comicData.status}
Total Chapters: {comicData.chapters.length}
{comicData.genre.map((g, i) => ( {g} ))}

Synopsis

{displayedSynopsis}

{shouldTruncateSynopsis && ( )}

Chapters

setSearchQuery(e.target.value)} />
{displayedChapters.length === 0 ? (

No chapters found

) : ( displayedChapters.map((chapter, index) => (
handleReadChapter(chapter.slug)} >
{chapter.chapter}
{chapter.date} {chapter.views}
Read →
)) )}
{shouldShowMoreChapters && !searchQuery && (
)}
); }