본문 바로가기

2024-07-11 Next.js 마이페이지 구현 로직

codeConnection 2024. 7. 12.

폴더구조

```
📦 
.eslintrc.json
.gitignore
.vscode
│  ├─ extensions.json
│  └─ settings.json
README.md
components.json
├─ next.config.mjs
├─ package.json
├─ postcss.config.mjs
public
Gallery_View_icon.svg
├─ src
│  ├─ app
│  │  ├─ (provider)
│  │  │  ├─ (root)
│  │  │  │  ├─ auth
│  │  │  │  │  ├─ login
│  │  │  │  │  │  └─ page.tsx
│  │  │  │  │  └─ signup
│  │  │  │  │     └─ page.tsx
│  │  │  │  ├─ concerts
│  │  │  │  │  ├─ [postId]
│  │  │  │  │  │  ├─ edit
│  │  │  │  │  │  │  └─ page.tsx
│  │  │  │  │  │  └─ page.tsx
│  │  │  │  │  ├─ page.tsx
│  │  │  │  │  └─ write
│  │  │  │  │     └─ page.tsx
│  │  │  │  ├─ layout.tsx
│  │  │  │  ├─ mypage
│  │  │  │  │  ├─ layout.tsx 🟪
│  │  │  │  │  └─ page.tsx 🟪
│  │  │  │  ├─ page.tsx
│  │  │  │  └─ posts
│  │  │  │     ├─ [postId]
│  │  │  │     │  ├─ _components
│  │  │  │     │  │  ├─ ButtonsChangePostStatus.tsx
│  │  │  │     │  │  ├─ ImageUploader
│  │  │  │     │  │  │  ├─ ImageUploader.tsx
│  │  │  │     │  │  │  └─ upload.ts
│  │  │  │     │  │  ├─ PostEditSection.tsx
│  │  │  │     │  │  └─ TagInput.tsx
│  │  │  │     │  ├─ edit
│  │  │  │     │  │  └─ page.tsx
│  │  │  │     │  └─ page.tsx
│  │  │  │     ├─ _components
│  │  │  │     │  ├─ PostItemSqure.tsx
│  │  │  │     │  └─ PostListView.tsx
│  │  │  │     ├─ create
│  │  │  │     │  └─ page.tsx
│  │  │  │     └─ page.tsx
│  │  │  └─ layout.tsx
│  │  ├─ api
│  │  │  ├─ auth
│  │  │  │  ├─ login
│  │  │  │  │  └─ route.ts
│  │  │  │  └─ signup
│  │  │  │     └─ route.ts
│  │  │  ├─ concerts
│  │  │  │  ├─ [postId]
│  │  │  │  │  └─ route.ts
│  │  │  │  └─ route.ts
│  │  │  └─ posts
│  │  │     ├─ [postId]
│  │  │     │  ├─ comments
│  │  │     │  │  ├─ [commentId]
│  │  │     │  │  │  └─ route.ts
│  │  │     │  │  └─ route.ts
│  │  │     │  └─ route.ts
│  │  │     └─ route.ts
│  │  ├─ favicon.ico
│  │  ├─ globals.css
│  │  └─ layout.tsx
│  ├─ components
│  │  ├─ Alert.tsx
│  │  ├─ Backdrop.tsx
│  │  ├─ BreakLine.tsx
│  │  ├─ CommentList
│  │  │  ├─ Comment.tsx
│  │  │  ├─ CommentUpload.tsx
│  │  │  ├─ Comments.tsx
│  │  │  ├─ CommentsView.tsx
│  │  │  ├─ ErrorGetComments.tsx
│  │  │  ├─ LoadingComments.tsx
│  │  │  └─ modal
│  │  │     ├─ CmtDelBtn.tsx
│  │  │     ├─ CmtDelModal.tsx
│  │  │     ├─ CmtEditBtn.tsx
│  │  │     ├─ CmtEditModal.tsx
│  │  │     ├─ CmtToDel.tsx
│  │  │     └─ CmtToModi.tsx
│  │  ├─ ConcertList
│  │  │  ├─ ConcertDeleteButton.tsx
│  │  │  └─ ConcertListView.tsx
│  │  ├─ ConcertSquare.tsx
│  │  ├─ Footer.tsx
│  │  ├─ Hashtag.tsx
│  │  ├─ Header.tsx
│  │  ├─ HeartIcon.tsx
│  │  ├─ Loading.tsx
│  │  ├─ Mainpage
│  │  │  ├─ BestInfo.tsx
│  │  │  ├─ Carousel.tsx
│  │  │  ├─ MainPage.tsx
│  │  │  └─ PerformanceInfo.tsx
│  │  ├─ MyPage
│  │  │  ├─ PostVIew
│  │  │  │  ├─ HowManyLikes.tsx 🟦
│  │  │  │  ├─ MyPostGalleyView.tsx 🟦
│  │  │  │  ├─ MyPostListView.tsx 🟦
│  │  │  │  └─ MyPostViewSwitcher.tsx 🟦
│  │  │  └─ ProfileEdit
│  │  │     ├─ MyProfile.tsx 🟦
│  │  │     ├─ ProfileEditButton.tsx 🟦
│  │  │     └─ ProfileEditModal.tsx 🟦
│  │  └─ ui
│  │     └─ alert.tsx
│  ├─ constant.ts
│  ├─ hooks
│  │  ├─ useAllLIkes.ts 🟩
│  │  ├─ useMyPosts.ts 🟩
│  │  └─ useUserData.ts 🟩
│  ├─ lib
│  │  └─ utils.ts
│  ├─ middleware.ts
│  ├─ types
│  │  ├─ Auth.ts
│  │  ├─ Comments.ts
│  │  ├─ Concert.ts
│  │  ├─ Post.ts
│  │  ├─ Tag.ts
│  │  └─ supabase.ts
│  ├─ utils
│  │  ├─ formatDateString.ts
│  │  ├─ getLikes.ts 🟩
│  │  ├─ getMyInfo.ts 🟩
│  │  ├─ getMyPosts.ts 🟩
│  │  ├─ getUser.ts 🟩
│  │  ├─ myPage
│  │  │  ├─ getLikes.ts
│  │  │  ├─ getMyInfo.ts
│  │  │  ├─ getMyPosts.ts
│  │  │  └─ getUser.ts
│  │  └─ supabase
│  │     ├─ client.ts
│  │     ├─ middleware.ts
│  │     └─ server.ts
│  └─ zustand
│     ├─ alert.store.ts
│     └─ auth.store.ts
├─ supabase
│  ├─ .gitignore
│  ├─ .temp
│  │  └─ cli-latest
│  ├─ config.toml
│  └─ seed.sql
├─ tailwind.config.ts
├─ tsconfig.json
└─ yarn.lock
```
  • 🟪 페이지와 레이아웃
  • 🟦 컴포넌트
  • 🟩 유필 파일

