Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

토이프로젝트 8조: 채팅 서비스 8LACK #2

Open
wants to merge 478 commits into
base: main
Choose a base branch
from

Conversation

turkey-kim
Copy link


프로젝트 소개

8lack 은 다양한 사람들과 다양한 주제로 이야기를 나눠볼 수 있는 채팅 서비스입니다.

다양한 사람들과 자유롭게 소통을 즐길 수 있으며, 그룹 채팅을 통해 사용자들이 서로의 경험을 공유하고 정보를 교환할 수 있는 커뮤니티입니다.

관련 링크

wiki 배포 레포

개발 기간

2023.11.06 - 11.16

테스트 계정

  • ID : palack
  • PW : test123


📌 요구사항

펼치기

필수

  • useState, useReducer를 활용한 상태 관리 구현
  • Sass 또는 styled-component를 활용한 스타일 구현
  • react 상태를 통한 CRUD 구현
  • 상태에 따라 달라지는 스타일 구현
  • custom hook을 통한 비동기 처리 구현
  • 유저인증 시스템(로그인, 회원가입) 구현
  • jwt등의 유저 인증 시스템 (로그인, 회원가입 기능)
  • 소켓을 이용한 채팅 구현

선택

  • typescript를 활용한 앱 구현



📜 실행 스크립트

$ git clone <https://github.com/turkey-kim/8lack.git>
$ npm ci
$ npm run start


✨ 참여한 사람

FE: 정범환 (팀장) FE: 김민서 FE: 김특희 FE: 박나영 FE: 장수빈
UI/UX 디자인
참여 가능한 그룹채팅방 리스트
새로운 그룹채팅방 연결
채팅방 내 초대기능
로딩 및 결과 없음 상태 컴포넌트
사용자 리스트 페이지
즐겨찾기 기능
1:1 채팅 연결
마이 페이지
메인페이지 - IntroSection
로그인/회원가입 페이지 및 기능
유저인증 처리
유저 권한에 따른 라우팅
활동중 유저 목록 최신화
메인페이지 - CardSection
채팅방 소켓 연결
실시간 메시지 수신/송신
채팅방 내 유저 접속상태 확인
채팅방 나가기 기능
새로운 채팅방 & 유저 초대 시 알림 기능
네비게이션바
사이드바
채팅방 리스트 실시간 업데이트 처리
메인페이지 - HeaderSection
404 페이지


💡 8lack 기능 소개

라우터

채팅 로비 접속 시, 유저 인증 처리 및 서버 소켓을 연결하였습니다.

또한 로그인한 유저가 아니면, 서비스 소개 페이지로 이동하고, 로그인한 유저일 경우 전체 페이지 조회 가능하도록 유저 권한에 따른 라우팅을 설정하였습니다.


로그인 / 회원가입 페이지




로그인 성공 시, 유저 인증 토큰 발급이 발급되고 채팅 로비로 연결됩니다.


회원가입 진행시 입력정보에 대한 유효성을 체크할 수 있는 UI 구성하였습니다. ( 중복확인, 비밀번호 검사 )


사이드바 - 자신이 속한 채팅방 리스트


현재 자신이 속해있는 개인&그룹 채팅방 리스트를 최신 메시지를 받은 순서대로 보여줍니다. 메시지를 받으면 실시간으로 리스트가 업데이트 됩니다.

또한 채팅하고 싶은 방을 클릭 시 바로 채팅에 참여할 수 있습니다.


참여 가능한 그룹채팅방 리스트


아직 내가 속해있지 않은 그룹 채팅방을 조회할 수 있습니다.

이름 및 채팅방 별 최근 채팅을 언제 했는지, 인원이 얼마나 있는지 파악 가능합니다.

또한 정렬을 통해 가나다 순, 최근 채팅 순, 인원 순으로 조회 가능하며 검색도 조합할 수 있습니다.



새로운 그룹채팅방을 만들 수 있습니다.

다른 사용자들을 조회, 추가하여 새로운 그룹채팅방을 생성할 수 있습니다.


사용자 리스트 페이지

이 사이트에 가입한 모든 사용자들을 조회하고, 이름을 검색하여 찾고 싶은 사용자를 찾을 수 있습니다.

