Next.js project Nextagram

Ng7

lshjju 2025. 9. 28. 17:09

디테일 패스 변경 합니다.


detail/page.tsx 패스를

posts/[id]/page.tsx 패스로 변경 합니다.

이 구조는 넥스트제이에스가 제공하는 템플릿 입니다.

의심하지 말고 그냥 따라하면 지구에 평화가 옵니다.


import dataTexts from '../../data/dataText';
import { useState } from 'react';
import { usePosts } from '../../context/PostContext'; 
import { useParams } from 'next/navigation';

 

posts/[id]/page.tsx

 

패스가 변경되었으니 패스 수정 합니다.

데이터를 사용하기 위한 툴 임포트 합니다.

import { useState } from 'react'; // React의 상태 관리 훅을 가져옵니다.
import { usePosts } from '../../context/PostContext'; // 게시물 데이터를 제공하는 커스텀 훅을 가져옵니다.
import { useParams } from 'next/navigation'; // URL 파라미터를 가져오기 위한 Next.js 훅을 사용합니다.


디테일 데이터 인터페이스 빌딩 합니다.


interface Post {
  id: number;
  title: string;
  summary: string;
  date: string;
  likeIt: string;
  content: string;
}

 

임포트 아래에 포스트 인터페이스 빌딩 합니다.

 

// 게시물의 데이터 구조를 정의하는 인터페이스입니다.
interface Post {
  id: number;      // 게시물 고유 식별자
  title: string;   // 게시물 제목
  summary: string; // 게시물 요약
  date: string;    // 작성일
  likeIt: string;  // 좋아요 수
  content: string; // 게시물 내용
}

interface DetailContentProps {
  post: Post;
  count: number;
  setCount: React.Dispatch<React.SetStateAction<number>>;
}

 

인터페이스포스트 아래에 디테일콘텐트프롭스 인터페이스 빌딩 합니다.

// Details 컴포넌트의 속성 타입을 정의하는 인터페이스입니다.
interface DetailContentProps {
  post: Post; // 표시할 게시물 데이터
  count: number; // 좋아요 카운트 상태
  setCount: React.Dispatch<React.SetStateAction<number>>; // 좋아요 카운트 상태 변경 함수
}

 



필요 변수 세팅, 예외처리, 펑션디테일스에 데이터 전달 합니다.


  const { posts, loading, error } = usePosts(); 
  const params = useParams(); 
  const postId = Number(params.id); 
  const [count, setCount] = useState(0);

  const post = posts.find((p) => p.id === postId);

 

펑션 디테일 바로 아래에 필요 변수를 세팅 합니다.

 

  const { posts, loading, error } = usePosts(); // Context에서 게시물 데이터를 가져옵니다.
  const params = useParams(); // URL에서 파라미터(id)를 가져옵니다.
  const postId = Number(params.id); // 문자열 ID를 숫자로 변환합니다.
  const [count, setCount] = useState(0); // 추가 좋아요 수를 관리하는 상태를 생성합니다.

  // 현재 URL의 ID와 일치하는 게시물을 찾습니다.
  const post = posts.find((p) => p.id === postId);

  if (loading) {
    return <div className="m-3 text-center">데이터 로딩 중입니다...</div>;
  }

  if (error) {
    return (
      <div className="m-3 text-danger text-center">
        데이터를 불러오는 데 실패했습니다.
      </div>
    );
  }

  if (!post) {
    return (
      <div className="m-3 text-center">
        해당 포스트를 찾을 수 없습니다. (ID: {postId})
      </div>
    );
  }

 

변수 아래에 로딩/에러 처리 합니다.

 

  // 로딩 중일 때 표시할 내용
  if (loading) {
    return <div className="m-3 text-center">데이터 로딩 중입니다...</div>;
  }

  // 오류 발생 시 표시할 내용
  if (error) {
    return (
      <div className="m-3 text-danger text-center">
        데이터를 불러오는 데 실패했습니다.
      </div>
    );
  }

  // 해당 ID의 게시물이 없을 때 표시할 내용
  if (!post) {
    return (
      <div className="m-3 text-center">
        해당 포스트를 찾을 수 없습니다. (ID: {postId})
      </div>
    );
  }

  // 모든 조건이 충족되면 상세 내용과 댓글 폼을 표시합니다.

  return (
    <div>
      <h4>{dataTexts[0].subCaps3}</h4>
      <Details post={post} count={count} setCount={setCount} />
      <Comment />
    </div>
  );

 

