/** * Author component utilities * Provides utility functions for author components */ import type { Author } from "@shared/schema"; import type { AuthorDisplayUtils, AuthorWithStats } from "./types"; /** * Utility functions for author display and formatting */ export const authorUtils: AuthorDisplayUtils = { /** * Get formatted lifespan display */ getLifespan: (author: Author): string => { if (!author.birthYear) return ""; return `${author.birthYear}${author.deathYear ? ` - ${author.deathYear}` : " - present"}`; }, /** * Get author initials for avatar fallback */ getInitials: (name: string): string => { return name .split(" ") .map((part) => part.charAt(0)) .join("") .toUpperCase() .slice(0, 2); }, /** * Format numbers for display */ formatNumber: (num: number | undefined, format: 'numbers' | 'abbreviated' | 'full' = 'numbers'): string => { if (num === undefined) return ''; if (format === 'abbreviated') { if (num >= 1000000) return `${(num / 1000000).toFixed(1)}M`; if (num >= 1000) return `${(num / 1000).toFixed(1)}K`; return num.toString(); } if (format === 'numbers') { return num.toLocaleString(); } return num.toString(); }, /** * Format rating for display */ formatRating: (rating: number | undefined): string => { if (rating === undefined) return ''; return rating.toFixed(1); }, /** * Get display location from country/city */ getLocation: (author: Author): string | undefined => { // TODO: This would need to be enhanced to resolve country/city IDs to names // For now, using the legacy country field if available return author.country; }, /** * Format bio with truncation */ formatBio: ( bio: string | undefined, maxLength: number, expanded: boolean = false ): { text: string; isTruncated: boolean } => { if (!bio) return { text: "", isTruncated: false }; const isTruncated = bio.length > maxLength && !expanded; const text = isTruncated ? `${bio.substring(0, maxLength)}...` : bio; return { text, isTruncated }; }, }; /** * Convert shared schema Author to AuthorWithStats for UI components */ export function enrichAuthorForUI( author: Author, stats?: { worksCount?: number; followersCount?: number; isFollowed?: boolean; isFeatured?: boolean; genres?: string[]; influences?: string[]; stats?: { views?: number; likes?: number; comments?: number; }; } ): AuthorWithStats { return { ...author, worksCount: stats?.worksCount, followersCount: stats?.followersCount, isFollowed: stats?.isFollowed || false, isFeatured: stats?.isFeatured || false, genres: stats?.genres, influences: stats?.influences, location: authorUtils.getLocation(author), nationality: author.country, // TODO: Resolve from countryId era: undefined, // TODO: Compute from birth/death years and literary movements stats: stats?.stats, }; } /** * Generate author URL from slug */ export function getAuthorUrl(author: Author): string { return `/authors/${author.slug}`; } /** * Check if author has complete profile information */ export function hasCompleteProfile(author: Author): boolean { return !!( author.name && author.biography && author.birthYear && author.portrait ); } /** * Calculate author age or lifespan */ export function getAuthorAge(author: Author): number | null { if (!author.birthYear) return null; const endYear = author.deathYear || new Date().getFullYear(); return endYear - author.birthYear; } /** * Sort authors by different criteria */ export function sortAuthors( authors: Author[], sortBy: "name" | "birthYear" | "alphabetical" = "name", order: "asc" | "desc" = "asc" ): Author[] { const sorted = [...authors].sort((a, b) => { let comparison = 0; switch (sortBy) { case "name": case "alphabetical": comparison = a.name.localeCompare(b.name); break; case "birthYear": { const aYear = a.birthYear || 0; const bYear = b.birthYear || 0; comparison = aYear - bYear; break; } default: comparison = 0; } return order === "desc" ? -comparison : comparison; }); return sorted; } /** * Filter authors by criteria */ export function filterAuthors( authors: Author[], filters: { country?: string; birthYearStart?: number; birthYearEnd?: number; hasPortrait?: boolean; hasBiography?: boolean; } ): Author[] { return authors.filter((author) => { // Filter by country if (filters.country && author.country !== filters.country) { return false; } // Filter by birth year range if ( filters.birthYearStart && (!author.birthYear || author.birthYear < filters.birthYearStart) ) { return false; } if ( filters.birthYearEnd && (!author.birthYear || author.birthYear > filters.birthYearEnd) ) { return false; } // Filter by portrait availability if (filters.hasPortrait && !author.portrait) { return false; } // Filter by biography availability if (filters.hasBiography && !author.biography) { return false; } return true; }); } /** * Search authors by query */ export function searchAuthors(authors: Author[], query: string): Author[] { if (!query.trim()) return authors; const searchTerm = query.toLowerCase().trim(); return authors.filter((author) => { return ( author.name.toLowerCase().includes(searchTerm) || author.biography?.toLowerCase().includes(searchTerm) || author.country?.toLowerCase().includes(searchTerm) ); }); } /** * Format lifespan from birth and death years */ export function formatLifespan(birthYear?: number, deathYear?: number): string { if (!birthYear) return ""; return `${birthYear}${deathYear ? ` - ${deathYear}` : " - present"}`; }