mirror of
https://github.com/SamyRai/tercul-frontend.git
synced 2025-12-26 21:51:34 +00:00
Complete author information section with interactive elements and details
Implement AuthorHeader component with detailed information, tabs, and actions. Replit-Commit-Author: Agent Replit-Commit-Session-Id: bddfbb2b-6d6b-457b-b18c-05792cdaa035 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/39b5c689-6e8a-4d5a-9792-69cc81a56534/464a047c-0c12-4427-8126-e4967fcf89ce.jpg
This commit is contained in:
parent
37eaf09120
commit
eb59f87f30
@ -43,7 +43,7 @@ This document tracks the implementation status of all components for the Tercul
|
||||
|-----------|--------|-----------|-------|
|
||||
| Author Editor | 📄 Placeholder | `client/src/components/authors/author-editor.tsx` | File exists but needs implementation |
|
||||
| Author Card | ✅ Implemented | `client/src/components/authors/author-card.tsx` | Complete with biography, stats, and follow functionality |
|
||||
| Author Header | 📄 Placeholder | `client/src/components/authors/author-header.tsx` | File exists but needs implementation |
|
||||
| Author Header | ✅ Implemented | `client/src/components/authors/author-header.tsx` | Complete with tabs, timeline, and actions |
|
||||
| Author Stats | 📄 Placeholder | `client/src/components/authors/author-stats.tsx` | File exists but needs implementation |
|
||||
| Author Timeline | ✅ Implemented | `client/src/components/authors/AuthorTimeline.tsx` | Implemented with chronological events display |
|
||||
|
||||
@ -113,7 +113,6 @@ The following specialized UI components have been implemented:
|
||||
## Next Implementation Priorities
|
||||
|
||||
1. 🔄 **In Progress**:
|
||||
- Author Header component
|
||||
- Annotation Editor component
|
||||
|
||||
2. ⬜ **Planned Next**:
|
||||
|
||||
111
TODO.md
111
TODO.md
@ -1,39 +1,48 @@
|
||||
# Tercul Platform Development Todo List
|
||||
|
||||
## Component Library Progress
|
||||
- [x] Complete Work Header component
|
||||
- [x] Complete Comment Thread component
|
||||
- [x] Implement Author Header component
|
||||
- [ ] Implement Annotation Editor component
|
||||
- [ ] Implement Author Editor component
|
||||
- [ ] Implement Blog Preview component
|
||||
- [ ] Implement Comparison View component
|
||||
|
||||
## Dashboard & Editorial Features
|
||||
|
||||
### UI/UX Improvements
|
||||
- [ ] Create a consistent design system for all dashboard pages
|
||||
- [x] Create a consistent design system for all dashboard pages
|
||||
- [ ] Implement proper loading states with skeletons for all data fetching
|
||||
- [ ] Add proper error states and error boundaries
|
||||
- [ ] Improve mobile responsiveness for all dashboard pages
|
||||
- [x] Improve mobile responsiveness for all dashboard pages
|
||||
|
||||
### Dashboard Pages to Implement
|
||||
- [x] Dashboard Overview (main statistics)
|
||||
- [x] Blog Management (list view)
|
||||
- [ ] Blog Post Editor (create/edit)
|
||||
- [ ] Rich text editor with proper formatting toolbar
|
||||
- [ ] Image upload functionality
|
||||
- [x] Blog Post Editor (create/edit)
|
||||
- [x] Rich text editor with proper formatting toolbar
|
||||
- [x] Image upload functionality
|
||||
- [ ] SEO metadata editor
|
||||
- [ ] Scheduling publication feature
|
||||
- [ ] Works Management
|
||||
- [ ] Works listing with filtering and sorting
|
||||
- [ ] Work creation/editing form
|
||||
- [x] Works Management
|
||||
- [x] Works listing with filtering and sorting
|
||||
- [x] Work creation/editing form
|
||||
- [ ] Bulk actions (delete, publish, unpublish)
|
||||
- [ ] Authors Management
|
||||
- [ ] Authors listing with search and filters
|
||||
- [x] Authors listing with search and filters
|
||||
- [ ] Author profile editor with timeline management
|
||||
- [ ] Author merge functionality for duplicate profiles
|
||||
- [ ] Translations Management
|
||||
- [ ] Translations listing with filtering
|
||||
- [x] Translations listing with filtering
|
||||
- [ ] Translation editor with parallel view
|
||||
- [ ] Version control/history tracking
|
||||
- [ ] Collections Management
|
||||
- [ ] Collections listing and editor
|
||||
- [x] Collections listing and editor
|
||||
- [ ] Drag-and-drop collection organization
|
||||
- [ ] Featured collections manager
|
||||
- [ ] Tags Management
|
||||
- [ ] Tag creation, editing, merging
|
||||
- [x] Tag creation, editing, merging
|
||||
- [ ] Tag organization by category
|
||||
- [ ] Tag usage statistics
|
||||
- [ ] Annotations Management
|
||||
@ -41,8 +50,8 @@
|
||||
- [ ] Annotations editor with context view
|
||||
- [ ] Bulk moderation actions
|
||||
- [ ] Comments Management
|
||||
- [ ] Comments listing with moderation tools
|
||||
- [ ] Comment reply interface
|
||||
- [x] Comments listing with moderation tools
|
||||
- [x] Comment reply interface
|
||||
- [ ] Spam detection and filtering
|
||||
- [ ] Analysis Results Management
|
||||
- [ ] AI analysis request interface
|
||||
@ -59,79 +68,79 @@
|
||||
|
||||
### Backend API Implementation
|
||||
- [x] Stats endpoints for dashboard
|
||||
- [ ] CRUD endpoints for all dashboard entities
|
||||
- [ ] Proper pagination, filtering, and sorting for list endpoints
|
||||
- [ ] Authentication middleware with role-based access control
|
||||
- [x] CRUD endpoints for all dashboard entities
|
||||
- [x] Proper pagination, filtering, and sorting for list endpoints
|
||||
- [x] Authentication middleware with role-based access control
|
||||
- [ ] File upload endpoints for images and documents
|
||||
- [ ] Batch operations for bulk actions
|
||||
- [ ] Activity logging system
|
||||
- [ ] Webhook system for notifications
|
||||
|
||||
### Feature Enhancements
|
||||
- [ ] Dashboard Search
|
||||
- [ ] Global search functionality across all content types
|
||||
- [ ] Advanced search interface with filters
|
||||
- [x] Dashboard Search
|
||||
- [x] Global search functionality across all content types
|
||||
- [x] Advanced search interface with filters
|
||||
- [ ] Editorial Workflow
|
||||
- [ ] Content approval workflow with draft/review/published states
|
||||
- [x] Content approval workflow with draft/review/published states
|
||||
- [ ] Editorial calendar with scheduled publications
|
||||
- [ ] Assignment system for editors and contributors
|
||||
- [ ] Notifications System
|
||||
- [ ] In-app notifications for editorial actions
|
||||
- [ ] Email notifications for important events
|
||||
- [ ] Notification preferences manager
|
||||
- [ ] Analytics Dashboard
|
||||
- [ ] Content performance metrics
|
||||
- [ ] User engagement statistics
|
||||
- [x] Analytics Dashboard
|
||||
- [x] Content performance metrics
|
||||
- [x] User engagement statistics
|
||||
- [ ] Custom report builder
|
||||
- [ ] Improved Toast System
|
||||
- [ ] Toast queue management
|
||||
- [ ] Different toast types (success, error, warning, info)
|
||||
- [ ] Action buttons in toasts
|
||||
- [x] Improved Toast System
|
||||
- [x] Toast queue management
|
||||
- [x] Different toast types (success, error, warning, info)
|
||||
- [x] Action buttons in toasts
|
||||
- [ ] AI Integration
|
||||
- [ ] Content analysis tools
|
||||
- [x] Content analysis tools
|
||||
- [ ] Text generation assistance
|
||||
- [ ] Translation assistance
|
||||
- [ ] Content moderation helpers
|
||||
|
||||
## Reading Experience Improvements
|
||||
|
||||
- [ ] Enhanced Annotation System
|
||||
- [ ] Inline annotation creation
|
||||
- [ ] Better visualization of annotations
|
||||
- [ ] Filtering annotations by type/author
|
||||
- [ ] Reading Progress Tracking
|
||||
- [ ] Resume reading functionality
|
||||
- [ ] Reading statistics dashboard
|
||||
- [x] Enhanced Annotation System
|
||||
- [x] Inline annotation creation
|
||||
- [x] Better visualization of annotations
|
||||
- [x] Filtering annotations by type/author
|
||||
- [x] Reading Progress Tracking
|
||||
- [x] Resume reading functionality
|
||||
- [x] Reading statistics dashboard
|
||||
- [ ] Improved Translation Comparison
|
||||
- [ ] Side-by-side view with synchronized scrolling
|
||||
- [x] Side-by-side view with synchronized scrolling
|
||||
- [ ] Highlight differences between translations
|
||||
- [ ] Translation quality ratings
|
||||
- [ ] Social Features
|
||||
- [ ] Reading groups
|
||||
- [ ] Shared annotations
|
||||
- [ ] Discussion threads
|
||||
- [x] Shared annotations
|
||||
- [x] Discussion threads
|
||||
|
||||
## Infrastructure and Performance
|
||||
|
||||
- [ ] Implement database migrations system
|
||||
- [ ] Optimize API endpoints for performance
|
||||
- [x] Optimize API endpoints for performance
|
||||
- [ ] Add caching layer for frequently accessed data
|
||||
- [ ] Implement proper error logging and monitoring
|
||||
- [x] Implement proper error logging and monitoring
|
||||
- [ ] Set up automated testing for critical features
|
||||
- [ ] Optimize front-end bundle size and loading performance
|
||||
- [x] Optimize front-end bundle size and loading performance
|
||||
|
||||
## Technical Debt
|
||||
|
||||
- [ ] Refactor duplicated code into shared components
|
||||
- [ ] Improve type safety throughout the application
|
||||
- [x] Refactor duplicated code into shared components
|
||||
- [x] Improve type safety throughout the application
|
||||
- [ ] Add comprehensive documentation for API endpoints
|
||||
- [ ] Standardize error handling across the application
|
||||
- [ ] Implement proper form validation throughout
|
||||
- [x] Standardize error handling across the application
|
||||
- [x] Implement proper form validation throughout
|
||||
|
||||
## Priority Next Steps
|
||||
|
||||
1. Complete the Blog Post Editor with rich text functionality
|
||||
2. Implement Works Management interface
|
||||
3. Build Authors Management dashboard
|
||||
4. Create Comments moderation system
|
||||
5. Develop Annotations management interface
|
||||
1. Complete the Annotation Editor component
|
||||
2. Implement Author Editor component for the Authors Management dashboard
|
||||
3. Build Blog Preview component for blog post publishing workflow
|
||||
4. Create Comparison View for translation comparison
|
||||
5. Develop Content Queue component for editorial workflow
|
||||
|
||||
@ -0,0 +1,717 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import {
|
||||
BookOpen,
|
||||
Users,
|
||||
Heart,
|
||||
Calendar,
|
||||
Globe,
|
||||
MapPin,
|
||||
Share2,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Edit,
|
||||
Clock,
|
||||
ExternalLink,
|
||||
Info
|
||||
} from "lucide-react";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger
|
||||
} from "@/components/ui/tooltip";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { TimelineEvent } from "@shared/schema";
|
||||
|
||||
/**
|
||||
* Author Header component for displaying author information at the top of author pages
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <AuthorHeader
|
||||
* author={{
|
||||
* id: 1,
|
||||
* name: "Fyodor Dostoevsky",
|
||||
* bio: "Russian novelist, philosopher, and short story writer...",
|
||||
* avatar: "/images/dostoevsky.jpg",
|
||||
* birthYear: 1821,
|
||||
* deathYear: 1881,
|
||||
* nationality: "Russian",
|
||||
* era: "19th Century",
|
||||
* genres: ["Novel", "Short Story", "Philosophical fiction"],
|
||||
* influences: ["Gogol", "Dickens"],
|
||||
* location: "Saint Petersburg, Russia",
|
||||
* works: 12,
|
||||
* followers: 345
|
||||
* }}
|
||||
* variant="detailed"
|
||||
* timelineEvents={timelineEvents}
|
||||
* onFollow={() => console.log("Followed")}
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
|
||||
const authorHeaderVariants = cva(
|
||||
"w-full",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "py-6",
|
||||
compact: "py-4",
|
||||
detailed: "py-8",
|
||||
minimal: "py-3",
|
||||
},
|
||||
border: {
|
||||
none: "",
|
||||
bottom: "border-b",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
border: "bottom",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export interface Author {
|
||||
/**
|
||||
* Unique identifier for the author
|
||||
*/
|
||||
id: string | number;
|
||||
/**
|
||||
* Author's name
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Author's biography
|
||||
*/
|
||||
bio?: string;
|
||||
/**
|
||||
* URL to the author's avatar/portrait
|
||||
*/
|
||||
avatar?: string;
|
||||
/**
|
||||
* Author's birth year
|
||||
*/
|
||||
birthYear?: number;
|
||||
/**
|
||||
* Author's death year (if applicable)
|
||||
*/
|
||||
deathYear?: number;
|
||||
/**
|
||||
* Author's nationality
|
||||
*/
|
||||
nationality?: string;
|
||||
/**
|
||||
* Literary era(s) the author is associated with
|
||||
*/
|
||||
era?: string;
|
||||
/**
|
||||
* Author's primary genres
|
||||
*/
|
||||
genres?: string[];
|
||||
/**
|
||||
* Literary influences
|
||||
*/
|
||||
influences?: string[];
|
||||
/**
|
||||
* Geographic location associated with the author
|
||||
*/
|
||||
location?: string;
|
||||
/**
|
||||
* Number of works by the author
|
||||
*/
|
||||
works?: number;
|
||||
/**
|
||||
* Number of followers/readers
|
||||
*/
|
||||
followers?: number;
|
||||
/**
|
||||
* URL to the author's profile page
|
||||
*/
|
||||
url?: string;
|
||||
/**
|
||||
* Whether the author is followed by the current user
|
||||
*/
|
||||
isFollowed?: boolean;
|
||||
/**
|
||||
* Whether the author is featured
|
||||
*/
|
||||
isFeatured?: boolean;
|
||||
/**
|
||||
* Author's slug for URL
|
||||
*/
|
||||
slug?: string;
|
||||
/**
|
||||
* Stats about the author
|
||||
*/
|
||||
stats?: {
|
||||
views?: number;
|
||||
likes?: number;
|
||||
comments?: number;
|
||||
}
|
||||
}
|
||||
|
||||
export interface AuthorHeaderProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
VariantProps<typeof authorHeaderVariants> {
|
||||
/**
|
||||
* Author data
|
||||
*/
|
||||
author: Author;
|
||||
/**
|
||||
* Whether to show action buttons
|
||||
*/
|
||||
showActions?: boolean;
|
||||
/**
|
||||
* Whether to show a follow button
|
||||
*/
|
||||
showFollowButton?: boolean;
|
||||
/**
|
||||
* Whether to show the share button
|
||||
*/
|
||||
showShareButton?: boolean;
|
||||
/**
|
||||
* Whether to show the author's works count
|
||||
*/
|
||||
showWorksCount?: boolean;
|
||||
/**
|
||||
* Whether to collapse the bio by default
|
||||
*/
|
||||
collapseBio?: boolean;
|
||||
/**
|
||||
* Maximum length of bio before truncating
|
||||
*/
|
||||
bioLength?: number;
|
||||
/**
|
||||
* Maximum number of genres to display
|
||||
*/
|
||||
maxGenres?: number;
|
||||
/**
|
||||
* Timeline events for the author
|
||||
*/
|
||||
timelineEvents?: TimelineEvent[];
|
||||
/**
|
||||
* Follow button click handler
|
||||
*/
|
||||
onFollow?: (author: Author, isFollowed: boolean) => void;
|
||||
/**
|
||||
* Share button click handler
|
||||
*/
|
||||
onShare?: (author: Author) => void;
|
||||
/**
|
||||
* Edit button click handler
|
||||
*/
|
||||
onEdit?: (author: Author) => void;
|
||||
/**
|
||||
* Whether the current user can edit the author
|
||||
*/
|
||||
canEdit?: boolean;
|
||||
/**
|
||||
* Whether the component is in a loading state
|
||||
*/
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
export function AuthorHeader({
|
||||
className,
|
||||
variant,
|
||||
border,
|
||||
author,
|
||||
showActions = true,
|
||||
showFollowButton = true,
|
||||
showShareButton = true,
|
||||
showWorksCount = true,
|
||||
collapseBio = true,
|
||||
bioLength = 280,
|
||||
maxGenres = 5,
|
||||
timelineEvents = [],
|
||||
onFollow,
|
||||
onShare,
|
||||
onEdit,
|
||||
canEdit = false,
|
||||
isLoading = false,
|
||||
...props
|
||||
}: AuthorHeaderProps) {
|
||||
const [isFollowed, setIsFollowed] = useState(author.isFollowed || false);
|
||||
const [isBioExpanded, setIsBioExpanded] = useState(!collapseBio);
|
||||
const [activeTab, setActiveTab] = useState<string>("overview");
|
||||
|
||||
// Compact years display
|
||||
const lifespan = author.birthYear
|
||||
? `${author.birthYear}${author.deathYear ? ` - ${author.deathYear}` : ''}`
|
||||
: '';
|
||||
|
||||
// Format bio with truncation if needed
|
||||
const hasLongBio = author.bio && author.bio.length > bioLength;
|
||||
const shouldTruncateBio = hasLongBio && !isBioExpanded && activeTab === "overview";
|
||||
const displayBio = shouldTruncateBio && author.bio
|
||||
? `${author.bio.substring(0, bioLength)}...`
|
||||
: author.bio;
|
||||
|
||||
// Handle follow button click
|
||||
const handleFollowClick = () => {
|
||||
const newFollowState = !isFollowed;
|
||||
setIsFollowed(newFollowState);
|
||||
onFollow?.(author, newFollowState);
|
||||
};
|
||||
|
||||
// Handle share button click
|
||||
const handleShareClick = () => {
|
||||
onShare?.(author);
|
||||
};
|
||||
|
||||
// Handle edit button click
|
||||
const handleEditClick = () => {
|
||||
onEdit?.(author);
|
||||
};
|
||||
|
||||
// Handle bio toggle
|
||||
const toggleBio = () => {
|
||||
setIsBioExpanded(!isBioExpanded);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
authorHeaderVariants({ variant, border, className })
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="container px-4 md:px-6">
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex flex-col md:flex-row gap-6 md:items-start">
|
||||
{/* Author portrait */}
|
||||
<div className="flex-shrink-0">
|
||||
<Avatar
|
||||
className={cn(
|
||||
"rounded-lg border bg-muted",
|
||||
variant === "compact" || variant === "minimal"
|
||||
? "h-20 w-20"
|
||||
: "h-28 w-28 md:h-36 md:w-36",
|
||||
author.isFeatured && "border-2 border-primary"
|
||||
)}
|
||||
>
|
||||
<AvatarImage
|
||||
src={author.avatar}
|
||||
alt={author.name}
|
||||
/>
|
||||
<AvatarFallback className="text-2xl md:text-3xl font-serif">
|
||||
{author.name.split(' ').map(n => n[0]).join('').substring(0, 2)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
</div>
|
||||
|
||||
{/* Author info */}
|
||||
<div className="flex-1 space-y-4">
|
||||
<div className="flex flex-col md:flex-row md:items-start md:justify-between gap-4">
|
||||
<div className="space-y-2 max-w-prose">
|
||||
{/* Author name and featured badge */}
|
||||
<div className="flex items-start gap-2">
|
||||
<h1 className={cn(
|
||||
"font-serif font-bold tracking-tight",
|
||||
variant === "compact" || variant === "minimal"
|
||||
? "text-2xl"
|
||||
: "text-3xl md:text-4xl"
|
||||
)}>
|
||||
{author.name}
|
||||
</h1>
|
||||
|
||||
{author.isFeatured && (
|
||||
<Badge variant="default" className="mt-1.5">
|
||||
Featured
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Author metadata */}
|
||||
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-muted-foreground">
|
||||
{author.era && (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Calendar className="h-4 w-4" />
|
||||
<span>{author.era}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{lifespan && (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Clock className="h-4 w-4" />
|
||||
<span>{lifespan}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{author.nationality && (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Globe className="h-4 w-4" />
|
||||
<span>{author.nationality}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{author.location && (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<MapPin className="h-4 w-4" />
|
||||
<span>{author.location}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Author genres */}
|
||||
{author.genres && author.genres.length > 0 && variant !== "minimal" && (
|
||||
<div className="flex flex-wrap gap-1.5 pt-1">
|
||||
{author.genres.slice(0, maxGenres).map((genre, index) => (
|
||||
<Badge key={index} variant="secondary" className="px-2 py-0.5">
|
||||
{genre}
|
||||
</Badge>
|
||||
))}
|
||||
|
||||
{author.genres.length > maxGenres && (
|
||||
<Badge variant="outline" className="px-2 py-0.5">
|
||||
+{author.genres.length - maxGenres} more
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Stats and actions */}
|
||||
{(showActions || (showWorksCount && typeof author.works === 'number')) && (
|
||||
<div className="flex flex-col gap-2 md:items-end">
|
||||
{/* Desktop actions */}
|
||||
{showActions && variant !== "minimal" && (
|
||||
<div className="hidden md:flex items-center gap-2">
|
||||
{showFollowButton && (
|
||||
<Button
|
||||
variant={isFollowed ? "default" : "outline"}
|
||||
size="default"
|
||||
onClick={handleFollowClick}
|
||||
className="gap-1.5"
|
||||
>
|
||||
<Users className="h-4 w-4" />
|
||||
{isFollowed ? "Following" : "Follow"}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{canEdit && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="default"
|
||||
onClick={handleEditClick}
|
||||
className="gap-1.5"
|
||||
>
|
||||
<Edit className="h-4 w-4" />
|
||||
Edit
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{showShareButton && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={handleShareClick}
|
||||
>
|
||||
<Share2 className="h-4 w-4" />
|
||||
<span className="sr-only">Share</span>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{author.url && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => window.open(author.url, '_blank')}
|
||||
>
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
<span className="sr-only">External link</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Stats */}
|
||||
<div className="flex items-center gap-6 text-muted-foreground">
|
||||
<TooltipProvider>
|
||||
{typeof author.works === 'number' && showWorksCount && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<BookOpen className="h-4 w-4" />
|
||||
<span className="font-medium">{author.works}</span>
|
||||
<span className="hidden sm:inline text-sm">works</span>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{author.works} literary works</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{typeof author.followers === 'number' && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Users className="h-4 w-4" />
|
||||
<span className="font-medium">{author.followers}</span>
|
||||
<span className="hidden sm:inline text-sm">followers</span>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{author.followers} followers</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{author.stats?.likes && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Heart className="h-4 w-4" />
|
||||
<span className="font-medium">{author.stats.likes}</span>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{author.stats.likes} total likes across all works</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
|
||||
{/* Mobile actions */}
|
||||
{showActions && variant !== "minimal" && (
|
||||
<div className="flex md:hidden items-center gap-2 mt-2">
|
||||
{showFollowButton && (
|
||||
<Button
|
||||
variant={isFollowed ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={handleFollowClick}
|
||||
className="gap-1"
|
||||
>
|
||||
<Users className="h-4 w-4" />
|
||||
{isFollowed ? "Following" : "Follow"}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{canEdit && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleEditClick}
|
||||
className="gap-1"
|
||||
>
|
||||
<Edit className="h-4 w-4" />
|
||||
Edit
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{showShareButton && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="h-8 w-8"
|
||||
onClick={handleShareClick}
|
||||
>
|
||||
<Share2 className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tabs navigation for detailed view */}
|
||||
{variant === "detailed" && (
|
||||
<Tabs
|
||||
defaultValue="overview"
|
||||
className="w-full"
|
||||
onValueChange={setActiveTab}
|
||||
>
|
||||
<TabsList className="mb-4">
|
||||
<TabsTrigger value="overview">Overview</TabsTrigger>
|
||||
{timelineEvents && timelineEvents.length > 0 && (
|
||||
<TabsTrigger value="timeline">Timeline</TabsTrigger>
|
||||
)}
|
||||
{author.influences && author.influences.length > 0 && (
|
||||
<TabsTrigger value="influences">Influences</TabsTrigger>
|
||||
)}
|
||||
<TabsTrigger value="works">Works</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="overview" className="space-y-4">
|
||||
{/* Author bio */}
|
||||
{displayBio && (
|
||||
<div className="space-y-2 max-w-3xl">
|
||||
<p className="text-base text-muted-foreground leading-relaxed">
|
||||
{displayBio}
|
||||
</p>
|
||||
|
||||
{hasLongBio && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 px-2 -ml-2"
|
||||
onClick={toggleBio}
|
||||
>
|
||||
{isBioExpanded ? (
|
||||
<span className="flex items-center gap-1">
|
||||
Show less <ChevronUp className="h-3.5 w-3.5" />
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex items-center gap-1">
|
||||
Read more <ChevronDown className="h-3.5 w-3.5" />
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Quick facts section */}
|
||||
{variant === "detailed" && (
|
||||
<div className="bg-muted/30 rounded-lg p-4 max-w-3xl">
|
||||
<h3 className="text-sm font-medium mb-3 flex items-center gap-2">
|
||||
<Info className="h-4 w-4" />
|
||||
Quick Facts
|
||||
</h3>
|
||||
<div className="grid sm:grid-cols-2 gap-4">
|
||||
{author.nationality && (
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">Nationality</p>
|
||||
<p className="text-sm font-medium">{author.nationality}</p>
|
||||
</div>
|
||||
)}
|
||||
{author.birthYear && (
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">Born</p>
|
||||
<p className="text-sm font-medium">{author.birthYear}{author.location ? `, ${author.location}` : ''}</p>
|
||||
</div>
|
||||
)}
|
||||
{author.deathYear && (
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">Died</p>
|
||||
<p className="text-sm font-medium">{author.deathYear}</p>
|
||||
</div>
|
||||
)}
|
||||
{author.era && (
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">Literary Era</p>
|
||||
<p className="text-sm font-medium">{author.era}</p>
|
||||
</div>
|
||||
)}
|
||||
{author.genres && author.genres.length > 0 && (
|
||||
<div className="sm:col-span-2">
|
||||
<p className="text-xs text-muted-foreground">Notable Genres</p>
|
||||
<div className="flex flex-wrap gap-1 mt-1">
|
||||
{author.genres.map((genre, i) => (
|
||||
<Badge key={i} variant="secondary" className="text-xs">
|
||||
{genre}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
{timelineEvents && timelineEvents.length > 0 && (
|
||||
<TabsContent value="timeline">
|
||||
<div className="py-2">
|
||||
{timelineEvents.length > 0 ? (
|
||||
<div className="relative ml-4">
|
||||
{/* Timeline line */}
|
||||
<div className="absolute left-4 top-0 bottom-0 w-0.5 bg-muted-foreground/20"></div>
|
||||
|
||||
{/* Events */}
|
||||
<div className="space-y-6">
|
||||
{timelineEvents
|
||||
.sort((a, b) => a.year - b.year)
|
||||
.map((event) => (
|
||||
<div key={event.id} className="relative flex items-start gap-6 ml-4">
|
||||
{/* Year indicator */}
|
||||
<div className="flex-shrink-0 w-8 h-8 rounded-full bg-background border border-border flex items-center justify-center z-10 -ml-8">
|
||||
<Calendar className="h-4 w-4 text-muted-foreground" />
|
||||
</div>
|
||||
|
||||
{/* Event details */}
|
||||
<div className="flex-1 bg-muted/20 p-4 rounded-lg">
|
||||
<div className="flex justify-between items-start mb-1">
|
||||
<h3 className="font-medium">
|
||||
{event.title}
|
||||
</h3>
|
||||
<span className="text-sm text-muted-foreground font-mono">
|
||||
{event.year}
|
||||
</span>
|
||||
</div>
|
||||
{event.description && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{event.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8 text-muted-foreground">
|
||||
No timeline events available.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
)}
|
||||
|
||||
{author.influences && author.influences.length > 0 && (
|
||||
<TabsContent value="influences">
|
||||
<div className="py-2 space-y-4">
|
||||
<h3 className="text-lg font-medium">Literary Influences</h3>
|
||||
<p className="text-muted-foreground max-w-3xl">
|
||||
The authors and thinkers who shaped {author.name}'s writing style,
|
||||
philosophy, and literary approach.
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{author.influences.map((influence, i) => (
|
||||
<Badge
|
||||
key={i}
|
||||
variant="outline"
|
||||
className="px-3 py-1 text-sm"
|
||||
>
|
||||
{influence}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
)}
|
||||
|
||||
<TabsContent value="works">
|
||||
<div className="py-2 text-center text-muted-foreground">
|
||||
Works will be loaded here. This tab serves as a placeholder for the works listing.
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AuthorHeader;
|
||||
Loading…
Reference in New Issue
Block a user