Skip to content

Commit

Permalink
Merge pull request #275 from boostcampwm-2024/dev-fe
Browse files Browse the repository at this point in the history
[MERGE] dev-fe to prod-fe
  • Loading branch information
gominzip authored Dec 2, 2024
2 parents 2d8ad06 + a75e50f commit db63848
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 41 deletions.
17 changes: 17 additions & 0 deletions frontend/src/apis/fetchChatRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { AxiosResponse } from 'axios';
import { fetchInstance } from '.';

export type ChatRuleResponse = {
notice: string;
channelName: string;
};

export const fetchChatRule = async ({ sessionKey }: { sessionKey: string }): Promise<ChatRuleResponse> => {
const response: AxiosResponse<ChatRuleResponse> = await fetchInstance().get('/streams/notice', {
params: {
sessionKey
}
});

return response.data;
};
10 changes: 10 additions & 0 deletions frontend/src/apis/queries/chat/useFetchChatRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useQuery } from '@tanstack/react-query';
import { ChatRuleResponse, fetchChatRule } from '@apis/fetchChatRule';

export const useFetchChatRule = ({ sessionKey }: { sessionKey: string }) => {
return useQuery<ChatRuleResponse, Error>({
queryKey: ['chatRule'],
queryFn: () => fetchChatRule({ sessionKey }),
refetchOnWindowFocus: false
});
};
19 changes: 12 additions & 7 deletions frontend/src/components/chat/ChatList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const ChatItemWrapper = memo(
if (chat.msgType === CHATTING_TYPES.QUESTION) {
return (
<ChatItem>
<QuestionCard type="client" question={chat} />
<QuestionCard type="client" question={chat} onNicknameClick={handleNicknameClick} />
</ChatItem>
);
} else if (chat.msgType === CHATTING_TYPES.NOTICE) {
Expand All @@ -43,12 +43,8 @@ const ChatItemWrapper = memo(
return (
<ChatItem>
<NormalChat $isHost={chat.owner === 'host'} $pointColor={chat.owner === 'host' ? '#0ADD91' : chat.color}>
{chat.owner === 'me' ? (
<span className="text_point">🧀</span>
) : chat.owner === 'host' ? (
<StyledIcon as={HostIconGreen} />
) : null}
<span className="text_point" onClick={handleNicknameClick}>
<span className="text_point user_name" onClick={handleNicknameClick}>
{chat.owner === 'me' ? '🧀' : chat.owner === 'host' ? <StyledIcon as={HostIconGreen} /> : null}
{chat.nickname}
</span>
<span className="chat_message">{chat.msg}</span>
Expand Down Expand Up @@ -171,6 +167,15 @@ const NormalChat = styled.div<{ $isHost: boolean; $pointColor: string }>`
line-height: 1.5;
}
.user_name {
cursor: pointer;
padding: 2px;
border-radius: 5px;
&:hover {
background-color: #393939;
}
}
overflow-wrap: break-word;
word-break: break-word;
`;
Expand Down
49 changes: 38 additions & 11 deletions frontend/src/components/chat/ChatQuestionSection.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { memo, useState, useCallback } from 'react';
import styled from 'styled-components';
import QuestionCard from './QuestionCard';
import { MessageReceiveData, MessageSendData } from '@type/chat';
import { MessageReceiveData, MessageSendData, UserInfoData } from '@type/chat';
import { CHATTING_SOCKET_SEND_EVENT } from '@constants/chat';
import { getStoredId } from '@utils/id';
import { UserType } from '@type/user';
import { useChat } from '@contexts/chatContext';

export interface ChatQuestionSectionProps {
questions: MessageReceiveData[];
Expand Down Expand Up @@ -38,6 +39,18 @@ const ChatQuestionSection = ({ questions, worker, userType, roomId }: ChatQuesti
[worker, roomId, userId]
);

const { dispatch } = useChat();

const onNicknameClick = useCallback(
(data: UserInfoData) => {
dispatch({
type: 'SET_SELECTED_USER',
payload: data
});
},
[dispatch]
);

return (
<SectionWrapper>
<SectionContainer>
Expand All @@ -51,18 +64,32 @@ const ChatQuestionSection = ({ questions, worker, userType, roomId }: ChatQuesti
question={questions[0]}
handleQuestionDone={handleQuestionDone}
ellipsis={!expanded}
onNicknameClick={() =>
onNicknameClick({
nickname: questions[0].nickname,
socketId: questions[0].socketId,
entryTime: questions[0].entryTime,
owner: questions[0].owner
})
}
/>
{expanded &&
questions
.slice(1)
.map((question) => (
<QuestionCard
key={question.questionId}
type={userType}
question={question}
handleQuestionDone={handleQuestionDone}
/>
))}
questions.slice(1).map((question) => (
<QuestionCard
key={question.questionId}
type={userType}
question={question}
handleQuestionDone={handleQuestionDone}
onNicknameClick={() =>
onNicknameClick({
nickname: question.nickname,
socketId: question.socketId,
entryTime: question.entryTime,
owner: question.owner
})
}
/>
))}
<SwipeBtn onClick={toggleSection} />
</>
)}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/chat/ChatRoomLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const ChatRoomLayout = ({ userType, roomId }: ChatRoomLayoutProps) => {

{state.isNoticePopupOpen && (
<PopupWrapper>
<NoticeCard />
<NoticeCard sessionKey={roomId} />
</PopupWrapper>
)}

Expand Down
29 changes: 14 additions & 15 deletions frontend/src/components/chat/NoticeCard.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import styled from 'styled-components';
import CloseIcon from '@assets/icons/close.svg';
import { useContext } from 'react';
import { memo, useCallback, useContext } from 'react';
import { ChatContext } from 'src/contexts/chatContext';
import { useFetchChatRule } from '@apis/queries/chat/useFetchChatRule';

export const NoticeCard = () => {
export const NoticeCard = ({ sessionKey }: { sessionKey: string }) => {
const { dispatch } = useContext(ChatContext);

const toggleSettings = () => {
const toggleSettings = useCallback(() => {
dispatch({ type: 'TOGGLE_ANNOUNCEMENT_POPUP' });
};
}, [dispatch]);

const { data: noticeInfo } = useFetchChatRule({ sessionKey });

return (
<NoticeCardContainer>
Expand All @@ -17,28 +20,22 @@ export const NoticeCard = () => {
<NoticeCardProfile></NoticeCardProfile>
<NoticeCardArea>
<div className="text_info">
<span className="text_point">네이버 부스트 캠프</span>
<span className="text_point">{noticeInfo?.channelName}</span>
<span>님의</span>
</div>
<div className="text_strong">컨퍼런스 공지 📢</div>
<div className="text_strong">채팅 규칙 📢</div>
</NoticeCardArea>
</NoticeCardWrapper>
<CloseBtn onClick={toggleSettings}>
<StyledCloseIcon />
</CloseBtn>
</NoticeCardHeader>

<NoticeMessage>
- 질문은 질문 채팅으로 부탁드립니다
<br /> - 컨퍼런스 보러와주셔서 감사합니다
<br /> - 컨퍼런스 보러와주셔서 감사합니다
<br /> - 컨퍼런스 보러와주셔서 감사합니다
<br /> - 컨퍼런스 보러와주셔서 감사합니다
</NoticeMessage>
<NoticeMessage>{noticeInfo?.notice}</NoticeMessage>
</NoticeCardContainer>
);
};
export default NoticeCard;
export default memo(NoticeCard);

const NoticeCardContainer = styled.div`
display: flex;
Expand Down Expand Up @@ -113,5 +110,7 @@ const NoticeMessage = styled.p`
margin-top: 10px;
max-height: 170px;
overflow-y: auto;
${({ theme }) => theme.tokenTypographys['display-bold14']}
${({ theme }) => theme.tokenTypographys['display-bold14']};
white-space: pre-line;
word-break: keep-all;
`;
35 changes: 29 additions & 6 deletions frontend/src/components/chat/QuestionCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { memo, useEffect, useMemo, useRef } from 'react';
import { memo, useEffect, useMemo, useRef, useCallback } from 'react';
import styled from 'styled-components';
import CheckIcon from '@assets/icons/check.svg';
import { MessageReceiveData } from '@type/chat';
Expand All @@ -10,9 +10,10 @@ interface QuestionCardProps {
question: MessageReceiveData;
handleQuestionDone?: (questionId: number) => void;
ellipsis?: boolean;
onNicknameClick: () => void;
}

export const QuestionCard = ({ type, question, handleQuestionDone, ellipsis = false }: QuestionCardProps) => {
const QuestionCard = ({ type, question, handleQuestionDone, onNicknameClick, ellipsis = false }: QuestionCardProps) => {
const startDateFormat = useMemo(() => new Date(question.msgTime), [question.msgTime]);
const nowRef = useRef<Date>(new Date());

Expand All @@ -36,19 +37,29 @@ export const QuestionCard = ({ type, question, handleQuestionDone, ellipsis = fa

const timeElement = useRef<HTMLSpanElement>(null);

const handleQuestionDoneMemoized = useCallback(() => {
if (handleQuestionDone) {
handleQuestionDone(question.questionId as number);
}
}, [handleQuestionDone, question.questionId]);

const onNicknameClickMemoized = useCallback(() => {
onNicknameClick();
}, [onNicknameClick]);

return (
<QuestionCardContainer>
<QuestionCardTop>
<QuestionInfo>
<span className="name_info">
<span className="name_info" onClick={onNicknameClickMemoized}>
<StyledIcon as={QuestionUserIcon} /> {question.nickname}
</span>
<span className="time_info" ref={timeElement}>
{formatTime.current}
</span>
</QuestionInfo>
{type === 'host' && handleQuestionDone && (
<CheckBtn onClick={() => handleQuestionDone(question.questionId as number)}>
<CheckBtn onClick={handleQuestionDoneMemoized}>
<StyledCheckIcon />
</CheckBtn>
)}
Expand All @@ -59,7 +70,14 @@ export const QuestionCard = ({ type, question, handleQuestionDone, ellipsis = fa
);
};

export default memo(QuestionCard);
// shouldComponentUpdate를 내부에서 사용할 수 있도록 memo 사용
export default memo(QuestionCard, (prevProps, nextProps) => {
return (
prevProps.question.questionId === nextProps.question.questionId &&
prevProps.type === nextProps.type &&
prevProps.ellipsis === nextProps.ellipsis
);
});

const QuestionCardContainer = styled.div`
display: flex;
Expand All @@ -86,7 +104,12 @@ const QuestionInfo = styled.div`
align-items: end;
gap: 12px;
.name_info {
${({ theme }) => theme.tokenTypographys['display-bold12']}
border-radius: 7px;
${({ theme }) => theme.tokenTypographys['display-bold12']};
cursor: pointer;
&:hover {
color: #bbbbbb;
}
}
.time_info {
${({ theme }) => theme.tokenTypographys['display-medium12']}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/contexts/chatContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const chatReducer = (state: ChatState, action: Action): ChatState => {
const initialState: ChatState = {
isSettingsOpen: false,
settingOption: null,
isNoticePopupOpen: false,
isNoticePopupOpen: true,
isUserInfoPopupOpen: false,
selectedUser: null
};
Expand Down

0 comments on commit db63848

Please sign in to comment.