펑션디테일 리턴에 데이터 세팅 합니다.

// 모든 조건이 충족되면 상세 내용과 댓글 폼을 표시합니다.
return (
    <div>
      <h4>{dataTexts[0].subCaps3}</h4>
      <Details post={post} count={count} setCount={setCount} /> {/* 게시물 상세 정보 컴포넌트 */}
      <Comment /> {/* 댓글 입력 폼 컴포넌트 */}
    </div>
  );


디테일스 컴포넌트 데이터바인딩 하고 좋아요 기능 추가 합니다.


function Details({ post, count, setCount }: DetailContentProps)

 

위 설계에 따라 세가지 정보를 파라미터로 사용 합니다.


function Details({ post, count, setCount }: DetailContentProps){
  return(
    <>
    <div className="card m-3">
    <img
      src={
        `https://raw.githubusercontent.com/lshjju/cdn/refs/heads/main/girls/` +
        (post.id + 1) +
        `.PNG`
      }
      className="card-img-top"
      alt="..."
    />

    <div className="card-body">
      <h5 className="card-title">{post.title}</h5>
      <p className="card-text">
      {post.content}
      </p>
      <p className="card-text">작성일: {post.date}</p>
      <div className="mt-2">

        <button
          type="button"
          className="btn active"
          onClick={() => {
            setCount(count + 1);
          }}
          data-bs-toggle="button"
          aria-pressed="true"
        >
          ❤️️
        </button>
        <span> {count + Number(post.likeIt)} </span>
      </div>
      <p className="card-text">
        <small className="text-body-secondary">
          Last updated 3 mins ago<br />
          Default like: {post.likeIt}
        </small>
      </p>
    </div>
  </div>
  </>
  )
}

 

데이터바인딩 하고 좋아요 기능 추가 합니다.

 


// 게시물 상세 정보를 표시하는 컴포넌트입니다.
function Details({ post, count, setCount }: DetailContentProps){
  return(
    <>
    <div className="card m-3">
      <img
        src={
          `https://raw.githubusercontent.com/lshjju/cdn/refs/heads/main/girls/` +
          (post.id + 1) +
          `.PNG`
        } // 게시물 ID에 해당하는 이미지를 가져옵니다.
        className="card-img-top"
        alt="..."
      />

      <div className="card-body">
        <h5 className="card-title">{post.title}</h5> {/* 게시물 제목 */}
        <p className="card-text">
          {post.content} {/* 게시물 내용 */}
        </p>
        <p className="card-text">작성일: {post.date}</p> {/* 작성일 표시 */}
        <div className="mt-2">
          {/* 좋아요 버튼 - 클릭 시 카운트를 증가시킵니다 */}
          <button
            type="button"
            className="btn active"
            onClick={() => {
              setCount(count + 1); // 좋아요 카운트를 1 증가시킵니다.
            }}
            data-bs-toggle="button"
            aria-pressed="true"
          >
            ❤️️
          </button>
          <span> {count + Number(post.likeIt)} </span> {/* 기존 좋아요 + 추가된 좋아요 표시 */}
        </div>
        <p className="card-text">
          <small className="text-body-secondary">
            Last updated 3 mins ago<br /> {/* 마지막 업데이트 시간 (정적 텍스트) */}
            Default like: {post.likeIt} {/* 원래 좋아요 수 표시 */}
          </small>
        </p>
      </div>
    </div>
    </>
  )
}

 

test

메인에서 포스트디테일 버튼 탭합니다.

해당 디테일뷰 유알엘로 잘 연결되는지 체크 합니다.

좋아요 탭 체크 합니다.


Completion

'use client';
import 'bootstrap/dist/css/bootstrap.min.css';
import dataTexts from '../../data/dataText';
import { useState } from 'react';
import { usePosts } from '../../context/PostContext';
import { useParams } from 'next/navigation';

interface Post {
  id: number;
  title: string;
  summary: string;
  date: string;
  likeIt: number;
  content: string;
}

interface DetailContentProps {
  post: Post;
  count: number;
  setCount: React.Dispatch<React.SetStateAction<number>>;
}

interface DataText {
  main: string;
  subCaps1: string;
  sub1: string;
  subCaps2: string;
  sub2: string;
  subCaps3: string;
  sub3: string;
  footer: string;
}

