import { useState, useRef, useEffect } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { useGetDocuments, useCreateDocument } from '../generated/anthoLumeAPIV1';
import type { Document, DocumentsResponse } from '../generated/model';
import { ActivityIcon, DownloadIcon, Search2Icon, UploadIcon } from '../icons';
import { LoadingState, Pagination } from '../components';
import { useToasts } from '../components/ToastContext';
import { formatDuration } from '../utils/formatters';
import { useDebounce } from '../hooks/useDebounce';
import { getErrorMessage } from '../utils/errors';
import {
getDocumentsViewMode,
setDocumentsViewMode,
type DocumentsViewMode,
} from '../utils/localSettings';
interface DocumentCardProps {
doc: Document;
}
function DocumentCard({ doc }: DocumentCardProps) {
const navigate = useNavigate();
const percentage = doc.percentage || 0;
const totalTimeSeconds = doc.total_time_seconds || 0;
return (
navigate(`/documents/${doc.id}`)}
onKeyDown={event => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
navigate(`/documents/${doc.id}`);
}
}}
>
Title
{doc.title || 'Unknown'}
Author
{doc.author || 'Unknown'}
Time Read
{formatDuration(totalTimeSeconds)}
);
}
interface DocumentListItemProps {
doc: Document;
}
function DocumentListItem({ doc }: DocumentListItemProps) {
const navigate = useNavigate();
const percentage = doc.percentage || 0;
const totalTimeSeconds = doc.total_time_seconds || 0;
return (
navigate(`/documents/${doc.id}`)}
onKeyDown={event => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
navigate(`/documents/${doc.id}`);
}
}}
>
Title
{doc.title || 'Unknown'}
Author
{doc.author || 'Unknown'}
Time Read
{formatDuration(totalTimeSeconds)}
);
}
export default function DocumentsPage() {
const [search, setSearch] = useState('');
const [page, setPage] = useState(1);
const [limit] = useState(9);
const [uploadMode, setUploadMode] = useState(false);
const [viewMode, setViewMode] = useState(getDocumentsViewMode);
const fileInputRef = useRef(null);
const { showInfo, showWarning, showError } = useToasts();
const debouncedSearch = useDebounce(search, 300);
useEffect(() => {
setDocumentsViewMode(viewMode);
}, [viewMode]);
useEffect(() => {
setPage(1);
}, [debouncedSearch]);
const { data, isLoading, refetch } = useGetDocuments({ page, limit, search: debouncedSearch });
const createMutation = useCreateDocument();
const docs = (data?.data as DocumentsResponse | undefined)?.documents;
const previousPage = (data?.data as DocumentsResponse | undefined)?.previous_page;
const nextPage = (data?.data as DocumentsResponse | undefined)?.next_page;
const handleFileChange = async (e: React.ChangeEvent) => {
const file = e.target.files?.[0];
if (!file) return;
if (!file.name.endsWith('.epub')) {
showWarning('Please upload an EPUB file');
return;
}
try {
await createMutation.mutateAsync({
data: {
document_file: file,
},
});
showInfo('Document uploaded successfully!');
setUploadMode(false);
refetch();
} catch (error) {
showError('Failed to upload document: ' + getErrorMessage(error));
}
};
const handleCancelUpload = () => {
setUploadMode(false);
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
};
const getViewModeButtonClasses = (mode: DocumentsViewMode) =>
`rounded px-3 py-1 text-sm font-medium transition-colors ${
viewMode === mode
? 'bg-content text-content-inverse'
: 'text-content-muted hover:bg-surface-muted'
}`;
return (
{viewMode === 'grid' ? (
{isLoading ? (
) : docs && docs.length > 0 ? (
docs.map(doc =>
)
) : (
No documents found.
)}
) : (
{isLoading ? (
) : docs && docs.length > 0 ? (
docs.map(doc =>
)
) : (
No documents found.
)}
)}
);
}