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

채팅 캐시 관련 버그 수정 #233

Merged
merged 8 commits into from
Jul 3, 2024
Merged
17 changes: 16 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,32 @@
{
"extends": [
"next/core-web-vitals",
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:import/recommended",
"plugin:import/typescript",
"plugin:storybook/recommended"
],
"parser": "@typescript-eslint/parser",
"settings": {
"import/parsers": {
"@typescript-eslint/parser": [
".ts",
".tsx"
]
},
"import/resolver": {
"typescript": true,
"node": true
}
},
"rules": {
"import/no-unused-modules": "error",
"sort-imports": [
"error",
{
"ignoreDeclarationSort": true
}
],
"import/order": [
"error",
{
Expand Down
418 changes: 400 additions & 18 deletions .pnp.cjs

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@typescript-eslint/parser": "^6.19.1",
"@typescript-eslint/eslint-plugin": "^7.15.0",
"@typescript-eslint/parser": "^7",
"eslint": "^8",
"eslint-config-next": "14.0.2",
"eslint-import-resolver-typescript": "^3.6.1",
Expand All @@ -65,14 +66,15 @@
"stylelint-order": "^6.0.4",
"supports-color": "^9.4.0",
"typescript": "5.3.2",
"typescript-eslint": "^7.15.0",
"webpack": "^5.89.0"
},
"packageManager": "[email protected]",
"lint-staged": {
"*.{ts,tsx}": [
"eslint --fix",
"stylelint --fix",
"prettier --write"
"*.{js,jsx,ts,tsx}": [
"yarn eslint --fix",
"yarn stylelint --fix",
"yarn prettier --write"
]
}
}
}
2 changes: 1 addition & 1 deletion src/app/signup-guard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const SignupGuard = () => {
const popupComponent = useMemo(() => {
switch (step) {
case SignUpStep.TERMS_AGREEMENT:
return <SignUpTermsPopup onSuccess={() => setNextStep()} />;
return <SignUpTermsPopup />;
case SignUpStep.PROFILE_CREATION:
return <SignUpProfileCreationPopup onSuccess={() => setNextStep()} />;
case SignUpStep.POSITION_SELECTION:
Expand Down
2 changes: 1 addition & 1 deletion src/components/atoms/CheckBox.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { forwardRef, type InputHTMLAttributes } from 'react';
import { type InputHTMLAttributes, forwardRef } from 'react';

import { css } from '@emotion/react';
import styled from '@emotion/styled';
Expand Down
2 changes: 1 addition & 1 deletion src/components/atoms/FitLogo.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SignatureLogo, MonochromeSignatureLogo, SymbolLogo } from '#/assets/logos';
import { MonochromeSignatureLogo, SignatureLogo, SymbolLogo } from '#/assets/logos';

interface LogoProps {
variant?: 'signature' | 'symbol';
Expand Down
32 changes: 16 additions & 16 deletions src/components/atoms/Icons.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import Image from 'next/image';
import { SVGProps } from 'react';
import Image, { StaticImageData } from 'next/image';

import {
AccountIcon,
Expand All @@ -12,39 +12,39 @@ import {
CameraIcon,
CheckIcon,
ClickIcon,
ClipBoldIcon,
ClipIcon,
CrossIcon,
CrownIcon,
EmailIcon,
EmojiFire,
EmojiHoldingBackTears,
EmojiPartyingFace,
EmojiPoliceCarLight,
EmojiWinkingFace,
FacebookIcon,
GithubIcon,
GoogleIcon,
HeartFilledIcon,
HeartIcon,
ImageIcon,
InfoIcon,
InstagramIcon,
KakaoIcon,
LinkIcon,
LinkedInIcon,
LogoutIcon,
MegaphoneIcon,
PencilIcon,
PhoneFillIcon,
PlusIcon,
ProgressIcon,
RunIcon,
TistoryIcon,
Upload,
UserIcon,
HeartIcon,
HeartFilledIcon,
EmailIcon,
PhoneFillIcon,
GithubIcon,
FacebookIcon,
LinkedInIcon,
TistoryIcon,
VelogIcon,
ClipBoldIcon,
UserLineIcon,
LogoutIcon,
EmojiPoliceCarLight,
VelogIcon,
} from '#/assets/icons';

export type IconName =
Expand Down Expand Up @@ -95,7 +95,7 @@ export type IconName =
| 'velog';

interface Icon {
SVGR: any;
SVGR: React.FC<React.SVGProps<SVGElement>> | StaticImageData;
color?: string;
style?: React.CSSProperties;
}
Expand Down Expand Up @@ -281,7 +281,7 @@ const icons: Record<IconName, Icon> = {
},
};

interface IconsProps extends React.HTMLAttributes<HTMLDivElement> {
interface IconsProps extends SVGProps<SVGElement> {
icon: IconName;
useCSSColor?: boolean;

Expand Down
4 changes: 2 additions & 2 deletions src/components/atoms/Label.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import { HTMLAttributes } from 'react';

import styled from '@emotion/styled';

Expand All @@ -18,7 +18,7 @@ const LabelText = styled(Txt)`
white-space: nowrap;
`;

interface LabelProps extends React.HTMLAttributes<HTMLDivElement> {
interface LabelProps extends HTMLAttributes<HTMLDivElement> {
text: string;
position?: 'top' | 'left';
gap?: React.CSSProperties['gap'];
Expand Down
9 changes: 7 additions & 2 deletions src/components/atoms/Loading.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { forwardRef } from 'react';
import type { HTMLAttributes } from 'react';

import styled from '@emotion/styled';

import { FitLogo } from '.';

export const Loading = forwardRef<HTMLDivElement>(({}, ref) => {
export const Loading = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>((props, ref) => {
return (
<Container ref={ref}>
<Container ref={ref} {...props}>
<Spinner>
<FitLogo variant="symbol" />
</Spinner>
Expand All @@ -19,6 +20,10 @@ Loading.displayName = 'Loading';
const Container = styled.div`
display: flex;
justify-content: center;

&[hidden] {
display: none;
}
`;

const Spinner = styled.div`
Expand Down
2 changes: 1 addition & 1 deletion src/components/atoms/MouseDetector.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MouseEventHandler, useEffect, useRef } from 'react';
import { useEffect, useRef } from 'react';

interface MouseDetectorProps extends React.HTMLAttributes<HTMLDivElement> {
onClickOutside?: () => void;
Expand Down
1 change: 0 additions & 1 deletion src/components/atoms/MyPage/PortfolioTicket.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { useCallback } from 'react';
import styled from '@emotion/styled';

import { useTempAuthStore } from '#/stores/tempAuth';
import { User } from '#/types';
import { IconName, Icons } from '#atoms/Icons';
import { Txt } from '#atoms/Text';

Expand Down
1 change: 0 additions & 1 deletion src/components/atoms/SocialLoginButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use client';

import { useEffect, useState } from 'react';
import Link from 'next/link';

import { css } from '@emotion/react';
Expand Down
2 changes: 1 addition & 1 deletion src/components/atoms/Text.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CSSProperties, HTMLAttributes } from 'react';
import { CSSProperties } from 'react';

import { css } from '@emotion/react';
import styled from '@emotion/styled';
Expand Down
97 changes: 45 additions & 52 deletions src/components/molecules/ChatBubbles.tsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,52 @@
import { useEffect, useRef, useState } from 'react';
import { useEffect, useRef } from 'react';

import styled from '@emotion/styled';

import _ from 'lodash';
import { useShallow } from 'zustand/react/shallow';

import { Loading } from '#/components/atoms';
import { nullUser } from '#/entities';
import { useChatMessagesQuery } from '#/hooks/use-chat';
import { useMeQuery } from '#/hooks/use-user';
import { useChatStore } from '#/stores';
import { Chat } from '#/types';
import { isImageMessage, isNoticeMessage, isTextMessage } from '#/utilities';
import { convertDtoToMessage } from '#/utilities/message';
import {
useChatId,
useChatMessagesQuery,
useChatSubscription,
useChatUsers,
useControlMessageHandler,
useMeQuery,
useNoticeMessageHandler,
} from '#/hooks';
import { MatchingRoom, Project } from '#/types';
import { isControlMessage, isImageMessage, isNoticeMessage, isTextMessage } from '#/utilities';
import { ChatBubble } from './ChatBubble';
import { NoticeBubble } from './NoticeBubble';

interface ChatBubblesProps {
chatId: Chat['id'];
projectId?: Project['id'];
matchingId?: MatchingRoom['id'];
}

export const ChatBubbles = ({ chatId }: ChatBubblesProps) => {
export const ChatBubbles = ({ projectId, matchingId }: ChatBubblesProps) => {
const topRef = useRef<HTMLDivElement>(null);

const [hasNext, setHasNext] = useState<boolean>(true);
const [pageCursor, setPageCursor] = useState(0);
const noticeMessageHandler = useNoticeMessageHandler(matchingId);
const controlMessageHandler = useControlMessageHandler({ projectId, matchingId });

const { participants, socket, messages, unshiftMessage, appendMessages } = useChatStore(
useShallow(({ chats, unshiftMessage, appendMessages }) => ({
participants: chats[chatId].users,
socket: chats[chatId].socket,
messages: chats[chatId].messages,
unshiftMessage,
appendMessages,
}))
);
const participants = useChatUsers({ projectId, matchingId });
const chatId = useChatId({ projectId, matchingId });

const { data: me } = useMeQuery();

const { data: pages, size, setSize } = useChatMessagesQuery(chatId);

useEffect(() => {
socket.on('get_message', (data: string) => {
const message = convertDtoToMessage(JSON.parse(data));
unshiftMessage(chatId, message);
});
return () => socket.off('get_message');
}, [chatId, socket, unshiftMessage]);
const { data: messages, setSize, append: appendMessage } = useChatMessagesQuery(chatId);
const { data: recentMessage } = useChatSubscription(chatId);

useEffect(() => {
if (pages && pages.length > pageCursor) {
const page = pages[pageCursor];
appendMessages(chatId, page.messages);
setHasNext(page.hasNext);
setPageCursor((c) => c + 1);
if (recentMessage) {
if (isNoticeMessage(recentMessage)) {
noticeMessageHandler(recentMessage);
}
if (isControlMessage(recentMessage)) {
controlMessageHandler(recentMessage);
}
appendMessage(recentMessage);
}
}, [appendMessages, chatId, pageCursor, pages, size]);
}, [appendMessage, controlMessageHandler, noticeMessageHandler, recentMessage]);

useEffect(() => {
const observer = new IntersectionObserver((entries) => {
Expand All @@ -74,19 +65,21 @@ export const ChatBubbles = ({ chatId }: ChatBubblesProps) => {
<div />
<div />
<div />
{messages?.map((message, index) =>
isNoticeMessage(message) ? (
<NoticeBubble key={index} message={message} />
) : isTextMessage(message) || isImageMessage(message) ? (
<ChatBubble
key={index}
user={participants.find((p) => p.id === message.userId) ?? nullUser}
message={message}
myBubble={me?.id === message.userId}
/>
) : null
)}
{hasNext && <Loading ref={topRef} />}
{messages
?.flatMap((m) => m.messages)
?.map((message) =>
isNoticeMessage(message) ? (
<NoticeBubble key={message.id} message={message} />
) : isTextMessage(message) || isImageMessage(message) ? (
<ChatBubble
key={message.id}
user={participants.find((p) => p.id === message.userId) ?? nullUser}
message={message}
myBubble={me?.id === message.userId}
/>
) : null
)}
<Loading ref={topRef} hidden={!messages?.at(-1)?.hasNext} />
</Container>
);
};
Expand Down
Loading
Loading