원하는 사용자를 즐겨찾기 할 수 있고, 현재 사이트에 접속인 친구를 확인 할 수 있습니다.

또, 원하는 친구와 1:1 채팅을 시작 할 수 있습니다.


채팅 페이지


실시간으로 메시지를 주고 받을 수 있습니다. 채팅방에 연결되자마자 이전 대화 기록이 보이도록 설정했습니다.

Drawer을 열어보면 현재 채팅방 유저의 정보와 현재 접속 상태를 한눈에 파악이 가능합니다.



채팅을 주고 받을 때, 메시지별로 날짜를 그룹화해서 보이도록 했고 새로운 유저가 참여하거나 나갈 시 시스템 메시지도 표시됩니다.

새로운 채팅방이 생성됐거나 기존 채팅방에 유저를 초대할 시 알림 메시지를 받을 수 있습니다.


내 정보 조회 / 수정


자신이 원하는 사진이나 이름으로 변경 할 수 있습니다.

⚒️ 기술 스택

Stack
언어
디자인
서버
라이브러리
협업툴
개발 환경


📌 유저 플로우



🗂️ 파일 구조

📂 src
┣ 📂 api
┣ 📂 assets                   # 폰트, 이미지 ,아이콘
┣ 📂 components               # 공용 컴포넌트
┃  ┣ 📂 Modal
┃  ┣ 📂 SideBar
┃  ┣ ...
┣ 📂 constant
┣ 📂 contexts                 # 소켓 컨택스트
┃  ┣ ChatSocketContext.tsx
┃  ┣ ...
┣ 📂 hooks                    # 커스텀훅
┣ 📂 pages                    # 페이지 컴포넌트
┃  ┣ 📂 Home
┃  ┣ 📂 GroupChatList
┃  ┣ 📂 UserList
┃  ┃  ┣ 📂 components
┃  ┃  ┣ index.tsx
┃  ┣ ...
┣ 📂 routes
┣ 📂 utils
┣ 📂 states                   # 전역상태
┣ 📂 styles                   # 스타일테마
┣ 📂 types                    # 타입스크립트 공용 인터페이스
┣ App.tsx
┣ index.tsx


📍 컨벤션

커밋 컨벤션
feat 새로운 기능 추가
fix 버그 수정
env 개발 환경 관련 설정
style 코드 스타일 수정 (세미 콜론, 인덴트 등의 스타일적인 부분만)
design css 등 디자인 추가 및 수정
refactor 코드 리팩토링
comment 주석 추가/수정
docs 내부 문서 추가/수정
test 테스트 추가/수정
chore 빌드 관련 코드 수정
rename 파일 및 폴더명 수정
remove 파일 삭제


💭 느낀점 및 회고

  • 김민서

    • 팀원분들 모두가 열정이 대단해서 배운 점이 정말 많았고, 처음부터 끝까지 즐거웠던 프로젝트였습니다. 이번 프로젝트를 통해 체계적인 시스템의 중요성을 알게 되었고, 앞으로의 프로젝트에서도 이번에 배운 점들을 적극적으로 적용할 예정입니다.
  • 김특희

    • 처음으로 디자인 시스템까지 적용하여 체계적으로 협업한 프로젝트였습니다. 탄탄한 기획과 체계적인 컨벤션으로 원활하게 협업할 수 있었습니다. 또한 팀원 모두가 프로젝트 구현 뿐만 아니라, 세심한 리포트와 리뷰 같은 협업 자체에도 신경을 많이 써주셔서 귀중한 경험이었습니다.
  • 장수빈

    • 좋은 팀장 & 팀원분들을 만나서 많은 것을 배우고 느끼게 된 프로젝트였습니다. 자기가 맡은 일 뿐 만이 아니라 도움이 필요한 부분이 있으면 적극적으로 도움을 주고받는 과정 덕분에 원활한 협업이 진행되었던 것 같습니다. 이 프로젝트를 통해 함께 성장할 수 있는 특별한 경험을 쌓을 수 있었습니다.
  • 정범환

    • 가장 모르는게 많은 제가 조장을 맡게 되었습니다. 그러나 누구 하나 빠짐없이 자신의 역할 이상을 해내려고 하여 부족한 리딩을 매꿔주고, 나아가서 자신의 맡은 기능을 최고의 완성도로 만드려는 의지가 완연하다는걸 느꼈습니다. 최고의 팀원들 덕분에 좋은 프로젝트를 경험했습니다.
  • 박나영

    • 실시간 서버 통신을 구현하면서 정말 재밋게 배웠던 프로젝트였습니다. 그리고 팀원들과 같은 어려움을 겪으면서 해결방법을 공유했던 것도 정말 좋은 경험이 되었습니다!

