mirror of
https://github.com/SamyRai/tercul-frontend.git
synced 2025-12-27 04: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 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 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 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 |
|
| 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
|
## Next Implementation Priorities
|
||||||
|
|
||||||
1. 🔄 **In Progress**:
|
1. 🔄 **In Progress**:
|
||||||
- Author Header component
|
|
||||||
- Annotation Editor component
|
- Annotation Editor component
|
||||||
|
|
||||||
2. ⬜ **Planned Next**:
|
2. ⬜ **Planned Next**:
|
||||||
|
|||||||
111
TODO.md
111
TODO.md
@ -1,39 +1,48 @@
|
|||||||
# Tercul Platform Development Todo List
|
# 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
|
## Dashboard & Editorial Features
|
||||||
|
|
||||||
### UI/UX Improvements
|
### 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
|
- [ ] Implement proper loading states with skeletons for all data fetching
|
||||||
- [ ] Add proper error states and error boundaries
|
- [ ] 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
|
### Dashboard Pages to Implement
|
||||||
- [x] Dashboard Overview (main statistics)
|
- [x] Dashboard Overview (main statistics)
|
||||||
- [x] Blog Management (list view)
|
- [x] Blog Management (list view)
|
||||||
- [ ] Blog Post Editor (create/edit)
|
- [x] Blog Post Editor (create/edit)
|
||||||
- [ ] Rich text editor with proper formatting toolbar
|
- [x] Rich text editor with proper formatting toolbar
|
||||||
- [ ] Image upload functionality
|
- [x] Image upload functionality
|
||||||
- [ ] SEO metadata editor
|
- [ ] SEO metadata editor
|
||||||
- [ ] Scheduling publication feature
|
- [ ] Scheduling publication feature
|
||||||
- [ ] Works Management
|
- [x] Works Management
|
||||||
- [ ] Works listing with filtering and sorting
|
- [x] Works listing with filtering and sorting
|
||||||
- [ ] Work creation/editing form
|
- [x] Work creation/editing form
|
||||||
- [ ] Bulk actions (delete, publish, unpublish)
|
- [ ] Bulk actions (delete, publish, unpublish)
|
||||||
- [ ] Authors Management
|
- [ ] Authors Management
|
||||||
- [ ] Authors listing with search and filters
|
- [x] Authors listing with search and filters
|
||||||
- [ ] Author profile editor with timeline management
|
- [ ] Author profile editor with timeline management
|
||||||
- [ ] Author merge functionality for duplicate profiles
|
- [ ] Author merge functionality for duplicate profiles
|
||||||
- [ ] Translations Management
|
- [ ] Translations Management
|
||||||
- [ ] Translations listing with filtering
|
- [x] Translations listing with filtering
|
||||||
- [ ] Translation editor with parallel view
|
- [ ] Translation editor with parallel view
|
||||||
- [ ] Version control/history tracking
|
- [ ] Version control/history tracking
|
||||||
- [ ] Collections Management
|
- [ ] Collections Management
|
||||||
- [ ] Collections listing and editor
|
- [x] Collections listing and editor
|
||||||
- [ ] Drag-and-drop collection organization
|
- [ ] Drag-and-drop collection organization
|
||||||
- [ ] Featured collections manager
|
- [ ] Featured collections manager
|
||||||
- [ ] Tags Management
|
- [ ] Tags Management
|
||||||
- [ ] Tag creation, editing, merging
|
- [x] Tag creation, editing, merging
|
||||||
- [ ] Tag organization by category
|
- [ ] Tag organization by category
|
||||||
- [ ] Tag usage statistics
|
- [ ] Tag usage statistics
|
||||||
- [ ] Annotations Management
|
- [ ] Annotations Management
|
||||||
@ -41,8 +50,8 @@
|
|||||||
- [ ] Annotations editor with context view
|
- [ ] Annotations editor with context view
|
||||||
- [ ] Bulk moderation actions
|
- [ ] Bulk moderation actions
|
||||||
- [ ] Comments Management
|
- [ ] Comments Management
|
||||||
- [ ] Comments listing with moderation tools
|
- [x] Comments listing with moderation tools
|
||||||
- [ ] Comment reply interface
|
- [x] Comment reply interface
|
||||||
- [ ] Spam detection and filtering
|
- [ ] Spam detection and filtering
|
||||||
- [ ] Analysis Results Management
|
- [ ] Analysis Results Management
|
||||||
- [ ] AI analysis request interface
|
- [ ] AI analysis request interface
|
||||||
@ -59,79 +68,79 @@
|
|||||||
|
|
||||||
### Backend API Implementation
|
### Backend API Implementation
|
||||||
- [x] Stats endpoints for dashboard
|
- [x] Stats endpoints for dashboard
|
||||||
- [ ] CRUD endpoints for all dashboard entities
|
- [x] CRUD endpoints for all dashboard entities
|
||||||
- [ ] Proper pagination, filtering, and sorting for list endpoints
|
- [x] Proper pagination, filtering, and sorting for list endpoints
|
||||||
- [ ] Authentication middleware with role-based access control
|
- [x] Authentication middleware with role-based access control
|
||||||
- [ ] File upload endpoints for images and documents
|
- [ ] File upload endpoints for images and documents
|
||||||
- [ ] Batch operations for bulk actions
|
- [ ] Batch operations for bulk actions
|
||||||
- [ ] Activity logging system
|
- [ ] Activity logging system
|
||||||
- [ ] Webhook system for notifications
|
- [ ] Webhook system for notifications
|
||||||
|
|
||||||
### Feature Enhancements
|
### Feature Enhancements
|
||||||
- [ ] Dashboard Search
|
- [x] Dashboard Search
|
||||||
- [ ] Global search functionality across all content types
|
- [x] Global search functionality across all content types
|
||||||
- [ ] Advanced search interface with filters
|
- [x] Advanced search interface with filters
|
||||||
- [ ] Editorial Workflow
|
- [ ] Editorial Workflow
|
||||||
- [ ] Content approval workflow with draft/review/published states
|
- [x] Content approval workflow with draft/review/published states
|
||||||
- [ ] Editorial calendar with scheduled publications
|
- [ ] Editorial calendar with scheduled publications
|
||||||
- [ ] Assignment system for editors and contributors
|
- [ ] Assignment system for editors and contributors
|
||||||
- [ ] Notifications System
|
- [ ] Notifications System
|
||||||
- [ ] In-app notifications for editorial actions
|
- [ ] In-app notifications for editorial actions
|
||||||
- [ ] Email notifications for important events
|
- [ ] Email notifications for important events
|
||||||
- [ ] Notification preferences manager
|
- [ ] Notification preferences manager
|
||||||
- [ ] Analytics Dashboard
|
- [x] Analytics Dashboard
|
||||||
- [ ] Content performance metrics
|
- [x] Content performance metrics
|
||||||
- [ ] User engagement statistics
|
- [x] User engagement statistics
|
||||||
- [ ] Custom report builder
|
- [ ] Custom report builder
|
||||||
- [ ] Improved Toast System
|
- [x] Improved Toast System
|
||||||
- [ ] Toast queue management
|
- [x] Toast queue management
|
||||||
- [ ] Different toast types (success, error, warning, info)
|
- [x] Different toast types (success, error, warning, info)
|
||||||
- [ ] Action buttons in toasts
|
- [x] Action buttons in toasts
|
||||||
- [ ] AI Integration
|
- [ ] AI Integration
|
||||||
- [ ] Content analysis tools
|
- [x] Content analysis tools
|
||||||
- [ ] Text generation assistance
|
- [ ] Text generation assistance
|
||||||
- [ ] Translation assistance
|
- [ ] Translation assistance
|
||||||
- [ ] Content moderation helpers
|
- [ ] Content moderation helpers
|
||||||
|
|
||||||
## Reading Experience Improvements
|
## Reading Experience Improvements
|
||||||
|
|
||||||
- [ ] Enhanced Annotation System
|
- [x] Enhanced Annotation System
|
||||||
- [ ] Inline annotation creation
|
- [x] Inline annotation creation
|
||||||
- [ ] Better visualization of annotations
|
- [x] Better visualization of annotations
|
||||||
- [ ] Filtering annotations by type/author
|
- [x] Filtering annotations by type/author
|
||||||
- [ ] Reading Progress Tracking
|
- [x] Reading Progress Tracking
|
||||||
- [ ] Resume reading functionality
|
- [x] Resume reading functionality
|
||||||
- [ ] Reading statistics dashboard
|
- [x] Reading statistics dashboard
|
||||||
- [ ] Improved Translation Comparison
|
- [ ] Improved Translation Comparison
|
||||||
- [ ] Side-by-side view with synchronized scrolling
|
- [x] Side-by-side view with synchronized scrolling
|
||||||
- [ ] Highlight differences between translations
|
- [ ] Highlight differences between translations
|
||||||
- [ ] Translation quality ratings
|
- [ ] Translation quality ratings
|
||||||
- [ ] Social Features
|
- [ ] Social Features
|
||||||
- [ ] Reading groups
|
- [ ] Reading groups
|
||||||
- [ ] Shared annotations
|
- [x] Shared annotations
|
||||||
- [ ] Discussion threads
|
- [x] Discussion threads
|
||||||
|
|
||||||
## Infrastructure and Performance
|
## Infrastructure and Performance
|
||||||
|
|
||||||
- [ ] Implement database migrations system
|
- [ ] Implement database migrations system
|
||||||
- [ ] Optimize API endpoints for performance
|
- [x] Optimize API endpoints for performance
|
||||||
- [ ] Add caching layer for frequently accessed data
|
- [ ] 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
|
- [ ] 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
|
## Technical Debt
|
||||||
|
|
||||||
- [ ] Refactor duplicated code into shared components
|
- [x] Refactor duplicated code into shared components
|
||||||
- [ ] Improve type safety throughout the application
|
- [x] Improve type safety throughout the application
|
||||||
- [ ] Add comprehensive documentation for API endpoints
|
- [ ] Add comprehensive documentation for API endpoints
|
||||||
- [ ] Standardize error handling across the application
|
- [x] Standardize error handling across the application
|
||||||
- [ ] Implement proper form validation throughout
|
- [x] Implement proper form validation throughout
|
||||||
|
|
||||||
## Priority Next Steps
|
## Priority Next Steps
|
||||||
|
|
||||||
1. Complete the Blog Post Editor with rich text functionality
|
1. Complete the Annotation Editor component
|
||||||
2. Implement Works Management interface
|
2. Implement Author Editor component for the Authors Management dashboard
|
||||||
3. Build Authors Management dashboard
|
3. Build Blog Preview component for blog post publishing workflow
|
||||||
4. Create Comments moderation system
|
4. Create Comparison View for translation comparison
|
||||||
5. Develop Annotations management interface
|
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