This implementation addresses the requirements to:
- Create a proper admin dashboard with meaningful statistics and subscriber information
- Add the ability to pin a single post that displays in the public sidebar
- Show the pinned post in the public sidebar or an empty state if none exists
- Adds
is_pinnedboolean column to theblog_poststable - Creates an index on
is_pinnedfor better query performance - Safe to run multiple times (checks if column exists before adding)
- Run with:
npm run db:migrate-pinned
New Functions Added:
getPinnedPost(): Fetches the currently pinned post (excludes deleted posts)pinPost(id): Pins a post and automatically unpins any previously pinned post (atomic transaction)unpinPost(id): Unpins a specific postgetAllSubscribers(): Retrieves all subscribers (both active and inactive) for admin dashboard
Interface Updates:
- Added
is_pinned?: booleantoBlogPostinterface - Updated table creation to include
is_pinnedcolumn with defaultfalse
- Added
is_pinned?: booleanfield to BlogPost type
- Fetches the currently pinned post
- Returns 404 if no post is pinned
- Public endpoint (no authentication required)
- POST: Pins a post (unpins any other pinned post automatically)
- DELETE: Unpins a post
- Requires authentication
- Validates post exists and is not deleted before pinning
- Fetches all subscribers with statistics
- Returns: subscribers array, total count, active count, inactive count
- Requires authentication
Completely redesigned to show a proper dashboard overview:
Statistics Cards:
- Total Posts: Shows all posts in the system
- Published Posts: Active, non-deleted posts
- Deleted Posts: Soft-deleted posts count
- Your Posts: Posts created by the current admin user
Subscriber Statistics:
- Total Subscribers: All subscribers
- Active Subscribers: Currently subscribed users
- Inactive Subscribers: Unsubscribed users
Subscribers List:
- Displays recent subscribers (up to 10)
- Shows email, status (active/inactive), and subscription date
- Empty state message when no subscribers exist
Quick Actions:
- Create New Post button
- Manage Posts button
- Manage Admins button
Key Improvements:
- Removed duplicate "Add Admin" functionality (this should be on a separate admin management page)
- Removed duplicate post management (moved to dedicated Posts page)
- Clean, focused dashboard showing key metrics at a glance
Added pinning functionality:
New Features:
- Pin/Unpin button for each post (visible for all non-deleted posts)
- Visual indicator showing which post is currently pinned (yellow badge with pin icon)
- Pin button changes color based on pin state:
- Gray when not pinned
- Yellow when pinned
- Filled icon when pinned
- Automatic unpinning of previous post when a new one is pinned
- Loading state while pin operation is in progress
Pin Handler:
handleTogglePin(): Toggles pin state with proper error handling- Updates local state optimistically for better UX
- Shows error messages if operation fails
Updated PinnedSection component:
New Behavior:
- Fetches pinned post from
/api/posts/pinnedon component mount - Shows loading state (returns null) while fetching
- Shows nothing (returns null) if no post is pinned - This is the empty state
- When pinned post exists:
- Displays post image (if available)
- Shows post title (truncated to 2 lines)
- Makes entire card clickable to navigate to post
- Maintains collapsed state behavior (shows minimal version)
Type Safety:
- Properly typed pinned post state
- Fixed TypeScript errors
- Removed unused imports
- Added
db:migrate-pinnedscript for running the migration
- Admin goes to
/admin/posts - Clicks pin button on desired post
- API call to
POST /api/posts/[id]/pin - Database transaction:
- Unpins all currently pinned posts
- Pins the selected post
- UI updates to show new pin state
- Public sidebar automatically shows the pinned post
- Admin clicks pin button on currently pinned post
- API call to
DELETE /api/posts/[id]/pin - Database updates post to
is_pinned = false - UI updates to show unpinned state
- Public sidebar shows nothing (empty state)
- On load, dashboard fetches:
- All posts via
/api/posts - All subscribers via
/api/admin/subscribers
- All posts via
- Calculates statistics client-side
- Displays overview cards and subscriber list
- Provides quick action buttons for common tasks
To apply the database schema changes:
npm run db:migrate-pinnedThis will add the is_pinned column and create the necessary index.
- Run migration script to add
is_pinnedcolumn - Login to admin dashboard
- Verify dashboard shows correct statistics
- Verify subscriber list displays (if any subscribers exist)
- Go to admin posts page
- Pin a post - verify it shows as pinned
- Check public sidebar - verify pinned post appears
- Pin a different post - verify first one is unpinned automatically
- Unpin the post - verify sidebar shows nothing
- Try to pin a deleted post - verify it fails gracefully
scripts/migrate-add-is-pinned.js- Database migration scriptsrc/app/api/admin/subscribers/route.ts- Subscribers API for adminsrc/app/api/posts/[id]/pin/route.ts- Pin/unpin post APIsrc/app/api/posts/pinned/route.ts- Get pinned post APIIMPLEMENTATION_PINNED_POSTS_DASHBOARD.md- This documentation
src/lib/database.ts- Added pin functions and subscribers gettersrc/types/blog.ts- Added is_pinned fieldsrc/app/admin/dashboard/page.tsx- Complete redesignsrc/app/admin/posts/page.tsx- Added pin functionalitysrc/components/sidebar.tsx- Dynamic pinned post loadingpackage.json- Added migration script
-
Only One Pinned Post: The implementation ensures only one post can be pinned at a time using a database transaction that unpins all posts before pinning the selected one.
-
Empty State: When no post is pinned, the sidebar component returns
null, effectively showing nothing. This is the empty state as requested. -
Soft Delete Compatibility: Pinned posts are automatically filtered to exclude deleted posts. Attempting to pin a deleted post returns an error.
-
Performance: The pinned post is fetched once on sidebar mount, keeping the public-facing page fast and efficient.
-
Admin Dashboard: The new dashboard focuses on high-level metrics and recent activity, with quick access to detailed management pages.
- All pin/unpin operations require authentication
- Admin API routes verify user is logged in
- Public pinned post endpoint is intentionally public (read-only)
- No sensitive subscriber information exposed through public endpoints