/** Shared utility functions for Cloud Reader */ /** * Format an ISO date string to a human-readable date. */ export function formatDate(iso: string): string { const date = new Date(iso); return date.toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric", }); } /** * Format a date as a relative time string (e.g., "2h ago", "3d ago"). */ export function formatRelativeTime(iso: string): string { const now = Date.now(); const then = new Date(iso).getTime(); const diffMs = now - then; const seconds = Math.floor(diffMs / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); if (days > 0) return `${days}d ago`; if (hours > 0) return `${hours}h ago`; if (minutes > 0) return `${minutes}m ago`; return "just now"; } /** * Validate an email address format. */ export function isValidEmail(email: string): boolean { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); } /** * Password strength check: * - At least 8 characters * - At least one uppercase letter * - At least one lowercase letter * - At least one digit */ export function isStrongPassword(password: string): boolean { return ( password.length >= 8 && /[A-Z]/.test(password) && /[a-z]/.test(password) && /\d/.test(password) ); } /** * Check if two passwords match. */ export function doPasswordsMatch(password: string, confirm: string): boolean { return password === confirm; } /** * Get a user-friendly reading status label. */ export function readingStatusLabel(status: string): string { const labels: Record = { to_read: "To Read", reading: "Reading", finished: "Finished", dnf: "Did Not Finish", }; return labels[status] ?? status; } /** * Format file size in bytes to a human-readable string. */ export function formatFileSize(bytes: number): string { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } // ---- API endpoint constants ---- export const API_ENDPOINTS = { auth: { login: "/api/auth/login/", register: "/api/auth/register/", tokenRefresh: "/api/auth/token/refresh/", profile: "/api/auth/profile/", }, books: { list: "/api/books/", detail: (id: number | string) => `/api/books/${id}/`, genres: "/api/books/genres/", authors: "/api/books/authors/", }, ebooks: { list: "/api/ebooks/", detail: (id: number) => `/api/ebooks/${id}/`, upload: "/api/ebooks/upload/", toc: (id: number) => `/api/ebooks/${id}/toc/`, content: (id: number, page: number) => `/api/ebooks/${id}/content/?page=${page}`, progress: (id: number) => `/api/ebooks/${id}/progress/`, settings: (id: number) => `/api/ebooks/${id}/settings/`, }, annotations: { list: "/api/annotations/", bookmarks: "/api/annotations/bookmarks/", notes: "/api/annotations/notes/", bookmarkDetail: (id: string) => `/api/annotations/bookmarks/${id}/`, noteDetail: (id: string) => `/api/annotations/notes/${id}/`, }, } as const;