import { useStore } from '@nanostores/react'
import { $posts } from '../stores/posts'
import { $analytics } from '../stores/analytics'
import { Button } from '../components/ui'
import { BreakdownList, EmptyState, HomePageSkeleton, PageHeader } from '../components/shared'
import { Icons, getReferrerIcon } from '../components/shared/Icons'
function formatChange(change: number): { text: string; positive: boolean } {
const sign = change >= 0 ? '+' : ''
return {
text: `${sign}${change.toFixed(1)}%`,
positive: change >= 0,
}
}
function formatRelativeTime(dateStr: string) {
const date = new Date(dateStr)
const now = new Date()
const diffMs = now.getTime() - date.getTime()
const diffMins = Math.floor(diffMs / 60000)
const diffHours = Math.floor(diffMs / 3600000)
const diffDays = Math.floor(diffMs / 86400000)
if (diffMins < 1) return 'Just now'
if (diffMins < 60) return `${diffMins}m ago`
if (diffHours < 24) return `${diffHours}h ago`
if (diffDays < 7) return `${diffDays}d ago`
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })
}
function formatDate(dateStr: string) {
return new Date(dateStr).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
})
}
function formatViews(views: number) {
if (views >= 1000) return `${(views / 1000).toFixed(1)}k`
return views.toString()
}
export default function HomePage() {
const { data: posts, error: postsError } = useStore($posts)
const { data: analytics } = useStore($analytics)
if (!posts) return
if (postsError) return
const drafts = posts
.filter(p => p.draft)
.sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime())
.slice(0, 3)
const published = posts
.filter(p => !p.draft)
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
.slice(0, 5)
const publishedCount = posts.filter(p => !p.draft).length
const draftCount = posts.filter(p => p.draft).length
const getPostViews = (slug: string): number => {
if (!analytics?.top_pages) return 0
const page = analytics.top_pages.find(p => p.path === `/posts/${slug}`)
return page?.views || 0
}
const change = analytics ? formatChange(analytics.views_change) : null
if (posts.length === 0) {
return (
Write Your First Post}
/>
)
}
return (
{/* Panel container - uses negative margins for full-bleed borders */}
{/* Stats row */}
{/* Vertical dividers at column boundaries */}
Views
{analytics?.total_views.toLocaleString() || '0'}
{change && (
{change.text} vs last period
)}
Visitors
{analytics?.unique_visitors.toLocaleString() || '0'}
Posts
{publishedCount}
{draftCount > 0 && (
{draftCount} draft{draftCount > 1 ? 's' : ''}
)}
{/* Full-bleed horizontal divider */}
{/* Content sections with vertical divider */}
{/* Vertical divider at exact center */}
{/* Left column: Posts */}
{/* Drafts */}
{drafts.length > 0 && (
)}
{/* Recent posts */}
{published.length > 0 && (
)}
{/* Right column: Referrers */}
Top Referrers
{analytics && analytics.top_referrers.length > 0 ? (
{
const label = r.referrer || 'Direct'
return {
label,
value: r.views,
percentage: (r.views / analytics.total_views) * 100,
Icon: getReferrerIcon(label),
}
})}
limit={5}
/>
) : (
No referrer data yet
)}
)
}