Skip to Content
📚 MyStoryFlow Docs — Your guide to preserving family stories

F017 - Author Management Dashboard

Objective

Create a comprehensive dashboard where authors can manage their manuscripts, view analysis history, track progress, and access all features in one place.

Quick Implementation

Using MyStoryFlow Components

  • Dashboard layout from @mystoryflow/ui
  • DataGrid for manuscript list from @mystoryflow/ui
  • Card components for stats from @mystoryflow/ui
  • Navigation sidebar from @mystoryflow/ui
  • Chart components for analytics from @mystoryflow/ui

New Requirements

  • Manuscript management logic
  • Dashboard statistics
  • Quick action buttons
  • Activity timeline

MVP Implementation

1. Database Queries

-- Dashboard statistics view (in analyzer schema) CREATE VIEW analyzer.author_dashboard_stats AS SELECT u.id as user_id, COUNT(DISTINCT m.id) as total_manuscripts, COUNT(DISTINCT ma.id) as total_analyses, AVG(ma.overall_score) as average_score, MAX(m.created_at) as last_upload_date, COUNT(DISTINCT CASE WHEN m.created_at > NOW() - INTERVAL '30 days' THEN m.id END) as manuscripts_last_30_days FROM auth.users u LEFT JOIN analyzer.manuscripts m ON m.user_id = u.id LEFT JOIN analyzer.manuscript_analyses ma ON ma.manuscript_id = m.id GROUP BY u.id; -- Recent activity CREATE VIEW analyzer.author_recent_activity AS SELECT 'upload' as activity_type, m.id as entity_id, m.title as entity_name, m.created_at as activity_date, m.user_id FROM analyzer.manuscripts m UNION ALL SELECT 'analysis' as activity_type, ma.manuscript_id as entity_id, m.title as entity_name, ma.created_at as activity_date, m.user_id FROM analyzer.manuscript_analyses ma JOIN analyzer.manuscripts m ON m.id = ma.manuscript_id ORDER BY activity_date DESC;

2. Dashboard Layout Component

// apps/analyzer-app/src/app/dashboard/page.tsx import { Suspense } from 'react' import { DashboardShell, DashboardHeader, DashboardContent } from '@mystoryflow/ui/dashboard' import { getSupabaseBrowserClient } from '@mystoryflow/database' import { ManuscriptStats, RecentManuscripts, ActivityTimeline, QuickActions } from '@/components/dashboard' export default async function DashboardPage() { const supabase = getSupabaseBrowserClient() const { data: { user } } = await supabase.auth.getUser() if (!user) return null // Fetch dashboard data const [stats, manuscripts, activity] = await Promise.all([ getDashboardStats(user.id), getRecentManuscripts(user.id), getRecentActivity(user.id) ]) return ( <DashboardShell> <DashboardHeader heading="Author Dashboard" text="Manage your manuscripts and track your writing progress." /> <DashboardContent> <div className="grid gap-6"> {/* Quick Actions */} <QuickActions /> {/* Statistics Cards */} <ManuscriptStats stats={stats} /> {/* Recent Manuscripts */} <Suspense fallback={<div>Loading manuscripts...</div>}> <RecentManuscripts manuscripts={manuscripts} /> </Suspense> {/* Activity Timeline */} <ActivityTimeline activities={activity} /> </div> </DashboardContent> </DashboardShell> ) } async function getDashboardStats(userId: string) { const supabase = getSupabaseBrowserClient() const { data } = await supabase .from('analyzer.author_dashboard_stats') .select('*') .eq('user_id', userId) .single() return data } async function getRecentManuscripts(userId: string) { const supabase = getSupabaseBrowserClient() const { data } = await supabase .from('analyzer.manuscripts') .select(` *, manuscript_analyses ( overall_score, created_at ), genre_detections ( primary_genre ) `) .eq('user_id', userId) .order('created_at', { ascending: false }) .limit(5) return data } async function getRecentActivity(userId: string) { const supabase = getSupabaseBrowserClient() const { data } = await supabase .from('analyzer.author_recent_activity') .select('*') .eq('user_id', userId) .limit(10) return data }