구현된 모습

페이지

수정 모달창

  • 인증된 유저의 현재 정보 렌더링
  • <닉네임, 선호하는 뮤지션> 가입 시 입력받은 추가 정보 수정
  • <프로필 사진> 변경
    • 이미지 변경 버튼 클릭 시 즉시 버킷에 올라가며 반영되는 로직 X
    • 이미지 변경 버튼 클릭 시 상태에 저장해 둔 이미지만 즉각적으로 렌더링 하고, 완료 버튼을 클릭해야 버킷에 전송 O
  • 입력필드 유효성 검사 :: 변경사항 없을 때 완료 버튼 클릭 시 에러 문구 출력

내가 쓴 게시글

  • 컴포넌트 뷰 전환 스위치 구현
    • 리스트형, 갤러리형 아이콘 클릭 시 전환
  • TanStack Query 캐싱 처리
  • TanStack Query useInfiniteQuery 및 react-intersection-observer를 활용한 무한 스크롤 구현
  • 인증된 사용자의 게시글만 추출하여 리스트 렌더링
  • 인증된 사용자가 작성한 게시글의 좋아요 수 렌더링
  • 내가 쓴 게시글 아이템 클릭 시 상세페이지로 router 연결

src/app/(provider)/(root)/mypage/layout.tsx

import { PropsWithChildren } from "react";

function MyPageLayout({ children }: PropsWithChildren) {
  return (
    <div className="flex flex-col min-h-screen items-center justify-start">
      <main className="w-full max-w-4xl p-8 rounded">
        {children}
      </main>
    </div>
  );
}

