fix(admin): redirect to edit page after creating blog post
CI/CD / CI · API (dotnet build + test) (push) Successful in 41s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 29s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m3s
CI/CD / CI · Admin Web (tsc) (push) Successful in 35s
CI/CD / CI · Website (tsc) (push) Successful in 43s
CI/CD / CI · Koja (tsc) (push) Successful in 48s
CI/CD / Deploy · all services (push) Failing after 2m2s

Root cause: after successful creation the form stayed on /blog/new.
User couldn't tell it worked, clicked Save again, the second attempt
hit the unique slug constraint and showed an error — making it look
like creation was broken.

Fix: adminPost is now typed, onSuccess redirects to /blog/{id} on new
posts so the user lands on the edit page immediately.

Also fixes commentCount being undefined in the list (MapPost now
includes comment count via eager-loaded Comments).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-01 08:24:57 +03:30
parent 9f002433c7
commit aea1d20fdc
2 changed files with 13 additions and 4 deletions
@@ -15,7 +15,9 @@ public class AdminWebsiteService(AppDbContext db) : IAdminWebsiteService
var q = db.WebsiteBlogPosts.AsQueryable(); var q = db.WebsiteBlogPosts.AsQueryable();
if (published.HasValue) q = q.Where(p => p.IsPublished == published.Value); if (published.HasValue) q = q.Where(p => p.IsPublished == published.Value);
var total = await q.CountAsync(ct); var total = await q.CountAsync(ct);
var posts = await q.OrderByDescending(p => p.CreatedAt) var posts = await q
.Include(p => p.Comments)
.OrderByDescending(p => p.CreatedAt)
.Skip((page - 1) * limit).Take(limit).ToListAsync(ct); .Skip((page - 1) * limit).Take(limit).ToListAsync(ct);
return new { Posts = posts.Select(MapPost), Total = total, Page = page, Limit = limit }; return new { Posts = posts.Select(MapPost), Total = total, Page = page, Limit = limit };
} }
@@ -162,5 +164,6 @@ public class AdminWebsiteService(AppDbContext db) : IAdminWebsiteService
p.Id, p.Slug, p.TitleFa, p.TitleEn, p.ExcerptFa, p.ExcerptEn, p.Id, p.Slug, p.TitleFa, p.TitleEn, p.ExcerptFa, p.ExcerptEn,
p.ContentFa, p.ContentEn, p.CategoryFa, p.CategoryEn, p.Author, p.ContentFa, p.ContentEn, p.CategoryFa, p.CategoryEn, p.Author,
p.TagsJson, p.CoverImage, p.IsPublished, p.PublishedAt, p.ViewCount, p.CreatedAt, p.TagsJson, p.CoverImage, p.IsPublished, p.PublishedAt, p.ViewCount, p.CreatedAt,
CommentCount = p.Comments?.Count ?? 0,
}; };
} }
@@ -3,6 +3,7 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useRouter } from "@/i18n/routing";
import { adminDelete, adminGet, adminPatch, adminPost, adminPut } from "@/lib/api/admin-client"; import { adminDelete, adminGet, adminPatch, adminPost, adminPut } from "@/lib/api/admin-client";
import type { import type {
AdminBlogPost, AdminBlogPost,
@@ -155,6 +156,7 @@ interface PostEditorProps {
export function AdminBlogEditorScreen({ postId }: PostEditorProps) { export function AdminBlogEditorScreen({ postId }: PostEditorProps) {
const t = useTranslations("admin.website"); const t = useTranslations("admin.website");
const qc = useQueryClient(); const qc = useQueryClient();
const router = useRouter();
const isNew = !postId; const isNew = !postId;
const { data: post } = useQuery({ const { data: post } = useQuery({
@@ -204,11 +206,15 @@ export function AdminBlogEditorScreen({ postId }: PostEditorProps) {
const saveMut = useMutation({ const saveMut = useMutation({
mutationFn: () => mutationFn: () =>
isNew isNew
? adminPost("/api/admin/website/posts", form) ? adminPost<{ id: string }>("/api/admin/website/posts", form)
: adminPut(`/api/admin/website/posts/${postId}`, form), : adminPut<{ id: string }>(`/api/admin/website/posts/${postId}`, form),
onSuccess: () => { onSuccess: (result) => {
qc.invalidateQueries({ queryKey: ["admin", "website", "blog"] }); qc.invalidateQueries({ queryKey: ["admin", "website", "blog"] });
notify.success(t("saved")); notify.success(t("saved"));
// After creating a new post, go to its edit page so the user can
// continue editing and won't accidentally hit Save again (which would
// fail on the unique slug constraint).
if (isNew) router.push(`/admin/website/blog/${result.id}`);
}, },
onError: () => notify.error(t("errorGeneric")), onError: () => notify.error(t("errorGeneric")),
}); });