키모스토리

#9. Create Blog Posts 본문

Web Devlopment/Next-Blog Project

#9. Create Blog Posts

키모형 2025. 4. 5. 18:11
반응형

로그인 여부에 따른 Navigation 수정 (글작성) - /posts/create

import getAuthUser from "@/lib/getAuthUser";
import NavLink from "./NavLink"
import { logout } from "@/actions/auth";

export default async function Navigation() {

  const authUser = await getAuthUser();
  console.log("authUser=", authUser);

  return (
    <nav>
      <NavLink label="Home" href="/" />
      {authUser ? 
        (
          <div className="flex items-center">
            <NavLink label="New Post" href="/posts/create" />
            <NavLink label="Dashboard" href="/dashboard" />
            <form action={logout}>
              <button className="nav-link">Logout</button>
            </form>
          </div>
        ) : 
        (
          <div className="flex items-center">
            <NavLink label="Register" href="/register" />
            <NavLink label="Login" href="/login" />
          </div>
        )}      
    </nav>
  )
}

 

블로그 글작성페이지  (BlogForm 컴포넌트로 분리작성)

 

formaction으로 db처리를 위해 action처리

 

/src/app/posts/create/page.jsx

"use client"

import { createPost } from "@/actions/posts";
import BlogForm from "@/components/BlogForm";

export default function CreatePost() {
  return (
    <div className="container w-1/2">
      <h1 className="title">Create a new post</h1>
      <BlogForm handler={createPost} />
    </div>
  );
}

 

BlogForm 컴포넌트

form의 action (DB입력) 처리를 위해 createPost

 

/src/components/BlogForm.jsx

"use client";

import { useActionState } from "react";


export default function BlogForm({handler}) {
  const [state, action, isPending] = useActionState(handler, undefined);

  return (
    <form action={action} className="space-y-4">
      <div>
        <label htmlFor="title">Title</label>
        <input type="text" name="title" defaultValue={state?.title} />
          {state?.errors?.title && (
            <p className="error">{state.errors.title}</p>
          )}
      </div>
      <div>
        <label htmlFor="content">Content</label>
        <textarea name="content" rows={10} defaultValue={state?.content} />
        {state?.errors?.content && (
            <p className="error">{state.errors.content}</p>
          )}
      </div>
      <button disabled={isPending} className="btn-primary w-full">
          {isPending ? "Loading..." : "Submit"}
        </button>   
    </form>
  );
}

 

글작성 처리 action

/src/actions/posts.js

"use server";

import { getCollection } from "@/lib/db";
import getAuthUser from "@/lib/getAuthUser";
import { BlogPostSchema } from "@/lib/rules";
import { ObjectId } from "mongodb";
import { redirect } from "next/navigation";

// 블로그 글관리 액션정의
export async function createPost(state, formData) {
  // 로그인 여부
  const authUser = await getAuthUser();
  if (!authUser) redirect("/login");

  const title = formData.get("title");
  const content = formData.get("content");

  // 유효성 검사 규칙은 src/lib/rules.js에 정의되어 있습니다.
  const validatedFields = BlogPostSchema.safeParse({
    title,
    content,
  });

  // 유효성 검사에 실패한 경우, 에러 메시지를 반환합니다.
  // 에러 메시지는 src/lib/rules.js에 정의된 규칙에 따라 다르게 표시됩니다.
  if (!validatedFields.success) {
    return {
      errors: validatedFields.error.flatten().fieldErrors,
      title: title,
      content: content,
    };
  }

  // Save to DB
  try {
    const postsCollection = await getCollection("posts");
    if( !postsCollection) {
      return { errors: { title: "Server error!" } };
    }    
    await postsCollection.insertOne({    
      title: validatedFields.data.title,
      content: validatedFields.data.content,
      userId: ObjectId.createFromHexString(authUser.userId)     
    });
  } catch (error) {
    return { errors: { title: error.message } };
  }
  redirect("/posts");
}

 

글작성 폼 validate 

/src/lib/rules.js 

// 블로그 등록 폼 검사합니다.
export const BlogPostSchema = z
.object({
  title: z
    .string()
    .min(1, { message: "Title field is required." })
    .max(100, { message: "Title can't be more than 100 characters." })
    .trim(),
  content: 
    z.string()
    .min(1, { message: "Content field is required." }).trim(),
});

 

 

반응형

'Web Devlopment > Next-Blog Project' 카테고리의 다른 글

#10. Read Blog Posts (home)  (0) 2025.04.05
#8. Auth & Guest Links  (0) 2025.04.05
#7. Login  (0) 2025.04.05
#6. Active Links  (0) 2025.04.05
#5. server-only  (0) 2025.04.05