export default MyPageLayout;
  • 처음에 페이지의 기본 레이아웃을 잡고 시작하면 좋음.
  • 상위의 레이아웃의 영향을 받으므로 참고해서 작성.

 

  • import { PropsWithChilderen } from "react";
    • 리액트의 PropsWithChilderen 타입을 import해서 childeren을 가질 수 있도록 함.
    • 레이아웃 컴포넌트에서 필수 유틸리티 타입임.
  • function MyPageLayout({ childeren }: PropsWithChildren) {}
    • 파일 이름은 layout.tsx로 Next.js의 예약어이지만 내용물은 함수형 컴포넌트여야 함.
    • 함수형 컴포넌트의 이름은 보통 본인의 페이지의 이름 + layout으로 작명. (자유)
    • Props로 위에서 import한 children을 받음. 그리고 이를 JSX로 반환하는 형태.
  • return ()
    • layout.tsx 컴포넌트는 함수형 컴포넌트 JSX를 반환함.
    • 이 return문이 레이아웃 컴포넌트의 핵심. page.tsx의 공통 레이아웃을 작성하는 곳임.
  • <div className="flex flex-col min-h-screen items-center justify-start">
    • TailwindCSS를 사용함.
    • flex : mypage를 flex 컨테이너 안에 포함시킬 것임.
    • flex-col : mypage의 자식 요소들을 세로 방향으로 정렬함.
    • min-h-screen: 이 div의 최소 높이를 화면 전체 높이로 설정하여 항상 화면을 꽉 채우게 함.
    • items-center: 자식 요소들을 수평 중앙에 정렬.
    • justify-start: 자식 요소들을 수직 방향으로 상단부터 정렬.
  • <main className="w-full max-w-4xl p-8 rounded">
    • 에니 콘텐츠 영역으로 설정.
      • w-full: 전체 너비를 사용하여 화면 너비에 맞춤.
      • max-w-4xl : 최대 너비를 4xl로 제한하여 너무 넓어지지 않게 함.
        • 카드 리스트를 구현해보고 뒤에 수정한 것. 카드 리스트의 크기를 고려했을 때 flex다 보니 한 줄에 3개씩 렌더링 되는 것이 가장 보기 좋았음. 카드의 크기를 늘리거나 줄여도 되나, 전체 크기를 너비를 수정함.
        • 그러나 다른 페이지와 너비가 달라 페에지의 너비를 별도로 잡는 것은 팀 프로젝트에서 고려해야 할 부분.
      • p-8: 패딩 8값(TailwindCSS의 단위. 1이 4px로 치환됨. 실제론 rem 단위로 해 두었음.)
      • rounded: border-radius 값 약간 지정.
  • {children}
    • 이 위치에 자식 요소들이 렌더링 됨. 즉 자식 요소는 page.tsx를 말함. 이 요소들은 위의 스타일링을 공통적으로 먹게 됨.
    • 만약 page내에서 여러 컴포넌트들이 혼재되어 있고 공통적으로 보여줘야 하는 헤더, 푸터 같은 요소가 있다면 이 children 위에 import하면 상시 노출 가능.

src/app/(provider)/(root)/mypage/page.tsx

import MyProfile from "@/components/MyPage/ProfileEdit/MyProfile";
import MyPostViewSwitcher from "@/components/MyPage/PostView/MyPostViewSwitcher";
export default function MyPage() {
  return (
    <div className="w-full">
      <div className="p-4 mx-auto max-w-4xl w-full">
        <MyProfile />
        <div className="mt-32">
          <span className="text-2xl font-bold">내가 쓴 게시글</span>
          <hr className="border-gray-300 w-full mt-4" />
        </div>
        <div className="mt-8">
          <MyPostViewSwitcher />
        </div>
      </div>
    </div>
  );
}
  • 실제로 /mypage 라는 URL segment를 갖는 페이지를 렌더링 할 page.tsx이다.
  • page.tsx는 껍데기만 존재할 뿐 내부에는 전부 컴포넌트로 분리시켰고, 추후 살펴보겠지만 내부 컴포넌트는 전부 클라이언트 컴포넌트이기 때문에 사실상 인덱싱 할 요소가 없어 이는 서버 컴포넌트가 아닌 클라이언트 컴포넌트와 다를 것이 없다.
  • 하지만 마이페이지는 인가가 필요한 페이지이고, 검색엔진 노출이 필요 없기 때문에 처음부터 클라이언트 컴포넌트로 작성해도 무방하다는 점을 생각하고 페이지를 작성하면 더 좋다.
  • 이 페이지의 CSS와 import에 대한 설명은 새얅함.
    • MyProfile (유저의 프로필을 나타내는 영역) 컴포넌트와 MyPostViewSwitcher (유저가 작성한 게시글을 나타내는 영역) 컴포넌트를 import하여 페이지를 구성하고 있다는 점만 보면 됨.

 

댓글