From a5af41df04c971ee6991598ab00cee4fac289756 Mon Sep 17 00:00:00 2001 From: KimGaeun Date: Thu, 14 Dec 2023 22:53:43 +0900 Subject: [PATCH 1/6] =?UTF-8?q?=F0=9F=93=9D=20Edit=20readme=20contents?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Minboy Co-authored-by: bananaba --- README.md | 134 ++++++++++++++++++++++++------------------------------ 1 file changed, 59 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 8493498..290ae7f 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ 3D 기반 웹 추억 저장 서비스 +[<별 하나에 글 하나> 사용해보기](https://www.xn--bj0b03z.site/) +
남겨두고 싶은 순간을 찍은 사진과, 그 순간을 떠올리며 적은 글을 별에 담습니다. @@ -29,16 +31,17 @@ ### [1. 프로젝트 소개](#%EF%B8%8F-프로젝트-소개) - [<별 하나에 글 하나>를 만들게 된 계기](#별-하나에-글-하나를-만들게-된-계기) -- [기능 설명](#기능-설명) +- [주요 기능 설명](#주요-기능-설명) - [프로젝트 실행 방법](#프로젝트-실행-방법) ### [2. 기술 스택](#%EF%B8%8F-기술-스택) ### [3. 기술적 경험](#-기술적-경험) + - [FE](#FE) - [R3F Camera](#r3f-camera) - [성능 최적화](#성능-최적화) - - [FSD 아키텍처](#fsd-아키텍처) + - [FSD 아키텍처](#fsd-아키텍처) - [BE](#BE) - [TDD, e2e 및 유닛 테스트](#tdd-e2e-및-유닛-테스트) - [인증/인가](#인증인가) @@ -48,6 +51,7 @@ - [admin 페이지 구현](#admin-페이지-구현) ### [4. 팀원 소개](#%EF%B8%8F-팀원-소개) + - [J010 김가은](#-j010-김가은-fe) - [J016 김동민](#-j016-김동민-fe) - [J053 박재하](#-j053-박재하-be) @@ -74,37 +78,11 @@
-## 기능 설명 - -### [ 랜딩페이지 ] - - - -- 왼쪽 위의 버튼을 이용해 배경음악을 끄고 켤 수 있습니다. -- 마우스의 움직임에 따라 배경의 은하가 움직입니다. -- f9를 눌러 전체화면으로 변경할 수 있습니다. - -### [ 회원가입 ] +## 주요 기능 설명 - - +wiki에서 더 많은 기능을 살펴볼 수 있습니다. -- 중복확인, 형식 검사 등을 거친 올바른 아이디 / 비밀번호 / 닉네임으로 회원가입할 수 있습니다. - -### [ 로그인 ] - - - -- 로그인 후 홈화면으로 이동할 때 화면전환 애니메이션이 발생합니다. -- 네이버 , 깃허브, 구글 소셜 로그인이 가능합니다. -- 오른쪽 위 '예시 은하 구경하기' 버튼을 누르면 로그인하지 않고도 은하를 구경해볼 수 있습니다. - -### [ 코치마크 ] - - - -- 사용자에게 기본적인 서비스 사용법을 알려주는 기능입니다. -- 첫 로그인 시에는 기본으로 뜨며, 이후에는 하단바의 물음표 버튼을 눌러 다시 볼 수 있습니다. +[🔗 wiki 프로젝트 소개 바로가기](https://github.com/boostcampwm2023/web16-B1G1/wiki) ### [ 글 조회 ] @@ -140,15 +118,6 @@ - 오른쪽 위의 되돌리기 버튼을 누르면 수정 이전의 내 은하 스타일로 돌아갑니다. - 왼쪽 아래의 초기화 버튼을 누르면 기본 은하 스타일로 돌아갑니다. -### [ 사용자 우주 설정 ] - - - -- 별의 밝기를 조절할 수 있습니다. -- 블러효과를 주어 우주에 흐림 효과를 줄 수 있습니다. -- 마우스 휠 속도를 조절할 수 있습니다. -- 우주의 소유주와 관계없이 내가 보는 화면에만 적용되는 속성입니다. - ### [ 은하 공유 ] @@ -219,14 +188,20 @@ yarn workspace server start:dev ### R3F Camera -3D 공간 상에서 카메라는 사용자의 시야와 같습니다. -그래서 카메라 움직임은 사용자 경험에 직결됩니다. +3D 공간 상에서 카메라는 사용자의 시점입니다. +그렇기 때문에 카메라 움직임은 사용자 경험에 직결됩니다. 저희는 `자연스러운 카메라 움직임`을 만들어내 사용자 경험을 향상시키기 위해 여러 과정을 거쳤습니다. 저희 서비스에서 별을 클릭하면 해당 별을 바라보도록 해야 합니다. 처음에는 카메라의 위치는 그대로 둔 채 시야만 회전하도록 하는 `회전 운동`의 방식을 사용했습니다. -그 다음에는 해당 별로 가까워지는 `직선 운동`으로 변경했습니다. -결과적으로는 회전운동과 직선운동의 장점을 합친 `포물선 운동` 방식을 적용해 훨씬 자연스러운 구현해냈습니다. +처음 `회전 운동` 방식을 적용해본 결과, 별을 바꿀때마다 별과 카메라 사이의 거리를 직접 조정해 줘야 한다는 문제가 있었습니다. + +그래서 별과 카메라 사이 거리를 유지한 채 별을 향해 `직선 운동` 하도록 변경했습니다. +이 방식은 `회전 운동`에 비해 사용하기 편했으나, 움직임이 너무 뻣뻣했기에 더 부드러운 모션을 추가하면 좋겠다는 생각을 하게 되었습니다. + +많은 고민 끝에 회전 운동처럼 별을 향해 회전하고 직선 운동처럼 별에 다가가도록 하여 '포물선 운동'을 만들어 냈습니다. +`포물선 운동`은 회전 운동의 장점인 자연스러운 움직임과 직선 운동의 장점인 직관적인 움직임을 모두 가졌습니다. +이러한 이유로 저희는 `포물선 운동`을 적용하게 되었습니다. - 직선 운동하는 카메라 @@ -236,62 +211,73 @@ yarn workspace server start:dev -하지만 아직도 문제가 남아있었습니다. -거리가 먼 별에 가까워지기까지 너무 많은 시간이 걸렸고, 멀리 있는 별은 너무 작게 보였습니다. -어찌보면 당연한 이야기일 수 있지만, 서비스 특성상 사용자 입장에서는 불편할 수 있는 요소였습니다. +하지만 아직 멀리 있는 별이 너무 작게 보이는 문제가 남아있었습니다. +어찌보면 당연한 이야기일 수 있지만, 서비스 특성상 사용자 입장에서 불편한 요소였고 시각적으로 좋지 않았습니다. +그래서 거리에 비해서 물체가 커 보이게 처리해 멀리 있는 별이 너무 작아보이지 않도록 했습니다. -그래서 멀리 이동할 때는 좀 더 빠르게, 가까이 이동할 때는 좀 더 느리게 이동하는 움직임을 구현했습니다. -또 거리에 비해서 물체가 커 보이게 처리함으로써 멀리 있는 별도 너무 작아보이지 않게 했습니다. +그랬더니 거리가 먼 별이 겉보기보다 멀리 위치하게 되는 문제가 발생했습니다. +사용자가 그 별로 이동하는데 예상하는 것보다 많은 시간이 소요되었습니다. +이 문제를 해결하기 위해 멀리 이동할 때는 좀 더 빠르게, 가까이 이동할 때는 좀 더 느리게 이동하도록 처리했습니다.
### 성능 최적화 -한 화면에 매우 많은 오브젝트를 보여줘야 하는 프로젝트 특성상 실행 환경에 많은 영향을 받기 때문에 최적화가 필수적이었습니다. - -이에 여러 최적화 방법 중 우리 프로젝트에 적용하기 가장 적합한 `Instancing`과 `Performace Moritoring`을 사용해 최적화를 진행했습니다. +저희는 은하를 만들기 위해 수많은 별 오브젝트들을 화면에 띄워야 했습니다. +하지만 별 개수를 늘릴수록 화면이 더 버벅이기 시작했습니다. +별 개수를 줄이면 시각적으로 좋지 않았기에, 저희는 별 개수를 유지하면서도 화면이 버벅이지 않도록 최적화를 시도하게 되었습니다. 1. Instancing - CPU가 GPU에게 무엇을 어떻게 그릴지 지시하는 `Draw Call`은 단순해 보이지만 상당히 무거운 작업입니다. 일반적인 컴퓨터 환경에서 Draw Call이 대략 1000회 넘어가 프레임 드랍이 생긴다는 점을 고려하면, 배경별만 4000개인 우리 프로젝트는 이 `Draw Call`을 줄여줘야 했습니다. + 저희가 선택한 첫 번째 최적화 방식은 `Instancing`이었습니다. + + CPU가 GPU에게 무엇을 어떻게 그릴지 지시하는 `Draw Call`은 단순해 보이지만 상당히 무거운 작업입니다. 일반적인 컴퓨터 환경에서 Draw Call이 대략 1000회 넘어가면 프레임 드랍이 생긴다고 합니다. 은하를 구성하는 별 오브젝트만 4000개인 저희 프로젝트에서 이러한 `Draw Call`을 줄이는 것이 중요햐다고 생각했습니다. + + 이를 위해 사용한 방식이 `Instancing`으로, 동일한 오브젝트를 여러 번 그리는 경우 이를 한번에 처리하도록 하는 방식입니다. 저희는 이를 `InstancedMesh`를 사용해 구현했습니다. 이 방식을 통해 은하를 구성하는 별을 종류별로 묶어줌으로써 4000개의 오브젝트를 13개의 인스턴스로 줄일 수 있었습니다. 이렇게 `Draw Call`에 의한 CPU 병목 현상을 해결했습니다. - 이를 위해 사용한 방식이 `Instancing`으로, 동일한 오브젝트를 여러 번 그리는 경우 이를 한번에 처리하도록 하는 방식입니다. 이 방식을 통해 배경별을 종류별로 묶어줌으로써 4000개의 오브젝트를 13개의 오브젝트로 줄였습니다. 이렇게 `Draw Call`에 의한 CPU 병목 현상을 해결했습니다. +
+ +하지만 금요일 프로젝트 현황 공유 시간 때 '처음으로 맥북 팬 소리를 들었어요', '컴터가 안좋아서 그런지 느려요ㅠㅜㅠ' 같은 피드백을 들으면서 추가적인 최적화 작업의 필요성을 느꼈습니다. + +
- 하지만 금요일 프로젝트 현황 공유 시간 때 '처음으로 맥북 팬 소리를 들었어요', '컴터가 안좋아서 그런지 느려요ㅠㅜㅠ' 같은 피드백을 들으면서 추가적인 최적화 작업에 대한 필요성을 느꼈습니다. +2. Performance Monitoring -2. Performance Monitoring + 피드백을 받은 이후 선택한 것은 `Performance Monitoring`입니다. 다양한 최적화 방식이 있었으나 프로젝트에서 사용하는 대부분의 오브젝트가 매우 단순한 형태라 그리 효과적이지 않았습니다. 이에 선택한 방법이 `Performance Monitoring`으로, 실시간으로 웹의 퍼포먼스를 모니터링해 이를 반영하는 방식입니다. - 피드백을 받은 이후 선택한 것은 Performance Monitoring을 사용한 방식입니다. 다양한 최적화 방식이 있었으나 프로젝트에서 사용하는 대부분의 오브젝트가 매우 단순한 형태라 그리 효과적이지 않았습니다. 이에 선택한 방법이 `Performance Monitoring`으로, 실시간으로 웹의 퍼포먼스를 확인해 이를 반영하는 방식입니다. + react-three/drei 라이브러리의 `Performance Monitor`를 통해 웹의 퍼포먼스를 모니터링합니다. 그리고 퍼포먼스가 좋지 않은 경우 Canvas의 `Device Pixel Ratio`을 최대 0.5까지 낮춥니다. 은하의 해상도를 낮추어 프레임 드랍을 해결하는 방식입니다. 이렇게 CPU만 고려하던 1번 방식에서 나아가 GPU의 부담까지 덜어주는 방식을 추가함으로써 더 최적화된 서비스를 만들 수 있었습니다. - 퍼포먼스가 좋지 않은 경우 (프레임 드랍 시) `Device Pixel Ratio`을 최대 0.5까지 낮추어 은하의 해상도가 낮아집니다. 이렇게 CPU만 고려하던 1번 방식에서 나아가 GPU의 부담까지 덜어주는 방식을 추가함으로써 더 최적화된 서비스를 만들 수 있었습니다. + 아래 사진 중 왼쪽은 최고 해상도인 경우이고, 오른쪽은 최저 해상도인 경우입니다. - 아래 사진 중 왼쪽은 최고 해상도인 경우이고, 오른쪽은 최저 해상도인 경우입니다. + + - - + 아래 사진은 메모리 사용량을 비교한 것으로, Performance Monitoring 최적화 전 13.46GB였던 메모리 사용량이 최적화 후 12.50GB까지 감소했습니다. - 아래 사진은 메모리 사용량을 비교한 것으로, Performance Monitoring 최적화 전 13.46GB였던 메모리 사용량이 최적화 후 12.50GB까지 감소했습니다. + + - - + 아래 사진은 퍼포먼스를 비교한 것으로, GPU 전력 사용량이 0.91 에서 0.62로 감소했고 GPU 사용률이 66에서 51로 감소했습니다. + + +
### FSD 아키텍처 -프로젝트를 진행함에 따라 파일 분리와 폴더 구조에 대한 명확한 원칙이 필요해졌습니다. -그래서 팀원들이 함께 폴더 구조에 대해 조사해보았고, 결과적으로 [FSD(Feature-Sliced Design)](https://feature-sliced.design/) 아키텍처를 적용하게 되었습니다. +프로젝트를 진행함에 따라 파일들이 점점 많아졌고, 파일 분리와 폴더 구조에 대한 명확한 원칙이 필요해졌습니다. +그래서 팀원들이 함께 여러 폴더 구조와 아키텍쳐들에 대해 조사해보았고, 결과적으로 [FSD(Feature-Sliced Design)](https://feature-sliced.design/) 아키텍처를 적용하게 되었습니다. -규모가 작은 저희 프로젝트와 달리, FSD 방식은 폴더를 세세하게 나누는 만큼 규모가 큰 프로젝트에 적합하다는 생각도 했습니다. -하지만 해당 방식의 장점이 매력적으로 다가오기도 했고, 이 프로젝트는 학습의 목적이 크기 때문에 팀원들 모두 새로운 폴더구조를 적용해보고 싶어했습니다. +저희 프로젝트는 상대적으로 규모가 작은 편인데, FSD 방식은 폴더를 세세하게 나누는 만큼 규모가 큰 프로젝트에 적합하다는 생각도 했습니다. +하지만 프로젝트를 분할하여 정복하는 해당 방식의 장점이 매력적으로 다가오기도 했고, 이 프로젝트는 학습의 목적이 크기 때문에 팀원들 모두 새로운 폴더구조를 적용해보고 싶어했습니다. _출처: https://feature-sliced.design/_ -FSD 아키텍처는 app, pages, widgets, features, entities, shared라는 6개의 `Layer`로 이루어져있습니다. 그리고 각각의 `Layer`는 `Slice`들로 이루어져있고, 그 `Slice`는 `Segment`로 이루어져있습니다. -저희 팀은 이렇게 각자의 역할이 분명한 폴더구조를 적용해봄으로써 모듈을 만들 때 각 모듈의 역할을 명확히 정의하게 되었습니다. -또 독립적으로 기능하는 `Slice` 단위로 개발함으로써 유지보수성을 높였습니다. +FSD 아키텍처는 app, pages, widgets, features, entities, shared라는 6개의 `Layer`로 이루어져있습니다. 그리고 각각의 `Layer`는 `Slice`들로 이루어져있고, 그 `Slice`는 `Segment`로 이루어져있습니다. 하위요소들을 조합하여 상위 요소를 구성하는 방식으로, 이 매커니즘이 저희에게 굉장히 매력적으로 다가왔습니다. +이렇게 각자의 역할이 분명한 폴더구조를 적용해봄으로써 모듈을 만들 때 각 모듈의 역할을 명확히 정의하게 되었습니다. 또한 하위 요소들이 모두 개별적으로 기능할 수 있기 때문에 훨씬 유지보수성이 높은 코드를 작성할 수 있게 되었습니다. -아래는 저희의 폴더구조입니다. +아래는 저희 프로젝트의 폴더구조입니다. ``` 📦src @@ -456,14 +442,12 @@ Interceptor, Exception Filter 등 학습을 하고 백엔드 코드에 적용을 ### admin 페이지 구현 리액트를 경험해보고 싶어서 Vite + React + TS를 활용해 간단한 admin 페이지를 만들어보았습니다. -admin용 계정 정보를 설정하고, 게시글 관리 및 컴퓨터 자원 사용량, 에러 로그의 차트를 볼 수 있습니다. +admin용 계정 정보를 설정하고, 게시글 관리 및 컴퓨터 자원 사용량, 에러 로그의 차트를 볼 수 있습니다. ![image](https://github.com/boostcampwm2023/web16-B1G1/assets/101378867/780802e8-94f2-427d-b5c5-96e4f03dea17) ![image](https://github.com/boostcampwm2023/web16-B1G1/assets/101378867/94877604-5f54-4807-8570-d7d7aa699c65) - -
# 🏃‍♂️ 팀원 소개 From d51655f3d96e07c466a95b80e1f9ba200e867511 Mon Sep 17 00:00:00 2001 From: KimGaeun Date: Thu, 14 Dec 2023 23:06:24 +0900 Subject: [PATCH 2/6] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Add=20FadeoutScreen=20?= =?UTF-8?q?onAnimationEnd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FadeoutScreen에 onAnimationEnd 추가 - isSwitching === 'end'일 경우를 WarpScreen 내에서 처리하도록 함 - 함수 오타 수정 --- packages/client/src/pages/Home/Home.tsx | 5 ++--- packages/client/src/widgets/warpScreen/WarpScreen.tsx | 7 +++++-- packages/client/src/widgets/warpScreen/ui/SpaceWarp.tsx | 6 +++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/client/src/pages/Home/Home.tsx b/packages/client/src/pages/Home/Home.tsx index b8862df..158c873 100644 --- a/packages/client/src/pages/Home/Home.tsx +++ b/packages/client/src/pages/Home/Home.tsx @@ -95,11 +95,10 @@ export default function Home() { <> {status === 'new' && } - {isSwitching !== 'end' && ( - - )} {text && {text}} + + diff --git a/packages/client/src/widgets/warpScreen/WarpScreen.tsx b/packages/client/src/widgets/warpScreen/WarpScreen.tsx index d2232af..0de1210 100644 --- a/packages/client/src/widgets/warpScreen/WarpScreen.tsx +++ b/packages/client/src/widgets/warpScreen/WarpScreen.tsx @@ -36,7 +36,11 @@ export default function WarpScreen({ isSwitching, setIsSwitching }: PropsType) { zIndex: 999, backgroundColor: theme.colors.background.bdp04, }; - if (isSwitching === 'fade') return ; + + if (isSwitching === 'end') return null; + + if (isSwitching === 'fade') + return setIsSwitching('end')} />; return ( @@ -50,7 +54,6 @@ export default function WarpScreen({ isSwitching, setIsSwitching }: PropsType) { - diff --git a/packages/client/src/widgets/warpScreen/ui/SpaceWarp.tsx b/packages/client/src/widgets/warpScreen/ui/SpaceWarp.tsx index 009498c..5e9728a 100644 --- a/packages/client/src/widgets/warpScreen/ui/SpaceWarp.tsx +++ b/packages/client/src/widgets/warpScreen/ui/SpaceWarp.tsx @@ -13,7 +13,7 @@ import { } from '../lib/constants'; import { WarpStateType } from 'shared/lib'; -const geSpaceWarpLinesInfo = () => { +const getSpaceWarpLinesInfo = () => { const positions = Array.from({ length: SPACE_WARP_LINES_NUM }, () => { const x = getRandomFloat(SPACE_WARP_XZ_MIN, SPACE_WARP_XZ_MAX); const y = getRandomFloat(SPACE_WARP_Y_MIN, SPACE_WARP_Y_MAX); @@ -38,10 +38,10 @@ interface PropsType { } export default function SpaceWarp({ setIsSwitching }: PropsType) { - const [positions, colors] = useMemo(() => geSpaceWarpLinesInfo(), []); + const [positions, colors] = useMemo(() => getSpaceWarpLinesInfo(), []); useFrame((state, delta) => { - if (state.camera.position.y <= 0) { + if (state.camera.position.y <= SPACE_WARP_Y_MIN) { state.scene.background = new THREE.Color(0xffffff); setIsSwitching('fade'); return; From bce1125d0bc87e0772d6191c432ed7c6ceb1e7b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EA=B0=80=EC=9D=80?= <80266418+KimGaeun0806@users.noreply.github.com> Date: Thu, 14 Dec 2023 23:29:50 +0900 Subject: [PATCH 3/6] Add Readme banner --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 290ae7f..5940097 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,12 @@

별 하나에 글 하나 🌟

+ +

"내 삶의 반짝이는 기억들을 우주에 담아보세요"

3D 기반 웹 추억 저장 서비스 -[<별 하나에 글 하나> 사용해보기](https://www.xn--bj0b03z.site/) -
남겨두고 싶은 순간을 찍은 사진과, 그 순간을 떠올리며 적은 글을 별에 담습니다. @@ -18,7 +18,7 @@
- +[<별 하나에 글 하나> 사용해보기](https://www.xn--bj0b03z.site/) [wiki 바로가기](https://github.com/boostcampwm2023/web16-B1G1/wiki) From db3d9f223b56cc367c77d179806806d412457855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EA=B0=80=EC=9D=80?= <80266418+KimGaeun0806@users.noreply.github.com> Date: Thu, 14 Dec 2023 23:31:12 +0900 Subject: [PATCH 4/6] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5940097..f2ea807 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,9 @@
-[<별 하나에 글 하나> 사용해보기](https://www.xn--bj0b03z.site/) +[✨ <별 하나에 글 하나> 사용해보기](https://www.xn--bj0b03z.site/) -[wiki 바로가기](https://github.com/boostcampwm2023/web16-B1G1/wiki) +[🔗 wiki 바로가기](https://github.com/boostcampwm2023/web16-B1G1/wiki) From e5826553ed030829d14d74739c585bd99cd8bc04 Mon Sep 17 00:00:00 2001 From: MinboyKim Date: Fri, 15 Dec 2023 01:36:16 +0900 Subject: [PATCH 5/6] =?UTF-8?q?=E2=9C=A8=20Post=20limit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 글 수정 제목 및 본문 제한 --- packages/client/src/shared/lib/types/post.ts | 2 + .../src/widgets/postModal/ui/PostModal.tsx | 113 +++++++++++++++--- .../widgets/writingModal/ui/WritingModal.tsx | 3 +- 3 files changed, 99 insertions(+), 19 deletions(-) diff --git a/packages/client/src/shared/lib/types/post.ts b/packages/client/src/shared/lib/types/post.ts index efaddd6..fb8738b 100644 --- a/packages/client/src/shared/lib/types/post.ts +++ b/packages/client/src/shared/lib/types/post.ts @@ -5,3 +5,5 @@ export interface PostData { images: string[]; like_cnt?: number; } + +export type TextStateTypes = 'DEFAULT' | 'INVALID' | 'OVER'; diff --git a/packages/client/src/widgets/postModal/ui/PostModal.tsx b/packages/client/src/widgets/postModal/ui/PostModal.tsx index 4bfcfa9..dad3bf5 100644 --- a/packages/client/src/widgets/postModal/ui/PostModal.tsx +++ b/packages/client/src/widgets/postModal/ui/PostModal.tsx @@ -6,11 +6,13 @@ import { useLocation, useNavigate, useParams } from 'react-router-dom'; import remarkGfm from 'remark-gfm'; import { instance } from 'shared/apis'; import { useCheckNickName, useFetch, useRefresh } from 'shared/hooks'; -import { PostData } from 'shared/lib'; +import { PostData, TextStateTypes } from 'shared/lib'; import { useCameraStore, useToastStore, useViewStore } from 'shared/store'; import { AlertDialog, Button, Input, Modal, TextArea } from 'shared/ui'; import { deletePost } from '../api/deletePost'; import ImageSlider from './ImageSlider'; +import { Caption } from 'shared/styles'; +import { css } from '@emotion/react'; export default function PostModal() { const [deleteModal, setDeleteModal] = useState(false); @@ -19,6 +21,8 @@ export default function PostModal() { const [title, setTitle] = useState(''); const [isDeleteButtonDisabled, setIsDeleteButtonDisabled] = useState(false); const [isSaveButtonDisabled, setIsSaveButtonDisabled] = useState(false); + const [titleState, setTitleState] = useState('DEFAULT'); + const [contentState, setContentState] = useState('DEFAULT'); const { setToast } = useToastStore(); const { setView } = useViewStore(); @@ -44,7 +48,6 @@ export default function PostModal() { const formData = new FormData(); formData.append('title', title); formData.append('content', content); - try { await instance({ url: `/post/${postId}`, @@ -105,6 +108,8 @@ export default function PostModal() { buttonType="CTA-icon" type="button" onClick={() => { + if (title === '') return setTitleState('INVALID'); + if (content === '') return setContentState('INVALID'); setIsEdit(false); handleEditSave(); }} @@ -160,21 +165,44 @@ export default function PostModal() { )} {isEdit ? ( - setTitle(e.target.value)} - /> -