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

토이프로젝트2 12조 LANGCHAT #10

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

Conversation

Yamyam-code
Copy link

@Yamyam-code Yamyam-code commented Nov 18, 2023

LANGCHAT: 언어 학습을 위한 채팅 앱

패스트캠퍼스 X 야놀자 프론트엔드 부트캠프 토이프로젝트2 12조

프로젝트 소개

제작기간 : 2023.11.06 ~ 2023.11.16
제작인원 : 4명

배포 주소

🌐 배포링크 : https://langchat-464b7.web.app/

🔒 테스트 계정 - ID: test0000 PW: 123123

팀원 소개

팀장 - 백상원 팀원 - 김성겸 팀원 - 지홍규 팀원 - 한은지
@Yamyam-code @skyeome @JiHongkyu @lilviolie
  • 유저 랭킹 리스팅
  • 영어 끝말잇기 구현
  • 레이아웃
  • 회원인증
    로그인/회원가입
  • 오픈채팅기능
  • 초기셋팅
  • 웹소켓 연결/실시간
    채팅 로직 구현
  • 1대1채팅 기능
  • 홈 화면 개발
  • 프로필 수정기능

기술 스택 및 라이브러리

사용 기술

React TypeScript React Router Socket.io Recoil Styled Components FIREBASE Framer MUI
Prettier
Eslint

협업툴

Notion Figma Slack Zoom Jira

🎯 주요 구현 사항

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

선택 구현 사항

  • Next.js를 활용한 서버 사이드 렌더링 구현
  • typescript를 활용한 앱 구현
  • storybook을 활용한 디자인 시스템 구현
  • jest를 활용한 단위 테스트 구현

😀 팀원별 상세 구현 사항

한은지 : 첫 화면, 프로필 페이지

주요 구현사항 설명

홈페이지

홈페이지

  • 상단의 select box에서 선택한 언어에 해당하는 유저 리스트 표시
  • 소켓 연결을 통해 유저 접속 여부 표시
  • 유저 선택 시 나타나는 프로필 모달 구현

프로필

  • 유저가 본인의 정보를 수정할 수 있는 프로필 페이지 구현
지홍규 : 메시지 페이지

주요 구현사항 설명

메시지 보내기 보달

  • 모든 유저 리스트 표시
  • 자음, 모음으로 유저 이름 검색
  • 유저 선택 시 보내기 버튼 활성화
  • 메시지 보내면 기존 채팅방 유무 확인 후 채팅방 생성 or 채팅방 이동

모달창

채팅리스트

  • 로그인 유저가 포함된 채팅 리스트 표시
  • 상대방의 프로필 사진과 이름 표시
  • 해당 채팅방의 마지막 메시지와 경과 시간 표시
  • 로딩 시 채팅 리스트 Skeleton UI 적용

채팅리스트

모든 채팅 가져오기

  • 채팅방 클릭 시 해당 채팅방의 대화 내용 모두 가져오기

모든 채팅가져오기

1대1채팅

  • 로그인한 유저와 대화상대방의 메시지 레이아웃 구분
  • 같은 시간에 보낸 메시지는 마지막 메시지에 한 번만 표시
  • 웹 소켓 연결 및 실시간 통신 로직 구현

채팅방

로그아웃

  • 로그아웃 시 localStorage 값과 accessTokenState 값 초기화 후 로그인 페이지로 이동

로그아웃

김성겸 : 인증관련, 오픈채팅 페이지

유저 인증 / 회원가입

회원가입

  • 아이디 - 중복 아이디 체크 기능
  • 비밀번호 - 5글자 이하인지 유효성 검사
  • 프로필 사진을 정사작형으로 잘라주는 에디터 추가
  • 관심사 선택
  • 언어, 수준 선택

회원가입 페이지

로그인

  • 로그인시 오류 발생하면 에러 메시지를 toast로 보여주는 기능
  • 이미 로그인 되어있으면 로그인 페이지로 갈수 없습니다.

로그인 페이지

오픈 채팅

  • 오픈채팅방 생성 기능
  • 로딩시 skeleton UI 적용
  • 추천친구/대화방 보여주는 기능
  • 채팅방 참여, 나가기, 초대 기능

오픈채팅방 생성
openchat-new

오픈채팅방 초대
openchat-invite

실시간 오픈채팅
openchat-chatting

오픈채팅 나가기
openchat-leave

백상원 : 게임 페이지

주요 구현사항 설명

랭킹