im-na0 and others added 30 commits November 13, 2023 08:03
[#34] 나와의 채팅방 생성
[#51] 그룹채팅방 검색 및 정렬 기능 추가
[#47] React Query를 이용한 실시간 업데이트 (그룹채팅방)
[#15] 사용자 리스트 즐겨찾기, 개인 채팅창 생성 기능 구현
[#38] 소켓 동시 연결 시 접속 끊기는 이슈 (임시)해결
TaePoong719 pushed a commit that referenced this pull request Nov 16, 2023
TaePoong719 pushed a commit that referenced this pull request Nov 16, 2023
Feat : 로그인 & 회원가입 구현
2YH02 added a commit that referenced this pull request Nov 17, 2023
Env: install react-icons
seungjun222 pushed a commit that referenced this pull request Nov 17, 2023
dbstjrals pushed a commit that referenced this pull request Nov 17, 2023
…on-bar

네비게이션 바 스타일 오류를 수정한다.
JeongMin83 added a commit that referenced this pull request Nov 17, 2023
Feat: Styled-component 적용 및 Layout 생성
jseo9732 added a commit that referenced this pull request Nov 18, 2023
TALK-20--feat/layout/nav 하단 내비게이션 바 추가
Yamyam-code added a commit that referenced this pull request Nov 18, 2023
noSPkeepgoing added a commit that referenced this pull request Nov 18, 2023
게임 내 헤더, 투표페이지 생성
Copy link

@JIYEONGYANGdev JIYEONGYANGdev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

우선 UI가 정말 깔끔하네요~~ 컨벤션도 맞추고, 가독성있고 맥락에 맞는 로직도 명확한 것 같아요!(중간중간 lodash나 삼항조건식 등등의 코멘트를 남기긴 했지만, 정답은 없고 팀원간 협업하기 좋게 사람의 관점에서 이해가 되는 코드, 가독성이 좋고 맥락이 잘 보이면 그게 최선의 코드라고 생각이 들어요)
웹소켓 사용하면서 많이 고민하며 개발하신 게 느껴집니다.
그리고 useAuthCheck 등 커스텀훅은 잘 만드신 것 같아요. 로그인 여부를 체크해서 라우팅을 달리한다든지에도 이런 식으로 쓸 수 있습니다.
추후에 반응형도 고려해보면 좋을 것 같습니다!
고생하셨습니다👍

+) 회고 내용도 보니 팀내 협업이 잘 이루어진 것 같아서도 보기 좋네요!

@@ -0,0 +1,16 @@
---

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

github활용 너무 좋습니다~

Comment on lines +9 to +24
function App() {
return (
<UserPublicRoute>
<>
<ServerSocketProvider>
<StyledContainer>
<Toast />
<Navigation />
<SideBar />
<Outlet />
</StyledContainer>
</ServerSocketProvider>
</>
</UserPublicRoute>
);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

컴포넌트 수준만 렌더하고,
provider는 최상위 root 에서 감싸도 될 것 같네요! (위계 통일)

try {
const response = await axios.post(
`${SERVER_URL}/login`,
{id: id, password: pw},

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게 동일한 파라미터명-변수 일때는

Suggested change
{id: id, password: pw},
{id, password: pw},

이렇게 줄여써도 됩니다! 웹스톰이나 이런 에디터에서는 아예 이렇게 하라고 워닝 주기도 하더라고요.

Comment on lines +5 to +18
const Avatars: React.FC<IAvatars> = ({users, isPrivate}) => {
if (isPrivate) {
const user = users?.[0];
return <Avatar src={user?.picture} alt={user?.username} />;
} else {
return (
<AvatarsContainer>
{users?.slice(0, 4).map((user, index) => (
<Avatar key={user.id} src={user.picture} alt={user.username} />
))}
</AvatarsContainer>
);
}
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isPrivate이 어떤의미인가요?
isPrivate플래그에 따라 다른 ui를 렌더하는 경우라면 렌더하는 부분에서 갈라주면 될 것 같아요.

컴포넌트의 렌더/ 로직 등 정의하는 공간을 구분하는 것도 맥락 파악에 용이하기도 하고요~

Suggested change
const Avatars: React.FC<IAvatars> = ({users, isPrivate}) => {
if (isPrivate) {
const user = users?.[0];
return <Avatar src={user?.picture} alt={user?.username} />;
} else {
return (
<AvatarsContainer>
{users?.slice(0, 4).map((user, index) => (
<Avatar key={user.id} src={user.picture} alt={user.username} />
))}
</AvatarsContainer>
);
}
};
const Avatars: React.FC<IAvatars> = ({users, isPrivate}) => {
const user = useMemo(() => users?.[0], [users]) // 요럴때 메모이제이션을 하는 거에요!
return (
isPrivate ? <Avatar src={user?.picture} alt={user?.username}/>
: <AvatarsContainer>
{users?.slice(0, 4).map((user, index) => (
<Avatar key={user.id} src={user.picture} alt={user.username}/>
))}
</AvatarsContainer>
)
}

Comment on lines +7 to +45
const Drawer: React.FC<IDrawer> = ({isOpen, onClose, children}) => {
const backdrop = {
visible: {opacity: 1},
hidden: {opacity: 0},
};

const drawer = {
visible: {
x: 0,
transition: {
type: 'spring',
stiffness: 300,
damping: 30,
},
},
hidden: {
x: '100%',
transition: {
type: 'spring',
stiffness: 300,
damping: 30,
},
},
};

return ReactDOM.createPortal(
<AnimatePresence>
{isOpen && (
<>
<MotionBackdrop variants={backdrop} initial="hidden" animate="visible" exit="hidden" onClick={onClose} />
<MotionDrawer variants={drawer} initial="hidden" animate="visible" exit="hidden">
{children}
</MotionDrawer>
</>
)}
</AnimatePresence>,
document.getElementById('drawer-root') as HTMLElement,
);
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요거는 createPortal 으로 만든 것 보니 모달처럼 뒤에 떠있는 것 같은데요,
돔을 지워야할 때는 없는지 나중에 고려해봐도 좋을 것 같아요~


export default function Navigation() {
const [isModalOpen, setIsModalOpen] = useState(false);
const [isLoggedIn, setIsLoggedIn] = useRecoilState(loginState);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

recoil이 깔끔한 편이더라고요. 상태관리 스택으로 좋은 선택같아요~

Comment on lines +49 to +53
<StyledCategoryContainer
onClick={() => {
setIsModalOpen(true);
}}
>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요런건 코드가 길지 않아서 한줄로 나와도 될 것 같아요.
에디터마다 다르지만 printWidth 이런 값으로 설정에서 지정할 수 있으니 활용해보세요!

Comment on lines +67 to +83
newSocket.on('join', (data: NewUser) => {
const joinMessage =
Array.isArray(data.joiners) && data.joiners.length > 0
? typeof data.joiners[0] === 'string'
? data.joiners.map(joiner => ({
id: `join-${joiner}-${uuidv4()}`,
text: `${joiner}님이 입장했습니다.`,
userId: 'system',
createdAt: new Date(),
}))
: data.joiners.map(joiner => ({
id: `join-${joiner.id}-${uuidv4()}`,
text: `${joiner.id}님이 입장했습니다.`,
userId: 'system',
createdAt: new Date(),
}))
: [];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lodash 등을 활용하면 복잡하지 않게 할 수 있을 것 같아요.
꼭 삼항조건식이 아니고,
경우를 나누어 메시지 값을 정의해놓고 사용해도 되고요.

@@ -0,0 +1,15 @@
import { ReportHandler } from 'web-vitals';

const reportWebVitals = (onPerfEntry?: ReportHandler) => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요건 사용을 하고 있나요?

Comment on lines +13 to +14
if (isLoggedIn) return <Navigate to="/" />;
if (!isLoggedIn) return children;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (isLoggedIn) return <Navigate to="/" />;
if (!isLoggedIn) return children;
return (
isLoggedIn ?
<Navigate to="/">
: <>{children}</>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants