Skip to content

[4주차] 이승연 과제 제출합니다.#18

Open
a-00-a wants to merge 107 commits intoCEOS-Developers:masterfrom
a-00-a:a-00-a
Open

[4주차] 이승연 과제 제출합니다.#18
a-00-a wants to merge 107 commits intoCEOS-Developers:masterfrom
a-00-a:a-00-a

Conversation

@a-00-a
Copy link
Copy Markdown

@a-00-a a-00-a commented Apr 25, 2026

배포링크:

https://ceos-week3-react-messenger-23rd.vercel.app

피그마 링크:

Figma

QA노션 링크:

QA Notion

추가구현기능:

채팅목록 핀(Pin) 기능
-핀 고정: 이름 또는 멤버수 옆(투명도: 0인 상태) 을 클릭하면 리스트 맨 위에 고정됨
-핀 해제: 핀아이콘 누르기 -> 가장 아래쪽 핀 바로 아래로 이동함.

Review Questions

  • React Router의 동적 라우팅(Dynamic Routing)이란 무엇이며, 언제 사용하나요?
    : 동적 라우팅 = URL의 일부를 변수처럼 받아서, 같은 컴포넌트로 여러 페이지를 처리하는 방식입니다.
    사용자 프로필, 게시글 수정 페이지, 블로그 상세 페이지, 상품 상세 페이지처럼 같은 화면 구조지만 데이터만 다른 페이지에 사용합니다.

  • 네트워크 속도가 느린 환경에서 사용자 경험을 개선하기 위해 사용할 수 있는 UI/UX 디자인 전략과 기술적 최적화 방법은 무엇인가요?
    : 우선 사용자가 멈춘 화면이라고 느끼지 않게 로딩 상태를 보여주거나 전체 데이터를 다 기다리지 않고, 가능한 부분부터 보여줄 수 있습니다.
    기술적 최적화로는 이미지 용량 줄이기, React Query와 같은 라이브러리를 사용하여 서버 데이터를 캐싱해 재사용하는 방법이 있습니다.

  • React에서 useState와 useReducer를 활용한 지역 상태 관리와 Context API 및 전역 상태 관리 라이브러리의 차이점을 설명하세요.
    : useState는 모달 열림 여부, 입력값, 버튼 클릭 상태, 간단한 카운터와 같은 간단한 지역 상태에 적합합니다. 반면 useReducer는 장바구니와 같이 상태 변경 로직이 복잡할 때 사용합니다.
    Context API는 여러 컴포넌트에 값 전달할 때 적합하고, 전역 상태 라이브러리는 크고 복잡한 앱의 전역 상태 관리에 적합합니다.

a-00-a added 30 commits March 24, 2026 00:19
a-00-a added 28 commits April 6, 2026 00:57
Copy link
Copy Markdown

@waldls waldls left a comment

Choose a reason for hiding this comment

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

4주차 과제 수고하셨습니다!

현재 배포된 환경에서 특정 페이지 접속 후 새로고침을 하면 404 Not Found 에러가 발생하고 있습니다!
SPA 특성상 클라이언트 라우팅을 사용하기 때문에, Vercel이 모든 요청을 index.html로 보내주도록 하는 설정이 필요합니다. 프로젝트 루트에 vercel.json 파일을 추가하고 아래 설정을 적용해 주시면 해결될 것 같습니다!!