3. Dashboard Components

// apps/analyzer-app/src/components/dashboard/ManuscriptStats.tsx import { Card } from '@mystoryflow/ui' import { FileText, TrendingUp, Calendar, BarChart3 } from 'lucide-react' interface StatsProps { stats: { total_manuscripts: number total_analyses: number average_score: number manuscripts_last_30_days: number } } export function ManuscriptStats({ stats }: StatsProps) { return ( <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4"> <Card className="p-6"> <div className="flex items-center gap-4"> <div className="p-3 bg-primary/10 rounded-lg"> <FileText className="h-6 w-6 text-primary" /> </div> <div> <p className="text-sm text-muted-foreground"> Total Manuscripts </p> <p className="text-2xl font-bold"> {stats.total_manuscripts} </p> </div> </div> </Card> <Card className="p-6"> <div className="flex items-center gap-4"> <div className="p-3 bg-blue-500/10 rounded-lg"> <BarChart3 className="h-6 w-6 text-blue-500" /> </div> <div> <p className="text-sm text-muted-foreground"> Analyses Run </p> <p className="text-2xl font-bold"> {stats.total_analyses} </p> </div> </div> </Card> <Card className="p-6"> <div className="flex items-center gap-4"> <div className="p-3 bg-green-500/10 rounded-lg"> <TrendingUp className="h-6 w-6 text-green-500" /> </div> <div> <p className="text-sm text-muted-foreground"> Average Score </p> <p className="text-2xl font-bold"> {Math.round(stats.average_score)}% </p> </div> </div> </Card> <Card className="p-6"> <div className="flex items-center gap-4"> <div className="p-3 bg-purple-500/10 rounded-lg"> <Calendar className="h-6 w-6 text-purple-500" /> </div> <div> <p className="text-sm text-muted-foreground"> This Month </p> <p className="text-2xl font-bold"> {stats.manuscripts_last_30_days} </p> </div> </div> </Card> </div> ) } // apps/analyzer-app/src/components/dashboard/RecentManuscripts.tsx import { Card, DataGrid, Badge, Button } from '@mystoryflow/ui' import Link from 'next/link' import { Eye, Download, RefreshCw } from 'lucide-react' export function RecentManuscripts({ manuscripts }: { manuscripts: any[] }) { const columns = [ { header: 'Title', accessor: 'title', cell: (row: any) => ( <Link href={`/manuscripts/${row.id}`} className="font-medium hover:underline" > {row.title} </Link> ) }, { header: 'Genre', accessor: 'genre', cell: (row: any) => ( <Badge variant="secondary"> {row.genre_detections?.[0]?.primary_genre || 'Unknown'} </Badge> ) }, { header: 'Score', accessor: 'score', cell: (row: any) => { const score = row.manuscript_analyses?.[0]?.overall_score if (!score) return <span className="text-muted-foreground">-</span> const variant = score >= 85 ? 'success' : score >= 70 ? 'warning' : 'destructive' return <Badge variant={variant}>{score}%</Badge> } }, { header: 'Words', accessor: 'word_count', cell: (row: any) => row.word_count?.toLocaleString() || '-' }, { header: 'Actions', accessor: 'actions', cell: (row: any) => ( <div className="flex gap-2"> <Button size="sm" variant="ghost" asChild> <Link href={`/manuscripts/${row.id}/report`}> <Eye className="h-4 w-4" /> </Link> </Button> <Button size="sm" variant="ghost" asChild> <Link href={`/api/reports/${row.id}/export?format=pdf`}> <Download className="h-4 w-4" /> </Link> </Button> <Button size="sm" variant="ghost" asChild> <Link href={`/manuscripts/${row.id}/analyze`}> <RefreshCw className="h-4 w-4" /> </Link> </Button> </div> ) } ] return ( <Card> <div className="p-6"> <div className="flex items-center justify-between mb-4"> <h3 className="text-lg font-semibold">Recent Manuscripts</h3> <Button variant="outline" size="sm" asChild> <Link href="/manuscripts">View All</Link> </Button> </div> <DataGrid data={manuscripts} columns={columns} className="border-none" /> </div> </Card> ) } // apps/analyzer-app/src/components/dashboard/QuickActions.tsx import { Button } from '@mystoryflow/ui' import { Upload, FileText, Users, HelpCircle } from 'lucide-react' import Link from 'next/link' export function QuickActions() { return ( <div className="grid gap-4 md:grid-cols-4"> <Button size="lg" asChild> <Link href="/manuscripts/upload"> <Upload className="mr-2 h-5 w-5" /> Upload Manuscript </Link> </Button> <Button size="lg" variant="outline" asChild> <Link href="/manuscripts"> <FileText className="mr-2 h-5 w-5" /> My Manuscripts </Link> </Button> <Button size="lg" variant="outline" asChild> <Link href="/coaches"> <Users className="mr-2 h-5 w-5" /> Find a Coach </Link> </Button> <Button size="lg" variant="outline" asChild> <Link href="/help"> <HelpCircle className="mr-2 h-5 w-5" /> Help Center </Link> </Button> </div> ) } // apps/analyzer-app/src/components/dashboard/ActivityTimeline.tsx import { Card } from '@mystoryflow/ui' import { FileText, BarChart3 } from 'lucide-react' import { formatDistanceToNow } from 'date-fns' export function ActivityTimeline({ activities }: { activities: any[] }) { const getActivityIcon = (type: string) => { switch (type) { case 'upload': return <FileText className="h-4 w-4" /> case 'analysis': return <BarChart3 className="h-4 w-4" /> default: return null } } const getActivityText = (activity: any) => { switch (activity.activity_type) { case 'upload': return `Uploaded "${activity.entity_name}"` case 'analysis': return `Analyzed "${activity.entity_name}"` default: return activity.entity_name } } return ( <Card className="p-6"> <h3 className="text-lg font-semibold mb-4">Recent Activity</h3> <div className="space-y-4"> {activities.map((activity, index) => ( <div key={index} className="flex gap-4"> <div className="flex-shrink-0 w-8 h-8 bg-muted rounded-full flex items-center justify-center"> {getActivityIcon(activity.activity_type)} </div> <div className="flex-1"> <p className="text-sm">{getActivityText(activity)}</p> <p className="text-xs text-muted-foreground"> {formatDistanceToNow(new Date(activity.activity_date), { addSuffix: true })} </p> </div> </div> ))} {activities.length === 0 && ( <p className="text-sm text-muted-foreground text-center py-8"> No activity yet. Upload your first manuscript to get started! </p> )} </div> </Card> ) }