ranking

  • 각 유저의 최고 점수를 기반으로 랭킹을 나열
  • 기본적으로 유저의 등수를 보여주며 호버 시 그 유저의 점수를 표기
  • 게임 플레이 중 최고 점수 갱신를 갱신하면 게임 종료 시 랭킹 리렌더링

끝말잇기 게임

submit

  • 네이버 지식백과 API 및 정규식을 활용하여 단어 유효성 검사
  • 배포서버 API CROS 이슈 AWS API GATEWAY 사용하여 해결(해당 방법이 api 딜레이가 가장 적고 https 프로토콜을 사용)
  • useState와 FRAMER 활용 남은 시간 게이지바로 표시
  • 정답 시 현재 점수를 +1 하며 현재 점수가 최고 점수를 넘어갈 경우 최고 점수 = 현재 점수
  • 효과음 사용

➡️ 유저 흐름(flow) 이미지

LangChat UserFlow

📂 폴더 구조

📦 LangChat
├─ node_modules/
├─ public/
│  ├─ index.html
│  ├─ favicon.ico
│  └─ manifest.json
├─ src/
│  ├─ components
│  │  ├─ common/
│  │  ├─ todo.../
│  │  └─ …/
│  ├─ assets/
│  ├─ common/
│  ├─ pages/
│  ├─ utils/
│  ├─ hooks/
│  ├─ styles/
│  ├─ types/
│  ├─ reducer/
│  ├─ App.js (라우팅까지)
│  └─ index.js
├─ package.json
├─ package-lock.json
└─ README.md

JiHongkyu and others added 30 commits November 7, 2023 12:43
Feat: login token 생성 및 localstorage 저장 기능
Feat: 메인 및 서브 레이아웃 적용
Style: 메인 및 서브 레이아웃 적용
@Yamyam-code Yamyam-code changed the title 토이프로젝트2 12조 12시까지만 하조 토이프로젝트2 12조 Langchat Nov 18, 2023
@Yamyam-code Yamyam-code changed the title 토이프로젝트2 12조 Langchat 토이프로젝트2 12조 LANGCHAT Nov 18, 2023
noSPkeepgoing added a commit that referenced this pull request Nov 18, 2023
🚑Fix: fix way to insert query string
LeHiHo 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.

다양한 라이브러리를 활용해본 것도 보이고요!
map으로 노드를 그릴 때 key값 등 챙긴 것도 꼼꼼함이 돋보입니다.
메모이제이션관련해서 더 개선해보면 좋을 것 같아요!
영어끝말잇기와 같은 게임 기획아이템을 넣어서 더 재밌는 사이트가 된 것 같아요.

추후에 리스팅 부분에 무한 스크롤을 적용해보는 것도 좋을 것 같아요. (요즘 어떤 사이트에서든 너무너무 많이 쓰이는 요소라서, 과제에서도 많이 나옵니다.)

Comment on lines +16 to +39
function App() {
return (
<Routes>
<Route element={<Layout />}>
{/* 상원님 부분 */}
<Route path="/game" element={<Game />} />
{/* 은지님 부분 */}
<Route path="/home" element={<Home />} />
<Route path="/profile" element={<Profile />} />
{/* 홍규님 부분 */}
<Route path="/chat" element={<ChatList />} />
<Route path="/chat/:chatId" element={<ChatRoom />} />
{/* 오픈 채팅 부분 */}
<Route path="/open" element={<Openchat />} />
<Route path="/open/:chatId" element={<OpenchatRoom />} />
</Route>
<Route element={<AuthLayout />}>
{/* 성겸 부분 */}
<Route index element={<SignIn />} />
<Route path="/signup" element={<SignUp />} />
</Route>
</Routes>
);
}

Choose a reason for hiding this comment

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

App 에서는 라우팅만 처리해놓은 게 깔끔하고 좋은 것 같아요~ 현업에서도 많이 쓰는 구조 입니다,

Comment on lines +4 to +7
export const accessTokenState = atom({
key: 'accessTokenState', // unique ID (with respect to other atoms/selectors)
default: localStorage.getItem('accessToken'), // default value (aka initial value)
});

Choose a reason for hiding this comment

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

토큰을 Recoil 이용해서 한번 더 상태관리를 감싼거죠? 이렇게 한 이유는 뭘까요?
로컬스토리지 만으로 했을 때 부족한 점이 있었나요?

Comment on lines +11 to +16
const formik = useFormik({
initialValues: {
id: '',
password: '',
confirmPassword: '',
},

Choose a reason for hiding this comment

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

이런 submit해야 하는 데이터 등을 폼으로 관리할 수 있는 라이브러리들이 유용한 것 같아요
react-hook-form 도 유용한 편입니다~

type="submit"
sx={{ mt: 3 }}
>
다음 단계로 ( 2 / 4 )

Choose a reason for hiding this comment

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

요런건 실제 스텝을 계산해 보여주도록 개선하면 좋을 것 같네요 step 이라는 값을 계산하고 있는 것 같아보여서요~

};
return newForm;
});
// console.log(values);

Choose a reason for hiding this comment

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

운영되는 서비스에서는 주석도 최대한 제거하고, 특히 console.log 는 남기지 않도록 습관을 들이면 좋습니다~

Comment on lines +12 to +19
const handleSelectedUser = () => {
if (selectedUser.id === user.id) {
setSelectedUser({ id: '', name: '', picture: '' });
return;
}

setSelectedUser(user);
};

Choose a reason for hiding this comment

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

요런 걸 useCallback으로 정의해봐도 좋겠죠~

Suggested change
const handleSelectedUser = () => {
if (selectedUser.id === user.id) {
setSelectedUser({ id: '', name: '', picture: '' });
return;
}
setSelectedUser(user);
};
const handleSelectedUser = useCallback (() => {
if (selectedUser.id === user.id) {
setSelectedUser({ id: '', name: '', picture: '' });
return;
}
setSelectedUser(user);
}, [user]}

const inputValue = inputElement?.value;
const existCheck = words?.find((e) => e === inputValue);
// 시작 버튼을 눌렀는지
if (start) {

Choose a reason for hiding this comment

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

조건문 뎁스가 너무 깊어요! 이부분 리팩토링 고려해보면 좋을 것 같아요.
경우마다 플래그를 만들고 리턴해주는 방향으로 가는 게 더 좋아보입니다~

   if(시작안함) 리턴
   if(이미 입력한 단어) 리턴
   if(끝말이 이어지지 않으면) 리턴
   if(형식에 맞지 않으면 ) 리턴

   if(사전 검색 가능한 단어) 어떤작업들~
 

   // ..

Copy link
Author

Choose a reason for hiding this comment

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

조언 감사드립니다! 해당 방법으로 리팩토링 해보겠습니다 ㅎㅎ

Comment on lines +50 to +66
setUserData(exceptMe);
};

useEffect(() => {
fetchData();
}, [language]);

return (
<List>
{userData.length !== 0
? userData.map((data: UserData) => {
const { id } = data;
return <User key={id} data={data} onlineUser={onlineUser} />;
})
: new Array(8)
.fill(0)
.map((_, index: React.Key) => <SkeletonUser key={index} />)}

Choose a reason for hiding this comment

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

exceptMe 가 없으면 빈 array를 userData로 만들고

리턴하는 곳에서는

userData?.map(() => ... ) 이렇게 해도 될 것 같아요.

리턴하는 부분에서는 정말 UI를 "그리는" 관점의 코드만 있는 게 SPA 컴포넌트기반 웹화면 개발에 맞는 것 같습니다.


const hasUserDataExceptMe = data.some((data) => data.id !== user.id)
const userDataArray = hasUserDataExceptMe ? data.filter((data) => data.id !== user.id) : new Array(8).fill(0) ...
setUserData(userDataArray)

// 
return 
...
<List>
  {userData?.map(() => ... }
</List>

Comment on lines +40 to +45
{ReactDom.createPortal(
<BackDrop userData={userData} onCloseModal={onCloseModal} />,
document.getElementById('backdrop-root') as HTMLElement,
)}
</>
);

Choose a reason for hiding this comment

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

createPortal 로 뒷배경에 모달띄울 때는 제거되어야할 때는 없는지, 여러개 쌓이는 경우가 있진 않은지 등등 확인해보는 것도 좋을 것 같아요.

onClickSelectBtn,
selectedIds,
}: OpenchatInviteFriendItemProps) {
const isSeleted = selectedIds.includes(user.id);

Choose a reason for hiding this comment

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

요런 값들을 메모이제이션 하는 거에요!
const isSelected = useMemo(() => selectedIds.includes(user.id), [selectedIds, user])

import convertBase64 from '../../utils/FileToBase64';
import Cropper from '../common/Cropper';

function SignUpForm2({ setStep }: SignUpFormProps) {

Choose a reason for hiding this comment

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

컴포넌트 명을 좀 더 명확히 하면 좋을 것 같습니다~

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.

5 participants