{
  "rewrites": [
    {
      "source": "/(.*)",
      "destination": "/index.html"
    }
  ]
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

현재 아이콘 파일명이 Frame 73.svg로 되어 있어, 이름만으로는 어떤 역할을 하는 아이콘인지 파악하기가 어려울 것 같습니다! (또한 이 파일은 public/images/ 경로에도 똑같이 들어있는 것 같습니다!)
그리고 프로젝트 내에 하이픈(-), 언더바(_), 대소문자가 혼용되고 있는데, 아이콘 네이밍 컨벤션을 하나로 통일해보는 건 어떨까요? 규칙이 일관되면 나중에 아이콘이 많아져도 검색이나 관리가 훨씬 수월해질 것 같습니다.

Comment on lines +22 to +24
<button type="button" onClick={handleCopy}>
<img src={copyIcon} alt="복사" className="h-5 w-5" />
</button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

현재 프로젝트에 SVGR 설정이 되어 있는 것으로 알고 있습니다!

<img> 태그로 SVG를 불러오면 나중에 아이콘 색상을 변경하거나 스타일을 확장할 때 제약이 생길 수 있어요. 아래처럼 SVGR 컴포넌트 방식으로 교체하면 className이나 fill 등을 props로 직접 제어할 수 있어 훨씬 유연해질 것 같습니다.

import CopyIcon from '@/assets/icons/copy.svg?react'; // 경로 수정 필요

<button type="button" onClick={handleCopy}>
  <CopyIcon className="size-5" aria-label="복사" />
</button>

Comment on lines +12 to +22
const mockFriends: Friend[] = [
{ id: 1, name: '강감찬', status: 'read' },
{ id: 2, name: '김씨', status: 'yet' },
{ id: 3, name: '나훈아', status: 'none' },
{ id: 4, name: '남궁선', status: 'read' },
{ id: 4, name: '박지훈', status: 'none' },
{ id: 4, name: '백하린', status: 'yet' },
{ id: 4, name: '이지후', status: 'none' },
{ id: 4, name: '한다현', status: 'none' },
{ id: 4, name: '한아현', status: 'none' },
];
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

mockData는 컴포넌트 파일 내에 두기보다는 src/mocks 혹은 별도의 constants 파일로 분리하는 건 어떨까요?!
로직과 데이터를 분리하면 컴포넌트 코드가 훨씬 간결해질 것 같습니다.
그리고 현재 데이터에 id: 4가 중복되어 있는데, 분리하면서 고유한 ID 값으로 수정해 주시면 더 좋을 것 같아요!

Comment on lines +52 to +53
{/*마지막 메시지*/}
<p className="max-w-[198px] h-4 Caption01R text-gray-60">{lastMessage?.messages ?? room.lastMessage}</p>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Image

현재는 메시지를 길게 보내고 나오면, 말줄임 처리가 되지 않아 레이아웃을 뚫고 나가는 현상이 있는 것 같습니다.

태그에 Tailwind의 `truncate` 클래스를 사용하시면 ... 와 같이 말줄임 처리가 가능할 것 같습니다. 부모가 flex 요소일 때는 자식의 최소 너비값 때문에 말줄임이 안 먹힐 수 있으니, `min-w-0` 도 같이 추가해주시는 걸 추천드려요!

Comment on lines +36 to +41
//전송 후 높이 초기화
if (textareaRef.current) {
textareaRef.current.style.height = 'auto';
}
}
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

메시지 전송 후에 입력창 높이가 자동으로 원래대로 돌아오도록 처리하신 부분이 인상 깊어요!
ux 측면에서 세심함이 느껴지는 것 같습니다 ㅎㅎ

Copy link
Copy Markdown

@ryu-won ryu-won left a comment

Choose a reason for hiding this comment

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

추가 기능까지 구현하시고 부드러운 효과까지 나서 UI가 더 예쁘게 느껴졌던 것 같습니다! 시험기간이셨을텐데 고생많으셨어요~!

onClick={() => navigate('/')}
className="flex h-8 w-8 items-center justify-center rounded-full transition-colors hover:bg-gray-30"
>
<img src={backIcon} alt="뒤로가기" className="h-8 w-8" />
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

cusor-pointer있으면 좋을 것 같아요~!

Comment on lines +7 to +12
.Heading01R {
@apply text-[20px] font-normal tracking-[-1px] leading-[1.2];
}
.Heading01SB {
@apply text-[20px] font-semibold tracking-[-1px] leading-[1.2];
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

디자인 시스템 이름 자체를 컴포넌트 클래스로 등록하신점 잘하셨네요..!! 이러면 피그마보면서 바로바로 클래스로 넣을 수 있어서 장점인 것 같습니다:)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

유틸분리도 잘되어있네요~

const storedMessages = localStorage.getItem(MESSAGE_STORAGE_KEY);

if (storedMessages) {
return JSON.parse(storedMessages) as Message[];
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

as 단언은 런타임 검증이 전혀 없어서 스토리지 데이터가 오염되면 런타임 오류가 발생될 수 있기에.
최소한 try-catch라도 넣으면 좋을 듯합니다!

const getInitialMessages = (): Message[] => {
  try {
    const stored = localStorage.getItem(MESSAGE_STORAGE_KEY);
    if (!stored) return initialMessages;
    return JSON.parse(stored) as Message[];
  } catch {
    return initialMessages; // 파싱 실패 시 초기값 fallback
  }
};

Comment on lines +20 to +37
const getCurrentTime = () => {
const now = new Date();
const hours = now.getHours();
const minutes = String(now.getMinutes()).padStart(2, '0');
const period = hours < 12 ? 'am' : 'pm';
const displayHour = hours % 12 === 0 ? 12 : hours % 12;

return `${displayHour}:${minutes}${period}`;
};

const getCurrentDate = () => {
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth() + 1;
const date = now.getDate();

return `${year}년 ${month}월 ${date}일`;
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

유틸함수로 따로 구분해도 좋을 듯 합니다!

"@/shared/*": ["shared/*"],
"@/app/*": ["app/*"],
"@/entities/*": ["entities/*"],
"@/features/*": ["features/*"],
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

features 폴더는 있는데 페이자가 없는 것 같습니다!

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.

3 participants