4. Navigation Update

// apps/analyzer-app/src/components/navigation/sidebar.tsx import { Home, FileText, Upload, BarChart3, Users, Settings, HelpCircle } from 'lucide-react' export const dashboardNav = [ { title: 'Dashboard', href: '/dashboard', icon: Home }, { title: 'My Manuscripts', href: '/manuscripts', icon: FileText }, { title: 'Upload New', href: '/manuscripts/upload', icon: Upload }, { title: 'Analytics', href: '/analytics', icon: BarChart3 }, { title: 'Find Coaches', href: '/coaches', icon: Users }, { title: 'Settings', href: '/settings', icon: Settings }, { title: 'Help', href: '/help', icon: HelpCircle } ]

MVP Acceptance Criteria

  • Dashboard with key statistics
  • Recent manuscripts list with actions
  • Quick action buttons
  • Activity timeline
  • Mobile-responsive layout
  • Real-time data updates
  • Navigation sidebar
  • Performance optimized queries

Post-MVP Enhancements

  • Writing goal tracking
  • Progress charts over time
  • Manuscript comparison view
  • Export analytics
  • Custom dashboard widgets
  • Writing streak tracking
  • Community feed integration
  • AI writing tips widget

Implementation Time

  • Development: 1.5 days
  • Testing: 0.5 days
  • Total: 2 days

Dependencies

  • User authentication system
  • Manuscript management system
  • Analysis data availability

Next Feature

After completion, update 00_IMPLEMENTATION_ROADMAP.md with all documentation links.