072DATA

OOTW 프로젝트 - 글 작성 구현하기 ( 이미지 업로드 ) 본문

FrontEnd/Next.js

OOTW 프로젝트 - 글 작성 구현하기 ( 이미지 업로드 )

0720 2024. 10. 18. 00:59

Next.js를 사용한 프로젝트에서 글 작성 페이지 구현하기

 

이번 포스트에서는 Next.js와 Zustand를 사용해 글 작성 페이지를 구현하는 방법을 기록하겠습니다

물론 이 페이지의 경우 클라이언트 사이드 렌더링을 사용하기 때문에 리액트와 다를 건 없습니다...ㅎㅎ

 

참고로 글 작성 기능은 이미지 업로드, 상태 관리, 폼 데이터 처리 등의 요소를 포함하고 있습니다.

 

1. Zustand로 상태 관리하기

 

먼저, 작성 페이지에서는 사용자 정보와 날씨 정보를 포함한 상태를 Zustand를 통해 관리합니다

여기서는 세 가지 상태를 사용해요

  • useUserStore: 사용자 정보를 관리.
  • useWeatherStore: 날씨 데이터를 관리.
  • useWriteStore: 작성 폼에 필요한 데이터를 관리.
const { user } = useUserStore();
const { weather, loading, setLocation } = useWeatherStore();
const { formData, imageState, categoryInput, setFormData, setImageState, setCategoryInput, resetForm } = useWriteStore();

 

사실 작성 폼 까지 Zustand를 사용해야 되나 싶었는데

결국 업데이트 페이지 같은 곳에서도 사용하게 되어서 잘 만들어둔 것 같습니다

 

 

2. 날씨 정보와 사용자 정보 초기화

 

컴포넌트가 마운트될 때, useEffect를 사용해 기본 날씨 정보와 폼을 초기화합니다. 

그 후에 날씨 데이터를 불러오고 해당 정보를 폼 데이터에 자동으로 채워 넣습니다.

useEffect(() => {
  const lat = 37.5665;
  const lon = 126.978;

  const handleGetWeatherAndUser = () => {
    resetForm();
    if (weather.weather[0].main === "") {
      setLocation(lat, lon);
    }
    if (!loading && weather.coord.lat !== 0) {
      setFormData({
        temperature: weather.main.temp || 0,
        post_weather: weather.weather[0].main || "정보 없음"
      });
    }
  };

  handleGetWeatherAndUser();
}, [weather, loading, setLocation, setFormData, resetForm]);

 

 

3. 글 작성 핸들러

 

폼 제출 시 호출되는 handleAddPost 함수는 입력 데이터의 유효성을 검사하며 작성된 데이터를 서버로 전송합니다.

유효성 검사가 통과되면, addPostHandler를 호출하여 데이터를 처리합니다.

const handleAddPost = async (e: React.FormEvent<HTMLFormElement>) => {
  e.preventDefault();
  
  // 필수 항목 검증
  if (formData.post_title.length === 0) {
    toast.warn("제목을 입력해주세요");
    return;
  } else if (formData.post_content.length === 0) {
    toast.warn("내용을 입력해주세요");
    return;
  } else if (formData.post_img.length === 0) {
    toast.warn("이미지를 업로드해주세요.");
    return;
  }

  setIsButtonDisabled(true);
  await addPostHandler(e, formData, imageState, user, resetForm);
  toast.success("작성이 완료되었습니다.");
  router.replace("/");
};

 

 

이때 모든 유효성 검사가 끝나고 나서 버튼의 동작 상태를 비활성화 시킨 다음

버튼을 여러 번 누르게 되면 같은 게시글이 여러번 작성되는 버그를 방지했습니다.

 

4. 이미지 업로드와 글 작성 처리

 

이미지가 포함된 글을 작성할 때는 먼저 이미지를 업로드하고,

그 후 포스트 데이터를 저장합니다. 아래는 이미지 업로드와 글 작성 처리 과정입니다.