export default function Detail() {
  const { posts, loading, error } = usePosts();
  const params = useParams();
  const postId = Number(params.id);
  const [count, setCount] = useState(0);

  const post = posts.find((p) => p.id === postId);

  if (loading) {
    return <div className="m-3 text-center">데이터 로딩 중입니다...</div>;
  }

  if (error) {
    return (
      <div className="m-3 text-danger text-center">
        데이터를 불러오는 데 실패했습니다.
      </div>
    );
  }

  if (!post) {
    return (
      <div className="m-3 text-center">
        해당 포스트를 찾을 수 없습니다. (ID: {postId})
      </div>
    );
  }

  return (
    <div>
      <h4>{dataTexts[0].subCaps3}</h4>
      <Details post={post} count={count} setCount={setCount} />
      <Comment />
    </div>
  );
}
function Details({ post, count, setCount }: DetailContentProps) {
  return (
    <>
      <div className="card m-3">
        <img
          src={
            `https://raw.githubusercontent.com/lshjju/cdn/refs/heads/main/girls/` +
            (post.id + 1) +
            `.PNG`
          }
          className="card-img-top"
          alt="..."
        />

        <div className="card-body">
          <h5 className="card-title">{post.title}</h5>
          <p className="card-text">{post.content}</p>
          <p className="card-text">작성일: {post.date}</p>
          <div className="mt-2">
            <button
              type="button"
              className="btn active"
              onClick={() => {
                setCount(count + 1);
              }}
              data-bs-toggle="button"
              aria-pressed="true"
            >
              ❤️️
            </button>
            <span> {count + Number(post.likeIt)} </span>
          </div>
          <p className="card-text">
            <small className="text-body-secondary">
              Last updated 3 mins ago
              <br />
              Default like: {post.likeIt}
            </small>
          </p>
        </div>
      </div>
    </>
  );
}

function Comment() {
  return (
    <div className="m-3">
      <form>
        <div className="mb-3">
          <label for="exampleInputEmail1" className="form-label">
            Comment
          </label>
          <input
            type="email"
            className="form-control"
            id="exampleInputEmail1"
            aria-describedby="emailHelp"
          />
          <div id="emailHelp" className="form-text">
            We'll share your story
          </div>
        </div>
        <div className="mb-3">
          <label for="exampleInputPassword1" className="form-label">
            Password
          </label>
          <input
            type="password"
            className="form-control"
            id="exampleInputPassword1"
          />
        </div>
        <div className="mb-3 form-check">
          <input
            type="checkbox"
            className="form-check-input"
            id="exampleCheck1"
          />
          <label className="form-check-label" for="exampleCheck1">
            Check me out
          </label>
        </div>
        <button type="submit" className="btn btn-outline-secondary">
          Submit
        </button>
      </form>
    </div>
  );
}

 

Comment ver

'use client'; // Next.js에서 클라이언트 컴포넌트임을 명시합니다.
import 'bootstrap/dist/css/bootstrap.min.css'; // 부트스트랩 CSS 스타일을 가져옵니다.
import dataTexts from '../../data/dataText'; // 텍스트 데이터를 가져옵니다.
import { useState } from 'react'; // React의 상태 관리 훅을 가져옵니다.
import { usePosts } from '../../context/PostContext'; // 게시물 데이터를 제공하는 커스텀 훅을 가져옵니다.
import { useParams } from 'next/navigation'; // URL 파라미터를 가져오기 위한 Next.js 훅을 사용합니다.

// 게시물의 데이터 구조를 정의하는 인터페이스입니다.
interface Post {
  id: number;      // 게시물 고유 식별자
  title: string;   // 게시물 제목
  summary: string; // 게시물 요약
  date: string;    // 작성일
  likeIt: number;  // 좋아요 수
  content: string; // 게시물 내용
}

// Details 컴포넌트의 속성 타입을 정의하는 인터페이스입니다.
interface DetailContentProps {
  post: Post;                                      // 표시할 게시물 데이터
  count: number;                                   // 좋아요 카운트 상태
  setCount: React.Dispatch<React.SetStateAction<number>>; // 좋아요 카운트 상태 변경 함수
}

