Skip to content

(SP: 2) [Frontend] Admin Blog: Authors and Categories management #388

@LesiaUKR

Description

@LesiaUKR

Goal

Admin can create and edit blog authors (with multilingual profile fields and Cloudinary profile photo) and manage blog categories (with 3 locale translations and display order).

Scope

Author routes

  • app/[locale]/admin/blog/authors/page.tsx — author list table (server component, direct DB query)
  • app/[locale]/admin/blog/authors/new/page.tsx — create author
  • app/[locale]/admin/blog/authors/[id]/page.tsx — edit author (notFound if missing)

Author components

  1. BlogAuthorListTable.tsx — desktop table + mobile cards: photo thumbnail (32x32), name (EN), jobTitle (EN), post count, Edit link, Delete button (disabled with tooltip if postCount > 0)

  2. BlogAuthorForm.tsx — create/edit mode:

    • Profile photo upload (reuses BlogImageUpload, existing /api/admin/blog/images route)
    • Locale tabs (EN/UK/PL) for: name (required), jobTitle, company, city, bio (textarea)
    • Slug: auto-generated from EN name, positioned below translations
    • Social media editor: dynamic rows with platform dropdown (lowercase values: github, linkedin, x, website, youtube, instagram, facebook, behance, dribbble) + URL input
    • Dirty tracking in edit mode (JSON snapshot comparison), context-aware submit button tooltips
    • Submit: POST /api/admin/blog/authors (create) or PUT /api/admin/blog/authors/[id] (edit)

Author API routes

  • app/api/admin/blog/authors/route.ts — POST create (existing, extended with optional fields)
  • app/api/admin/blog/authors/[id]/route.ts — PUT update, DELETE (409 if author has posts)

No GET endpoints — list/detail data fetched by server components via direct DB queries.
Image upload: separate Cloudinary upload via existing /api/admin/blog/images route (not bundled in POST/PUT).
Delete guard: explicit postCount check (DB FK uses onDelete: 'set null', business rule blocks deletion).

Category routes

  • app/[locale]/admin/blog/categories/page.tsx — inline CRUD (no separate create/edit pages)

Category component

BlogCategoryManager.tsx — single-page inline management:

  • List all categories ordered by displayOrder, showing title (EN), description (EN), post count
  • Move up/down buttons to reorder (swap displayOrder values between adjacent items)
  • Inline create: slug (auto-gen from EN title) + 3 locale titles + descriptions → POST
  • Inline edit: single-open expand pattern, slug + locale tabs (title + description) → PUT
  • Delete: disabled if postCount > 0 (tooltip shows count), confirm dialog

Category API routes

  • app/api/admin/blog/categories/route.ts — POST create (existing, extended with optional description)
  • app/api/admin/blog/categories/[id]/route.ts — PUT update, DELETE (409 if category has posts)
  • app/api/admin/blog/categories/reorder/route.ts — POST swap (body: { id1, id2 })

Query layer — db/queries/blog/admin-blog.ts

9 new functions added:

  • getAdminBlogAuthorsFull() — list with EN name, imageUrl, jobTitle, postCount (via subquery)
  • getAdminBlogAuthorById(id) — full author with all locale translations + socialMedia cast
  • updateBlogAuthor(id, input) — update base fields + upsert translations (onConflictDoUpdate)
  • deleteBlogAuthor(id) — throws AUTHOR_HAS_POSTS if posts reference this author
  • getAdminBlogCategoriesFull() — list with all translations (for inline edit) + postCount
  • getAdminBlogCategoryById(id) — single with all locale translations
  • updateBlogCategory(id, input) — update slug + upsert translations
  • deleteBlogCategory(id) — throws CATEGORY_HAS_POSTS if posts use this category
  • swapBlogCategoryOrder(id1, id2) — fetch both displayOrder values, swap with two UPDATEs

Extended existing functions:

  • CreateBlogAuthorInput — added optional imageUrl, imagePublicId, socialMedia, bio, jobTitle, company, city
  • CreateBlogCategoryInput — added optional description per locale
  • Both backward compatible with existing inline forms (optional fields)

Validation — lib/validation/admin-blog.ts

New schemas: updateBlogAuthorSchema, updateBlogCategorySchema, swapCategoryOrderSchema
Extended: createBlogAuthorSchema (optional image/social/bio fields), createBlogCategorySchema (optional description)
New: socialMediaEntrySchema{ platform: string.min(1), url: string.url() }

Full file list

New files (8):

  • app/[locale]/admin/blog/authors/new/page.tsx — create author page
  • app/[locale]/admin/blog/authors/[id]/page.tsx — edit author page
  • app/api/admin/blog/authors/[id]/route.ts — PUT + DELETE
  • app/api/admin/blog/categories/[id]/route.ts — PUT + DELETE
  • app/api/admin/blog/categories/reorder/route.ts — POST swap
  • components/admin/blog/BlogAuthorListTable.tsx
  • components/admin/blog/BlogAuthorForm.tsx
  • components/admin/blog/BlogCategoryManager.tsx

Modified files (4):

  • app/[locale]/admin/blog/authors/page.tsx — replaced stub with real page
  • app/[locale]/admin/blog/categories/page.tsx — replaced stub with real page
  • db/queries/blog/admin-blog.ts — 9 new functions, extended 2 create inputs
  • lib/validation/admin-blog.ts — 3 new schemas, extended 2 existing

Verification (all passed):

  1. Create author with photo + 3 locales + social links → appears in list and post dropdown
  2. Edit author: change name/photo/social → changes reflected
  3. Delete author with 0 posts → succeeds; with posts → button disabled, tooltip shown
  4. Create category with 3 locale titles + descriptions → appears in list and post checkboxes
  5. Edit category inline → changes reflected
  6. Reorder categories (move up/down) → order changes
  7. Delete category with 0 posts → succeeds; with posts → button disabled
  8. Inline creation from post form still works (backward compatible)
  9. TypeScript compilation: zero new errors

Depends on: Issue #4

Expected impact

Full blog content management from admin panel. No Sanity required for any blog operation.

Out of scope

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions