swimminginthecode DIVE!

Dazz6/ABOUT

모각 프로젝트 - #2-4. 비밀 고민 상담

dazz6 2025. 8. 4. 18:00

말 못할 고민을 익명으로 털어놓을 수 있는 게시판으로, 글을 작성할 때 시간을 지정하여 해당 시간이 경과하면 자동으로 삭제된다.

 

 

친구들에게 말 못할 고민을 털어놓자는 콘셉트로 추가된 페이지다.

비밀일기장 같은 느낌을 위한 일러스트 이미지를 메인에 삽입했다.

일러스트디자인은 디자이너 지인이 도와주었습니다 https://www.youtube.com/@티거-r9c

 

 

고민을 작성하는 시점에 시간을 지정할 수 있으며, 기본 시간은 24시간이다.

작성을 완료하면 해당 작성글로 바로 redirect 되고, 삭제까지 남은 시간이 실시간으로 줄어들게 설정했다.

첫 번째 댓글은 서버가 반환한 '모각봇' 의 AI 위로 답변이다.

 

"use client";

import { useEffect, useState } from "react";
import { AiFillHeart, AiOutlineHeart } from "react-icons/ai";
import {
  timeArrayToSeconds,
  secondsToTimeArray,
  formatTimeArray,
} from "@/utils/shared/time.util";
import { AdviceDetailResponse } from "@/types/advice.type";
import AdviceCommentsCard from "./advice-comments-card";
import { AdviceEmpathyPost } from "@/lib/client/advice.client.api";

import AlertModal from "@/app/components/custom-modal";
import { useRouter } from "next/navigation";
import { useAuthStore } from "@/store/authStore";

export default function AdviceContents({
  id,
  data,
}: {
  id: string;
  data: AdviceDetailResponse;
}) {
  const [showModal, setShowModal] = useState(false);
  const [empathyCount, setEmpathyCount] = useState(data.empathyCount);
  const [hasEmpathized, setHasEmpathized] = useState(data.hasEmpathized);

  const [restSeconds, setRestSeconds] = useState(
    timeArrayToSeconds(data.restTime)
  );

  const jwt = useAuthStore((state) => state.jwt);

  useEffect(() => {
    const timer = setInterval(() => {
      setRestSeconds((prev) => (prev > 0 ? prev - 1 : 0));
    }, 1000);
    return () => clearInterval(timer);
  }, []);

  const handleEmpathy = async () => {
    if (!jwt) {
      setShowModal(true);
      return;
    }

    try {
      const res = await AdviceEmpathyPost(id);
      const { empathyCount, hasEmpathized } = res;
      setEmpathyCount(empathyCount);
      setHasEmpathized(hasEmpathized);
    } catch (err) {
      console.log("공감 실패 : " + err);
    }
  };

  const router = useRouter();

  const toLogin = () => {
    router.push(`/login`);
  };

  return (
    <div className="flex flex-col lg:flex-row gap-4 max-w-screen-xl mx-auto px-4 sm:px-6 py-10">
      <div className="lg:w-[65%] w-full flex flex-col gap-6">
        <div className="flex items-center justify-between border-borders dark:border-border-dark border-b pb-2">
          <div>
            <h2 className="text-lg font-bold text-primary dark:text-primary-dark">
              {data.title}
            </h2>
            <p className="text-sm text-border-dark dark:text-borders">
              {formatTimeArray(secondsToTimeArray(restSeconds))} 남음
            </p>
          </div>
        </div>

        <p className="text-sm text-text dark:text-text-dark whitespace-pre-line">
          {data.body}
        </p>

        <button
          className="flex items-center gap-1 text-error dark:text-error-dark text-sm"
          onClick={handleEmpathy}
        >
          {hasEmpathized ? (
            <AiFillHeart size={18} />
          ) : (
            <AiOutlineHeart size={18} />
          )}
          공감 {empathyCount}개
        </button>
      </div>

      <div className="w-full lg:w-[35%]">
        <AdviceCommentsCard worryId={id} />
      </div>
      {showModal && (
        <AlertModal
          message="로그인 후 이용해 주세요."
          moveMsg="로그인하기"
          onMove={toLogin}
          confirmMsg="닫기"
          onConfirm={() => setShowModal(false)}
        />
      )}
    </div>
  );
}

 

advice 도 동일하게 page.tsx 는 server component 로 유지하며 wrapper(contents) 를 작성하여 import 하는 방식을 사용했다.

사실 여러 번 구현해 보았던 게시판과 크게 다른 부분은 '삭제까지의 시간 출력' 부분밖에 없었고

남은 시간 출력 후 클라이언트단에서 1초씩 감소하는 방식을 사용하여 크게 어렵지 않았다.