2024-07-11 Next.js 마이페이지 구현 로직
폴더구조
```
📦
.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하여 페이지를 구성하고 있다는 점만 보면 됨.
'Programing > TIL' 카테고리의 다른 글
2024-07-16 User Flow Chart (0) | 2024.07.16 |
---|---|
2024-07-12 Next.js 팀프로젝트 회고(readme) (0) | 2024.07.13 |
2024-07-11 supabase를 클라이언트-서버 컴포넌트에서 사용할 때 클라이언트를 계속 생성해야 하는가? (0) | 2024.07.11 |
2024-07-08 Next.js 마이페이지 구현하기 (1) (0) | 2024.07.09 |
2023-07-03 Next.js + TanStack Query 포켓몬 도감 만들기 (2) | 2024.07.04 |
댓글