// 텍스트 데이터의 구조를 정의하는 인터페이스입니다.
interface DataText {
  main: string;      // 메인 텍스트
  subCaps1: string;  // 첫 번째 소제목 (대문자)
  sub1: string;      // 첫 번째 소제목 내용
  subCaps2: string;  // 두 번째 소제목 (대문자)
  sub2: string;      // 두 번째 소제목 내용
  subCaps3: string;  // 세 번째 소제목 (대문자)
  sub3: string;      // 세 번째 소제목 내용
  footer: string;    // 푸터 텍스트
}

// 상세 페이지의 메인 컴포넌트입니다.
export default function Detail() {
  const { posts, loading, error } = usePosts(); // Context에서 게시물 데이터를 가져옵니다.
  const params = useParams(); // URL에서 파라미터(id)를 가져옵니다.
  const postId = Number(params.id); // 문자열 ID를 숫자로 변환합니다.
  const [count, setCount] = useState(0); // 추가 좋아요 수를 관리하는 상태를 생성합니다.

  // 현재 URL의 ID와 일치하는 게시물을 찾습니다.
  const post = posts.find((p) => p.id === postId);

  // 로딩 중일 때 표시할 내용
  if (loading) {
    return <div className="m-3 text-center">데이터 로딩 중입니다...</div>;
  }

  // 오류 발생 시 표시할 내용
  if (error) {
    return (
      <div className="m-3 text-danger text-center">
        데이터를 불러오는 데 실패했습니다.
      </div>
    );
  }

  // 해당 ID의 게시물이 없을 때 표시할 내용
  if (!post) {
    return (
      <div className="m-3 text-center">
        해당 포스트를 찾을 수 없습니다. (ID: {postId})
      </div>
    );
  }

  // 모든 조건이 충족되면 상세 내용과 댓글 폼을 표시합니다.
  return (
    <div>
      <h4>{dataTexts[0].subCaps3}</h4> {/* 세 번째 소제목을 페이지 제목으로 사용합니다. */}
      <Details post={post} count={count} setCount={setCount} /> {/* 게시물 상세 정보 컴포넌트 */}
      <Comment /> {/* 댓글 입력 폼 컴포넌트 */}
    </div>
  );
}

// 게시물 상세 정보를 표시하는 컴포넌트입니다.
function Details({ post, count, setCount }: DetailContentProps) {
  return (
    <>
      <div className="card m-3">
        <img
          src={
            `https://raw.githubusercontent.com/lshjju/cdn/refs/heads/main/girls/` +
            (post.id + 1) +
            `.PNG`
          } // 게시물 ID에 해당하는 이미지를 가져옵니다.
          className="card-img-top"
          alt="..."
        />

        <div className="card-body">
          <h5 className="card-title">{post.title}</h5> {/* 게시물 제목 */}
          <p className="card-text">{post.content}</p> {/* 게시물 내용 */}
          <p className="card-text">작성일: {post.date}</p> {/* 작성일 표시 */}
          <div className="mt-2">
            {/* 좋아요 버튼 - 클릭 시 카운트를 증가시킵니다 */}
            <button
              type="button"
              className="btn active"
              onClick={() => {
                setCount(count + 1); // 좋아요 카운트를 1 증가시킵니다.
              }}
              data-bs-toggle="button"
              aria-pressed="true"
            >
              ❤️️
            </button>
            <span> {count + Number(post.likeIt)} </span> {/* 기존 좋아요 + 추가된 좋아요 표시 */}
          </div>
          <p className="card-text">
            <small className="text-body-secondary">
              Last updated 3 mins ago<br /> {/* 마지막 업데이트 시간 (정적 텍스트) */}
              Default like: {post.likeIt} {/* 원래 좋아요 수 표시 */}
            </small>
          </p>
        </div>
      </div>
    </>
  );
}

// 댓글 입력 폼 컴포넌트입니다.
function Comment() {
  return (
    <div className="m-3">
      <form>
        <div className="mb-3">
          <label for="exampleInputEmail1" className="form-label">
            Comment {/* 댓글 입력 필드 레이블 */}
          </label>
          <input
            type="email"
            className="form-control"
            id="exampleInputEmail1"
            aria-describedby="emailHelp"
          />
          <div id="emailHelp" className="form-text

 

 

 

 

 

'Next.js project Nextagram' 카테고리의 다른 글

Ng6  (0) 2025.09.28
Ng5  (0) 2025.09.28
Ng4  (0) 2025.09.28
Ng3  (0) 2025.09.27
Ng2  (0) 2025.09.27