export const addPostHandler = async (
  e: React.FormEvent<HTMLFormElement>,
  formData: Omit<WriteTypes, "fileInputRef">,
  imageState: ImageType,
  user: UserData,
  resetForm: () => void
) => {
  e.preventDefault();

  try {
    // 이미지 업로드 처리
    if (imageState.imageFile) {
      await uploadImage(imageState);
    }

    // 포스트 데이터 저장
    const postData = {
      ...formData,
      post_date: formatDateTime(new Date()),
      mem_no: user.userId
    };
    await addPost(postData);
    resetForm();
  } catch (error) {
    console.log("글 작성 에러:", error);
  }
};

 

해당 함수 인자의 타입을 지정해주고  preventDefault() 메소드를 사용하여 새로고침되는 동작을 방지합니다

여기서는 fomatDateTime 라는 함수를 기억해주십쇼! 어쨌든 요청이 완료되면 작성 폼의 상태를 초기화시킵니다..!

 

export const addPost = async (formData: Omit<WriteTypes, "fileInputRef">) => {
  const { data, error } = await browserClient.from("post").insert(formData).select("*");

  if (error) throw error;
  return data;
};

export const uploadImage = async (imageState: ImageType) => {
  if (!imageState.imageFile) {
    throw new Error("이미지 파일이 없습니다.");
  }

  const { data, error } = await browserClient.storage
    .from("post")
    .upload(`post_${imageState.imageFile.lastModified}`, imageState.imageFile);

  if (error) throw error;
  return data;
};

 

 

이 함수는 API 요청 함수로 수파베이스에 접근하여

보내준 이미지와 게시글 데이터를 쿼리문에 맞게 전달합니다!!

 

이미지 업로드의 경우 수파베이스 프로젝트에서 스토리지에 버킷을 생성해야 합니다

제 코드를 보시면 storage.post라는 경로로 업로드가 되는 것을 보실 수 있습니다!!!

 

5. 날짜 및 시간 포맷 처리

아까 밑에 함수를 기억해달라고 했었죠 이 함수는

작성 날짜를 YYYY-MM-DD HH:MM:SS 형식으로 포맷팅 하는 함수입니다.

export const formatDateTime = (date: Date) => {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, "0");
  const day = String(date.getDate()).padStart(2, "0");
  const hours = String(date.getHours()).padStart(2, "0");
  const minutes = String(date.getMinutes()).padStart(2, "0");
  const seconds = String(date.getSeconds()).padStart(2, "0");
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};

 

이 함수가 필요했던 이유는 new Date() 는 로컬 시간을 반환합니다

따라서 UTF에 따라 한국의 시간이나 다른 나라의 시간을 가져오는데

 

수파베이스는 이 날짜 데이터를 저장할 때 한국의 시간이 아닌

세계 표준시로 저장하는 이상한(?) 버그가 있었습니다....!

 

따라서 한국 시간이나 다른 로컬 시간을 사용하려면, 시간 포맷팅이 필요했고

이를 위해 formatDateTime 함수로 로컬 시간을 문자열 형태의 날짜로 변환하여

필요한 데이터를 저장할 수 있었습니다ㅎㅎ

 

이 처리를 통해서 오늘의 게시글 페이지나 게시글, 댓글의 날짜를 버그 없이 프로젝트에 녹여낼 수 있었습니다.

 

마무리

프로젝트 진행하며 구현했던 일부이지만 생각보다 골치 아픈 오류와 로직이었습니다

이번 프로젝트는 정말 많이 배우게 되었는데 next js를 사용하면서도

next js의 이점을 제대로 살리지 못했던 것 같아서 그 부분이 너무 아쉽습니다

그렇기 때문에 꼭 !!!!!! 다시 한 번 검토하여 프로젝트를 멋지게 리팩토링 해야겠어요 ( 이번엔 진짜임 )