From 569d842a4d5e4475c8ab24583a4becfa065ecd08 Mon Sep 17 00:00:00 2001 From: GuDoYoon Date: Sun, 30 Nov 2025 17:32:54 +0900 Subject: [PATCH 1/6] =?UTF-8?q?fkx:=20esLint=20Rule=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- eslint.config.mjs | 65 ++++++-------------------------- packages/back/eslint.config.mjs | 31 +++------------ packages/front/eslint.config.mjs | 36 ++++++++++++++++++ packages/front/package.json | 1 + yarn.lock | 10 +++++ 5 files changed, 64 insertions(+), 79 deletions(-) create mode 100644 packages/front/eslint.config.mjs diff --git a/eslint.config.mjs b/eslint.config.mjs index 58f59cd..05da4ff 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,25 +1,9 @@ -// /eslint.config.mjs -import globals from "globals"; import pluginJs from "@eslint/js"; import tseslint from "typescript-eslint"; -import pluginReactConfig from "eslint-plugin-react/configs/recommended.js"; -import nextPlugin from "@next/eslint-plugin-next"; -export default [ +export const sharedConfig = [ { - rules: { - "@typescript-eslint/no-unused-vars": [ - "error", - { - argsIgnorePattern: "^_", - varsIgnorePattern: "^_", - caughtErrorsIgnorePattern: "^_", - }, - ], - }, - }, - { - // 전체 프로젝트에 적용될 기본 설정 + // 공통 무시 파일들 ignores: [ "**/node_modules/**", "**/dist/**", @@ -30,45 +14,20 @@ export default [ "packages/front/next-env.d.ts", ], }, - // JavaScript 파일 기본 규칙 pluginJs.configs.recommended, - // TypeScript 파일 기본 규칙 ...tseslint.configs.recommended, - - // React/Next.js (front) 패키지 설정 { - files: ["packages/front/**/*.{js,jsx,ts,tsx}"], - ...pluginReactConfig, - plugins: { - "@next/next": nextPlugin, - }, rules: { - ...nextPlugin.configs.recommended.rules, - ...nextPlugin.configs["core-web-vitals"].rules, - "react/react-in-jsx-scope": "off", - }, - settings: { - next: { - rootDir: "packages/front/", - }, - react: { - version: "detect", - }, - }, - languageOptions: { - globals: { - ...globals.browser, - ...globals.node, - }, - }, - }, - // Node.js (back) 패키지 설정 - { - files: ["packages/back/**/*.{js,ts}"], - languageOptions: { - globals: { - ...globals.node, - }, + "@typescript-eslint/no-unused-vars": [ + "error", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + caughtErrorsIgnorePattern: "^_", + }, + ], }, }, ]; + +export default sharedConfig; diff --git a/packages/back/eslint.config.mjs b/packages/back/eslint.config.mjs index 4e9f827..54e88e1 100644 --- a/packages/back/eslint.config.mjs +++ b/packages/back/eslint.config.mjs @@ -1,35 +1,14 @@ -// @ts-check -import eslint from '@eslint/js'; -import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; +import { sharedConfig } from '../../eslint.config.mjs'; import globals from 'globals'; -import tseslint from 'typescript-eslint'; -export default tseslint.config( - { - ignores: ['eslint.config.mjs'], - }, - eslint.configs.recommended, - ...tseslint.configs.recommendedTypeChecked, - eslintPluginPrettierRecommended, +export default [ + ...sharedConfig, + { languageOptions: { globals: { ...globals.node, - ...globals.jest, - }, - sourceType: 'commonjs', - parserOptions: { - projectService: true, - tsconfigRootDir: import.meta.dirname, }, }, }, - { - rules: { - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-floating-promises': 'warn', - '@typescript-eslint/no-unsafe-argument': 'warn', - "prettier/prettier": ["error", { endOfLine: "auto" }], - }, - }, -); +]; diff --git a/packages/front/eslint.config.mjs b/packages/front/eslint.config.mjs new file mode 100644 index 0000000..f1714b5 --- /dev/null +++ b/packages/front/eslint.config.mjs @@ -0,0 +1,36 @@ +import pluginReactConfig from "eslint-plugin-react/configs/recommended.js"; +import nextPlugin from "@next/eslint-plugin-next"; +import globals from "globals"; +import { sharedConfig } from "../../eslint.config.mjs"; +import suspensePlugin from "eslint-plugin-react-suspense-check"; + +export default [ + ...sharedConfig, + suspensePlugin.configs.recommended, + { + ...pluginReactConfig, + plugins: { + "@next/next": nextPlugin, + }, + rules: { + ...nextPlugin.configs.recommended.rules, + ...nextPlugin.configs["core-web-vitals"].rules, + "react/react-in-jsx-scope": "off", + "react-suspense-check/detect-suspense-hook": ["warn", { language: "kr" }], + }, + settings: { + next: { + rootDir: ".", + }, + react: { + version: "detect", + }, + }, + languageOptions: { + globals: { + ...globals.browser, + ...globals.node, + }, + }, + }, +]; diff --git a/packages/front/package.json b/packages/front/package.json index fe91d38..0926898 100644 --- a/packages/front/package.json +++ b/packages/front/package.json @@ -31,6 +31,7 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "15.5.2", + "eslint-plugin-react-suspense-check": "^1.0.2", "tailwindcss": "^4", "typescript": "^5" } diff --git a/yarn.lock b/yarn.lock index 02c1926..2dac462 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6711,6 +6711,15 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-react-suspense-check@npm:^1.0.2": + version: 1.0.2 + resolution: "eslint-plugin-react-suspense-check@npm:1.0.2" + peerDependencies: + eslint: ">=8.0.0" + checksum: 10c0/b69c7aea5ea65c3362ac9c6184ac942c12c8f8965ab596b58d0685b9193fff9a0350fd109b5228f26ac337be29888c17a1a24fb46c2271f8ebda000c608ae29e + languageName: node + linkType: hard + "eslint-plugin-react@npm:^7.33.2, eslint-plugin-react@npm:^7.34.2, eslint-plugin-react@npm:^7.37.0": version: 7.37.5 resolution: "eslint-plugin-react@npm:7.37.5" @@ -7423,6 +7432,7 @@ __metadata: common: "workspace:*" eslint: "npm:^9" eslint-config-next: "npm:15.5.2" + eslint-plugin-react-suspense-check: "npm:^1.0.2" ky: "npm:^1.11.0" next: "npm:15.5.2" next-themes: "npm:^0.4.6" From 4c0a46e0b4949960c965ffeb730237c423557117 Mon Sep 17 00:00:00 2001 From: GuDoYoon Date: Sun, 30 Nov 2025 21:04:49 +0900 Subject: [PATCH 2/6] =?UTF-8?q?refactor:=20=EC=84=9C=EC=8A=A4=ED=8E=9C?= =?UTF-8?q?=EC=8A=A4=20=EC=BB=A8=EB=B2=A4=EC=85=98=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/front/src/app/_hooks/index.ts | 2 +- .../_hooks/{useAuth.ts => useSuspenseAuth.ts} | 2 +- .../app/chat-rooms/_components/ChatHeader.tsx | 7 +- .../chat-rooms/_components/ChatRoomList.tsx | 78 ------------------ .../_components/PublicChatRoomCard.tsx | 2 +- .../_components/SuspenseChatRoomList.tsx | 81 ++++++++++++++++--- .../SuspenseChatRoomListWrapper.tsx | 15 ++++ ...Menu.tsx => SuspenseHeaderProfileMenu.tsx} | 6 +- .../src/app/chat-rooms/_components/index.ts | 6 +- packages/front/src/app/chat-rooms/page.tsx | 4 +- ...ainer.tsx => SuspensePersonaContainer.tsx} | 2 +- .../chat-rooms/personas/_components/index.ts | 2 +- .../src/app/chat-rooms/personas/page.tsx | 4 +- .../[id]/_components/AILoadingMessage.tsx | 11 ++- .../app/chat/[id]/_components/ChatCard.tsx | 16 ++-- .../chat/[id]/_components/EmptyChatCard.tsx | 11 ++- .../src/app/chat/[id]/_components/SideBar.tsx | 4 +- .../{ChatList.tsx => SuspenseChatList.tsx} | 2 +- .../{ChatRoom.tsx => SuspenseChatRoom.tsx} | 10 +-- ...rofileCard.tsx => SuspenseProfileCard.tsx} | 6 +- .../src/app/chat/[id]/_components/index.ts | 6 +- 21 files changed, 150 insertions(+), 127 deletions(-) rename packages/front/src/app/_hooks/{useAuth.ts => useSuspenseAuth.ts} (85%) delete mode 100644 packages/front/src/app/chat-rooms/_components/ChatRoomList.tsx create mode 100644 packages/front/src/app/chat-rooms/_components/SuspenseChatRoomListWrapper.tsx rename packages/front/src/app/chat-rooms/_components/{HeaderProfileMenu.tsx => SuspenseHeaderProfileMenu.tsx} (95%) rename packages/front/src/app/chat-rooms/personas/_components/{PersonaContainer.tsx => SuspensePersonaContainer.tsx} (90%) rename packages/front/src/app/chat/[id]/_components/{ChatList.tsx => SuspenseChatList.tsx} (90%) rename packages/front/src/app/chat/[id]/_components/{ChatRoom.tsx => SuspenseChatRoom.tsx} (86%) rename packages/front/src/app/chat/[id]/_components/{ProfileCard.tsx => SuspenseProfileCard.tsx} (92%) diff --git a/packages/front/src/app/_hooks/index.ts b/packages/front/src/app/_hooks/index.ts index 7a1494b..cc8d2a5 100644 --- a/packages/front/src/app/_hooks/index.ts +++ b/packages/front/src/app/_hooks/index.ts @@ -1,4 +1,4 @@ -export { useAuth } from "./useAuth"; +export { useSuspenseAuth } from "./useSuspenseAuth"; export { useLocalStorage } from "./useLocalStorage"; export { useSession } from "./useSession"; export { useUnmount } from "./useUnmount"; diff --git a/packages/front/src/app/_hooks/useAuth.ts b/packages/front/src/app/_hooks/useSuspenseAuth.ts similarity index 85% rename from packages/front/src/app/_hooks/useAuth.ts rename to packages/front/src/app/_hooks/useSuspenseAuth.ts index be9c8f0..859dff7 100644 --- a/packages/front/src/app/_hooks/useAuth.ts +++ b/packages/front/src/app/_hooks/useSuspenseAuth.ts @@ -1,7 +1,7 @@ import { userQueries } from "@/app/_queries"; import { useSuspenseQuery } from "@tanstack/react-query"; -export const useAuth = () => { +export const useSuspenseAuth = () => { const userQueryOption = userQueries.me(); const { data: user } = useSuspenseQuery(userQueryOption); diff --git a/packages/front/src/app/chat-rooms/_components/ChatHeader.tsx b/packages/front/src/app/chat-rooms/_components/ChatHeader.tsx index 76e97a3..64a04e3 100644 --- a/packages/front/src/app/chat-rooms/_components/ChatHeader.tsx +++ b/packages/front/src/app/chat-rooms/_components/ChatHeader.tsx @@ -2,7 +2,10 @@ import { Suspense } from "react"; import Image from "next/image"; -import { ChatRoomTab, HeaderProfileMenu } from "@/app/chat-rooms/_components"; +import { + ChatRoomTab, + SuspenseHeaderProfileMenu, +} from "@/app/chat-rooms/_components"; export default function ChatHeader() { return ( @@ -15,7 +18,7 @@ export default function ChatHeader() {
} > - +
diff --git a/packages/front/src/app/chat-rooms/_components/ChatRoomList.tsx b/packages/front/src/app/chat-rooms/_components/ChatRoomList.tsx deleted file mode 100644 index 6c95c86..0000000 --- a/packages/front/src/app/chat-rooms/_components/ChatRoomList.tsx +++ /dev/null @@ -1,78 +0,0 @@ -"use client"; - -import { useSuspenseQuery } from "@tanstack/react-query"; -import { ChatRoomCard } from "@/app/chat-rooms/_components"; -import { chatRoomQueries } from "@/app/_queries"; -import { useSearchParams } from "next/navigation"; -import PublicChatRoomCard from "@/app/chat-rooms/_components/PublicChatRoomCard"; -import { transition } from "@ssgoi/react"; -import { fade } from "@ssgoi/react/transitions"; - -export default function ChatRoomList() { - const searchParams = useSearchParams(); - - const currentTab = - searchParams.get("tab") === "public" ? "public" : "private"; - - const { data: chatRoomList } = useSuspenseQuery(chatRoomQueries.myList()); - const { data: publicChatRoomList } = useSuspenseQuery( - chatRoomQueries.publicList(), - ); - - if (currentTab === "public") { - if (publicChatRoomList.length === 0) { - return ( -

- 공개된 채팅방이 없습니다. -

- ); - } - - return ( - - ); - } - if (chatRoomList.length === 0) { - return ( -

- 참여중인 채팅방이 없습니다. -

- ); - } - - return ( - - ); -} diff --git a/packages/front/src/app/chat-rooms/_components/PublicChatRoomCard.tsx b/packages/front/src/app/chat-rooms/_components/PublicChatRoomCard.tsx index 3cc9173..a44bdec 100644 --- a/packages/front/src/app/chat-rooms/_components/PublicChatRoomCard.tsx +++ b/packages/front/src/app/chat-rooms/_components/PublicChatRoomCard.tsx @@ -6,7 +6,7 @@ import Image from "next/image"; import { useSuspenseQuery } from "@tanstack/react-query"; import { chatRoomQueries } from "@/app/_queries"; -export default function PublicChatRoomCard({ id, persona }: ChatRoom) { +export default function SuspensePublicChatRoomCard({ id, persona }: ChatRoom) { const { data: active } = useSuspenseQuery(chatRoomQueries.active(id)); return (
  • diff --git a/packages/front/src/app/chat-rooms/_components/SuspenseChatRoomList.tsx b/packages/front/src/app/chat-rooms/_components/SuspenseChatRoomList.tsx index 62cbae2..e478ff7 100644 --- a/packages/front/src/app/chat-rooms/_components/SuspenseChatRoomList.tsx +++ b/packages/front/src/app/chat-rooms/_components/SuspenseChatRoomList.tsx @@ -1,15 +1,78 @@ "use client"; -import dynamic from "next/dynamic"; -const ChatRoomList = dynamic( - () => import("@/app/chat-rooms/_components/ChatRoomList"), - { ssr: false }, -); -import { Suspense } from "react"; + +import { useSuspenseQuery } from "@tanstack/react-query"; +import { ChatRoomCard } from "@/app/chat-rooms/_components"; +import { chatRoomQueries } from "@/app/_queries"; +import { useSearchParams } from "next/navigation"; +import PublicChatRoomCard from "@/app/chat-rooms/_components/PublicChatRoomCard"; +import { transition } from "@ssgoi/react"; +import { fade } from "@ssgoi/react/transitions"; export default function SuspenseChatRoomList() { + const searchParams = useSearchParams(); + + const currentTab = + searchParams.get("tab") === "public" ? "public" : "private"; + + const { data: chatRoomList } = useSuspenseQuery(chatRoomQueries.myList()); + const { data: publicChatRoomList } = useSuspenseQuery( + chatRoomQueries.publicList(), + ); + + if (currentTab === "public") { + if (publicChatRoomList.length === 0) { + return ( +

    + 공개된 채팅방이 없습니다. +

    + ); + } + + return ( +
      + {publicChatRoomList.map((chat) => ( + + ))} +
    + ); + } + if (chatRoomList.length === 0) { + return ( +

    + 참여중인 채팅방이 없습니다. +

    + ); + } + return ( - Loading...}> - - +
      + {chatRoomList.map((chat) => ( + + ))} +
    ); } diff --git a/packages/front/src/app/chat-rooms/_components/SuspenseChatRoomListWrapper.tsx b/packages/front/src/app/chat-rooms/_components/SuspenseChatRoomListWrapper.tsx new file mode 100644 index 0000000..c0a8177 --- /dev/null +++ b/packages/front/src/app/chat-rooms/_components/SuspenseChatRoomListWrapper.tsx @@ -0,0 +1,15 @@ +"use client"; +import dynamic from "next/dynamic"; +const SuspenseChatRoomList = dynamic( + () => import("@/app/chat-rooms/_components/SuspenseChatRoomList"), + { ssr: false }, +); +import { Suspense } from "react"; + +export default function SuspenseChatRoomListWrapper() { + return ( + Loading...}> + + + ); +} diff --git a/packages/front/src/app/chat-rooms/_components/HeaderProfileMenu.tsx b/packages/front/src/app/chat-rooms/_components/SuspenseHeaderProfileMenu.tsx similarity index 95% rename from packages/front/src/app/chat-rooms/_components/HeaderProfileMenu.tsx rename to packages/front/src/app/chat-rooms/_components/SuspenseHeaderProfileMenu.tsx index 619c524..ce8b501 100644 --- a/packages/front/src/app/chat-rooms/_components/HeaderProfileMenu.tsx +++ b/packages/front/src/app/chat-rooms/_components/SuspenseHeaderProfileMenu.tsx @@ -3,17 +3,17 @@ import { useState } from "react"; import Link from "next/link"; import { UserProfileIcon } from "@/app/_components"; -import { useAuth } from "@/app/_hooks"; +import { useSuspenseAuth } from "@/app/_hooks"; import { useMutation } from "@tanstack/react-query"; import { userQueries } from "@/app/_queries"; import { User } from "common"; import { transition } from "@ssgoi/react"; import { slide } from "@ssgoi/react/transitions"; -export default function HeaderProfileMenu() { +export default function SuspenseHeaderProfileMenu() { const [isOpen, setIsOpen] = useState(false); const { mutate: onLogout } = useMutation(userQueries.logout()); - const user = useAuth(); + const user = useSuspenseAuth(); const toggleMenu = () => setIsOpen((prev) => !prev); const closeMenu = () => setIsOpen(false); diff --git a/packages/front/src/app/chat-rooms/_components/index.ts b/packages/front/src/app/chat-rooms/_components/index.ts index 46f950e..bb4ecdd 100644 --- a/packages/front/src/app/chat-rooms/_components/index.ts +++ b/packages/front/src/app/chat-rooms/_components/index.ts @@ -1,7 +1,7 @@ export { default as ChatHeader } from "./ChatHeader"; export { default as ChatRoomCard } from "./ChatRoomCard"; -export { default as ChatRoomList } from "./ChatRoomList"; +export { default as SuspenseChatRoomList } from "./SuspenseChatRoomList"; export { default as ChatRoomTab } from "./ChatRoomTab"; -export { default as HeaderProfileMenu } from "./HeaderProfileMenu"; +export { default as SuspenseHeaderProfileMenu } from "./SuspenseHeaderProfileMenu"; export { default as PublicChatRoomCard } from "./PublicChatRoomCard"; -export { default as SuspenseChatRoomList } from "./SuspenseChatRoomList"; +export { default as SuspenseChatRoomListWrapper } from "./SuspenseChatRoomListWrapper"; diff --git a/packages/front/src/app/chat-rooms/page.tsx b/packages/front/src/app/chat-rooms/page.tsx index 9caa224..f51414d 100644 --- a/packages/front/src/app/chat-rooms/page.tsx +++ b/packages/front/src/app/chat-rooms/page.tsx @@ -1,4 +1,4 @@ -import { SuspenseChatRoomList } from "@/app/chat-rooms/_components"; +import { SuspenseChatRoomListWrapper } from "@/app/chat-rooms/_components"; import { dehydrate, HydrationBoundary, @@ -50,7 +50,7 @@ export default async function ChatRoomsPage() { 현재 채팅 목록 - +
    diff --git a/packages/front/src/app/chat-rooms/personas/_components/PersonaContainer.tsx b/packages/front/src/app/chat-rooms/personas/_components/SuspensePersonaContainer.tsx similarity index 90% rename from packages/front/src/app/chat-rooms/personas/_components/PersonaContainer.tsx rename to packages/front/src/app/chat-rooms/personas/_components/SuspensePersonaContainer.tsx index e348167..d252541 100644 --- a/packages/front/src/app/chat-rooms/personas/_components/PersonaContainer.tsx +++ b/packages/front/src/app/chat-rooms/personas/_components/SuspensePersonaContainer.tsx @@ -4,7 +4,7 @@ import { personaQueries } from "@/app/_queries/personaQueries"; import { PersonaCard } from "@/app/chat-rooms/personas/_components"; import { useSuspenseQuery } from "@tanstack/react-query"; -export default function PersonaContainer() { +export default function SuspensePersonaContainer() { const { data: personas } = useSuspenseQuery(personaQueries.list()); return ( diff --git a/packages/front/src/app/chat-rooms/personas/_components/index.ts b/packages/front/src/app/chat-rooms/personas/_components/index.ts index 90d1c75..a5ea257 100644 --- a/packages/front/src/app/chat-rooms/personas/_components/index.ts +++ b/packages/front/src/app/chat-rooms/personas/_components/index.ts @@ -1,2 +1,2 @@ export { default as PersonaCard } from "./PersonaCard"; -export { default as PersonaContainer } from "./PersonaContainer"; +export { default as SuspensePersonaContainer } from "./SuspensePersonaContainer"; diff --git a/packages/front/src/app/chat-rooms/personas/page.tsx b/packages/front/src/app/chat-rooms/personas/page.tsx index b84d899..23a8a66 100644 --- a/packages/front/src/app/chat-rooms/personas/page.tsx +++ b/packages/front/src/app/chat-rooms/personas/page.tsx @@ -1,7 +1,7 @@ "use client"; import { BackButton } from "@/app/_components"; -import { PersonaContainer } from "@/app/chat-rooms/personas/_components"; +import { SuspensePersonaContainer } from "@/app/chat-rooms/personas/_components"; import { SsgoiTransition } from "@ssgoi/react"; import { Suspense } from "react"; @@ -22,7 +22,7 @@ export default function PersonasPage() {

    Loading...}> - + diff --git a/packages/front/src/app/chat/[id]/_components/AILoadingMessage.tsx b/packages/front/src/app/chat/[id]/_components/AILoadingMessage.tsx index 9a5c940..1ba59a2 100644 --- a/packages/front/src/app/chat/[id]/_components/AILoadingMessage.tsx +++ b/packages/front/src/app/chat/[id]/_components/AILoadingMessage.tsx @@ -1,5 +1,6 @@ "use client"; -import { ProfileCard } from "@/app/chat/[id]/_components"; +import { SuspenseProfileCard } from "@/app/chat/[id]/_components"; +import { Suspense } from "react"; import Markdown from "react-markdown"; interface AILoadingMessageProps { @@ -11,7 +12,13 @@ export default function AILoadingMessage({ return (
    - + + } + > + +
    {streamingMessage} diff --git a/packages/front/src/app/chat/[id]/_components/ChatCard.tsx b/packages/front/src/app/chat/[id]/_components/ChatCard.tsx index d490a28..f8faa26 100644 --- a/packages/front/src/app/chat/[id]/_components/ChatCard.tsx +++ b/packages/front/src/app/chat/[id]/_components/ChatCard.tsx @@ -1,10 +1,10 @@ "use client"; -import { useAuth } from "@/app/_hooks"; -import { ProfileCard } from "@/app/chat/[id]/_components"; +import { useSuspenseAuth } from "@/app/_hooks"; +import { SuspenseProfileCard } from "@/app/chat/[id]/_components"; import { transition } from "@ssgoi/react"; import { slide } from "@ssgoi/react/transitions"; import { Message } from "common"; -import { memo } from "react"; +import { memo, Suspense } from "react"; import Markdown from "react-markdown"; interface ChatCardProps extends Message { @@ -17,7 +17,7 @@ export default memo(function ChatCard({ id, content, }: ChatCardProps) { - const user = useAuth(); + const user = useSuspenseAuth(); if (author === "Gemini") { return ( @@ -30,7 +30,13 @@ export default memo(function ChatCard({ })} className={`flex lg:items-end mb-2 lg:flex-row flex-col lg:justify-start items-start`} > - + + } + > + +
    {content} diff --git a/packages/front/src/app/chat/[id]/_components/EmptyChatCard.tsx b/packages/front/src/app/chat/[id]/_components/EmptyChatCard.tsx index 40f03c8..dd25159 100644 --- a/packages/front/src/app/chat/[id]/_components/EmptyChatCard.tsx +++ b/packages/front/src/app/chat/[id]/_components/EmptyChatCard.tsx @@ -1,6 +1,7 @@ -import ProfileCard from "@/app/chat/[id]/_components/ProfileCard"; +import SuspenseProfileCard from "@/app/chat/[id]/_components/SuspenseProfileCard"; import { transition } from "@ssgoi/react"; import { fade } from "@ssgoi/react/transitions"; +import { Suspense } from "react"; export default function EmptyChatCard() { return ( @@ -12,7 +13,13 @@ export default function EmptyChatCard() { })} >
    - + + } + > + +
    diff --git a/packages/front/src/app/chat/[id]/_components/SideBar.tsx b/packages/front/src/app/chat/[id]/_components/SideBar.tsx index 615a094..5eb82ed 100644 --- a/packages/front/src/app/chat/[id]/_components/SideBar.tsx +++ b/packages/front/src/app/chat/[id]/_components/SideBar.tsx @@ -1,6 +1,6 @@ "use client"; -import { ProfileCard } from "@/app/chat/[id]/_components/"; +import { SuspenseProfileCard } from "@/app/chat/[id]/_components/"; import { Suspense } from "react"; import { IoChatboxEllipses } from "react-icons/io5"; @@ -15,7 +15,7 @@ export default function SideBar() {
    채팅방
    Loading......}> - +
    diff --git a/packages/front/src/app/chat/[id]/_components/ChatList.tsx b/packages/front/src/app/chat/[id]/_components/SuspenseChatList.tsx similarity index 90% rename from packages/front/src/app/chat/[id]/_components/ChatList.tsx rename to packages/front/src/app/chat/[id]/_components/SuspenseChatList.tsx index f3ea8e4..f5a12ec 100644 --- a/packages/front/src/app/chat/[id]/_components/ChatList.tsx +++ b/packages/front/src/app/chat/[id]/_components/SuspenseChatList.tsx @@ -9,7 +9,7 @@ import { useEffect, useRef } from "react"; interface ChatListProps { streamingMessage?: string; } -export default function ChatList({ streamingMessage }: ChatListProps) { +export default function SuspenseChatList({ streamingMessage }: ChatListProps) { const { id } = useParams(); const messagesEndRef = useRef(null); diff --git a/packages/front/src/app/chat/[id]/_components/ChatRoom.tsx b/packages/front/src/app/chat/[id]/_components/SuspenseChatRoom.tsx similarity index 86% rename from packages/front/src/app/chat/[id]/_components/ChatRoom.tsx rename to packages/front/src/app/chat/[id]/_components/SuspenseChatRoom.tsx index 7524e1a..5d1b6c6 100644 --- a/packages/front/src/app/chat/[id]/_components/ChatRoom.tsx +++ b/packages/front/src/app/chat/[id]/_components/SuspenseChatRoom.tsx @@ -1,12 +1,12 @@ "use client"; -import { useAuth } from "@/app/_hooks"; +import { useSuspenseAuth } from "@/app/_hooks"; import { ChatSendForm, AILoadingMessage, EmptyChatCard, + SuspenseChatList, } from "@/app/chat/[id]/_components"; -import ChatList from "@/app/chat/[id]/_components/ChatList"; import { useChat, useChatHistoryUpdater, @@ -16,8 +16,8 @@ import { Message } from "common"; import { useParams } from "next/navigation"; import { Suspense } from "react"; -export default function ChatRoom() { - const user = useAuth(); +export default function SuspenseChatRoom() { + const user = useSuspenseAuth(); const { id } = useParams(); const { displayedText, addChunk, setText, reset } = useTypingEffect(); const { historyUpdater } = useChatHistoryUpdater(Number(id)); @@ -45,7 +45,7 @@ export default function ChatRoom() { <>
    Loading...
    }> - + {isAiThinking && !displayedText && } {displayedText && } diff --git a/packages/front/src/app/chat/[id]/_components/ProfileCard.tsx b/packages/front/src/app/chat/[id]/_components/SuspenseProfileCard.tsx similarity index 92% rename from packages/front/src/app/chat/[id]/_components/ProfileCard.tsx rename to packages/front/src/app/chat/[id]/_components/SuspenseProfileCard.tsx index 3919215..c2b05fb 100644 --- a/packages/front/src/app/chat/[id]/_components/ProfileCard.tsx +++ b/packages/front/src/app/chat/[id]/_components/SuspenseProfileCard.tsx @@ -1,7 +1,7 @@ "use client"; import ToggleButton from "@/app/_components/ToggleButton"; -import { useAuth } from "@/app/_hooks"; +import { useSuspenseAuth } from "@/app/_hooks"; import { chatRoomMutations, chatRoomQueries } from "@/app/_queries"; import { useMutation, useSuspenseQuery } from "@tanstack/react-query"; import Image from "next/image"; @@ -11,9 +11,9 @@ interface ProfileCardProps { size: "small" | "large"; } -export default function ProfileCard({ size }: ProfileCardProps) { +export default function SuspenseProfileCard({ size }: ProfileCardProps) { const { id } = useParams(); - const user = useAuth(); + const user = useSuspenseAuth(); const { data } = useSuspenseQuery(chatRoomQueries.detail(Number(id))); const { mutate } = useMutation(chatRoomMutations.patchStatus(data.id)); const { persona } = data; diff --git a/packages/front/src/app/chat/[id]/_components/index.ts b/packages/front/src/app/chat/[id]/_components/index.ts index 6aa7b7c..44d8515 100644 --- a/packages/front/src/app/chat/[id]/_components/index.ts +++ b/packages/front/src/app/chat/[id]/_components/index.ts @@ -1,8 +1,8 @@ export { default as AILoadingMessage } from "./AILoadingMessage"; export { default as ChatCard } from "./ChatCard"; -export { default as ChatList } from "./ChatList"; -export { default as ChatRoom } from "./ChatRoom"; +export { default as SuspenseChatList } from "./SuspenseChatList"; +export { default as SuspenseChatRoom } from "./SuspenseChatRoom"; export { default as ChatSendForm } from "./ChatSendForm"; export { default as EmptyChatCard } from "./EmptyChatCard"; -export { default as ProfileCard } from "./ProfileCard"; +export { default as SuspenseProfileCard } from "./SuspenseProfileCard"; export { default as SideBar } from "./SideBar"; From 130e612c6dcb13f014746ce52d8338dce341d601 Mon Sep 17 00:00:00 2001 From: GuDoYoon Date: Mon, 1 Dec 2025 19:37:44 +0900 Subject: [PATCH 3/6] =?UTF-8?q?fix:=20import=20=EB=84=A4=EC=9D=B4=EB=B0=8D?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/app/chat/[id]/_components/SuspenseChatRoom.tsx | 4 ++-- packages/front/src/app/chat/[id]/_hooks/useChat.ts | 6 +++--- packages/front/src/app/chat/[id]/page.tsx | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/front/src/app/chat/[id]/_components/SuspenseChatRoom.tsx b/packages/front/src/app/chat/[id]/_components/SuspenseChatRoom.tsx index 5d1b6c6..a2cc2da 100644 --- a/packages/front/src/app/chat/[id]/_components/SuspenseChatRoom.tsx +++ b/packages/front/src/app/chat/[id]/_components/SuspenseChatRoom.tsx @@ -8,7 +8,7 @@ import { SuspenseChatList, } from "@/app/chat/[id]/_components"; import { - useChat, + useSuspenseChat, useChatHistoryUpdater, useTypingEffect, } from "@/app/chat/[id]/_hooks"; @@ -21,7 +21,7 @@ export default function SuspenseChatRoom() { const { id } = useParams(); const { displayedText, addChunk, setText, reset } = useTypingEffect(); const { historyUpdater } = useChatHistoryUpdater(Number(id)); - const { isAiThinking, sendMessage } = useChat(Number(id), { + const { isAiThinking, sendMessage } = useSuspenseChat(Number(id), { onStream: (chunk: string) => addChunk(chunk), onStreamError: (message: string) => { setText(`[오류 발생] ${message}`); diff --git a/packages/front/src/app/chat/[id]/_hooks/useChat.ts b/packages/front/src/app/chat/[id]/_hooks/useChat.ts index a6b1d51..0653c3c 100644 --- a/packages/front/src/app/chat/[id]/_hooks/useChat.ts +++ b/packages/front/src/app/chat/[id]/_hooks/useChat.ts @@ -3,7 +3,7 @@ import { useCallback, useEffect, useState } from "react"; import { Message, User } from "common"; import { io, Socket } from "socket.io-client"; -import { useAuth } from "@/app/_hooks"; +import { useSuspenseAuth } from "@/app/_hooks"; import { toast } from "react-toastify"; interface UseChatOptions { @@ -13,8 +13,8 @@ interface UseChatOptions { onMessage?: (message: Message) => void; } -export const useChat = (roomId?: number, options?: UseChatOptions) => { - const user = useAuth(); +export const useSuspenseChat = (roomId?: number, options?: UseChatOptions) => { + const user = useSuspenseAuth(); const [socket, setSocket] = useState(null); const [isConnected, setIsConnected] = useState(false); const [isAiThinking, setIsAiThinking] = useState(false); diff --git a/packages/front/src/app/chat/[id]/page.tsx b/packages/front/src/app/chat/[id]/page.tsx index 21a54d2..0e66481 100644 --- a/packages/front/src/app/chat/[id]/page.tsx +++ b/packages/front/src/app/chat/[id]/page.tsx @@ -8,7 +8,7 @@ const DynamicSideBarWithNoSSR = dynamic( { ssr: false }, ); const DynamicChatRoomWithNoSSR = dynamic( - () => import("@/app/chat/[id]/_components/ChatRoom"), + () => import("@/app/chat/[id]/_components/SuspenseChatRoom"), { ssr: false }, ); From d7c1ba4c4ba56ed2ae74ecc88ca7dd078bdf9ea5 Mon Sep 17 00:00:00 2001 From: GuDoYoon Date: Tue, 2 Dec 2025 03:53:40 +0900 Subject: [PATCH 4/6] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F?= =?UTF-8?q?=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/back/src/chat/chat.gateway.ts | 1 - .../front/src/app/chat/[id]/_components/ChatCard.tsx | 12 ------------ .../app/chat/[id]/_components/SuspenseChatList.tsx | 11 ++--------- .../app/chat/[id]/_components/SuspenseChatRoom.tsx | 3 ++- 4 files changed, 4 insertions(+), 23 deletions(-) diff --git a/packages/back/src/chat/chat.gateway.ts b/packages/back/src/chat/chat.gateway.ts index 7b3a5f5..5864477 100644 --- a/packages/back/src/chat/chat.gateway.ts +++ b/packages/back/src/chat/chat.gateway.ts @@ -164,7 +164,6 @@ export class ChatGateway { async handleDisconnect(@ConnectedSocket() socket: Socket) { const roomId = socket.data.roomId; - console.log(socket.data.user); if (roomId) { await this.chatService.removeActiveUser(Number(roomId), socket.id); diff --git a/packages/front/src/app/chat/[id]/_components/ChatCard.tsx b/packages/front/src/app/chat/[id]/_components/ChatCard.tsx index f8faa26..759699c 100644 --- a/packages/front/src/app/chat/[id]/_components/ChatCard.tsx +++ b/packages/front/src/app/chat/[id]/_components/ChatCard.tsx @@ -22,12 +22,6 @@ export default memo(function ChatCard({ if (author === "Gemini") { return (
    diff --git a/packages/front/src/app/chat/[id]/_components/SuspenseChatList.tsx b/packages/front/src/app/chat/[id]/_components/SuspenseChatList.tsx index f5a12ec..6fec848 100644 --- a/packages/front/src/app/chat/[id]/_components/SuspenseChatList.tsx +++ b/packages/front/src/app/chat/[id]/_components/SuspenseChatList.tsx @@ -4,12 +4,9 @@ import { chatRoomQueries } from "@/app/_queries"; import { ChatCard } from "@/app/chat/[id]/_components"; import { useSuspenseQuery } from "@tanstack/react-query"; import { useParams } from "next/navigation"; -import { useEffect, useRef } from "react"; +import { useRef } from "react"; -interface ChatListProps { - streamingMessage?: string; -} -export default function SuspenseChatList({ streamingMessage }: ChatListProps) { +export default function SuspenseChatList() { const { id } = useParams(); const messagesEndRef = useRef(null); @@ -17,10 +14,6 @@ export default function SuspenseChatList({ streamingMessage }: ChatListProps) { chatRoomQueries.history(Number(id)!), ); - useEffect(() => { - messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); - }, [messages, streamingMessage]); - return ( <> {messages.map((msg, index) => ( diff --git a/packages/front/src/app/chat/[id]/_components/SuspenseChatRoom.tsx b/packages/front/src/app/chat/[id]/_components/SuspenseChatRoom.tsx index a2cc2da..22b550e 100644 --- a/packages/front/src/app/chat/[id]/_components/SuspenseChatRoom.tsx +++ b/packages/front/src/app/chat/[id]/_components/SuspenseChatRoom.tsx @@ -45,10 +45,11 @@ export default function SuspenseChatRoom() { <>
    Loading...
    }> - + {isAiThinking && !displayedText && } {displayedText && } +
    From b589d3277519476e21ebfa50fd84e9cb97714d47 Mon Sep 17 00:00:00 2001 From: GuDoYoon Date: Tue, 2 Dec 2025 14:33:20 +0900 Subject: [PATCH 5/6] =?UTF-8?q?feat:=20ChatSendForm=EC=97=90=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20useChat=20?= =?UTF-8?q?=ED=9B=85=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/app/chat/[id]/_components/ChatSendForm.tsx | 13 +++++++++++-- .../app/chat/[id]/_components/SuspenseChatRoom.tsx | 9 +++++---- packages/front/src/app/chat/[id]/_hooks/index.ts | 6 +++--- packages/front/src/app/chat/[id]/_hooks/useChat.ts | 8 +++++--- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/packages/front/src/app/chat/[id]/_components/ChatSendForm.tsx b/packages/front/src/app/chat/[id]/_components/ChatSendForm.tsx index e07e54e..63b92b3 100644 --- a/packages/front/src/app/chat/[id]/_components/ChatSendForm.tsx +++ b/packages/front/src/app/chat/[id]/_components/ChatSendForm.tsx @@ -1,13 +1,17 @@ "use client"; -import { useState } from "react"; +import { RefObject, useState } from "react"; import { FiSend } from "react-icons/fi"; interface ChatSendFormProps { onSubmit: (msg: string) => void; + messageRef?: RefObject; } -export default function ChatSendForm({ onSubmit }: ChatSendFormProps) { +export default function ChatSendForm({ + onSubmit, + messageRef, +}: ChatSendFormProps) { const [inputMessage, setInputMessage] = useState(""); const handleSendMessage = (e: React.FormEvent) => { @@ -15,6 +19,11 @@ export default function ChatSendForm({ onSubmit }: ChatSendFormProps) { if (inputMessage.trim() === "") return; onSubmit(inputMessage); setInputMessage(""); + if (messageRef && messageRef.current) { + setTimeout(() => { + messageRef.current?.scrollIntoView({ behavior: "smooth" }); + }, 100); + } }; return ( diff --git a/packages/front/src/app/chat/[id]/_components/SuspenseChatRoom.tsx b/packages/front/src/app/chat/[id]/_components/SuspenseChatRoom.tsx index 22b550e..b8a1861 100644 --- a/packages/front/src/app/chat/[id]/_components/SuspenseChatRoom.tsx +++ b/packages/front/src/app/chat/[id]/_components/SuspenseChatRoom.tsx @@ -8,20 +8,21 @@ import { SuspenseChatList, } from "@/app/chat/[id]/_components"; import { - useSuspenseChat, + useChat, useChatHistoryUpdater, useTypingEffect, } from "@/app/chat/[id]/_hooks"; import { Message } from "common"; import { useParams } from "next/navigation"; -import { Suspense } from "react"; +import { Suspense, useRef } from "react"; export default function SuspenseChatRoom() { const user = useSuspenseAuth(); const { id } = useParams(); + const messagesEndRef = useRef(null); const { displayedText, addChunk, setText, reset } = useTypingEffect(); const { historyUpdater } = useChatHistoryUpdater(Number(id)); - const { isAiThinking, sendMessage } = useSuspenseChat(Number(id), { + const { isAiThinking, sendMessage } = useChat(user, Number(id), { onStream: (chunk: string) => addChunk(chunk), onStreamError: (message: string) => { setText(`[오류 발생] ${message}`); @@ -51,7 +52,7 @@ export default function SuspenseChatRoom() { {displayedText && }
    - + ); } diff --git a/packages/front/src/app/chat/[id]/_hooks/index.ts b/packages/front/src/app/chat/[id]/_hooks/index.ts index 1cb6f6c..5c4b577 100644 --- a/packages/front/src/app/chat/[id]/_hooks/index.ts +++ b/packages/front/src/app/chat/[id]/_hooks/index.ts @@ -1,3 +1,3 @@ -export * from "./useChat"; -export * from "./useTypingEffect"; -export * from "./useChatHistoryUpdater"; +export { useChat } from "./useChat"; +export { useChatHistoryUpdater } from "./useChatHistoryUpdater"; +export { useTypingEffect } from "./useTypingEffect"; diff --git a/packages/front/src/app/chat/[id]/_hooks/useChat.ts b/packages/front/src/app/chat/[id]/_hooks/useChat.ts index 0653c3c..1706626 100644 --- a/packages/front/src/app/chat/[id]/_hooks/useChat.ts +++ b/packages/front/src/app/chat/[id]/_hooks/useChat.ts @@ -3,7 +3,6 @@ import { useCallback, useEffect, useState } from "react"; import { Message, User } from "common"; import { io, Socket } from "socket.io-client"; -import { useSuspenseAuth } from "@/app/_hooks"; import { toast } from "react-toastify"; interface UseChatOptions { @@ -13,8 +12,11 @@ interface UseChatOptions { onMessage?: (message: Message) => void; } -export const useSuspenseChat = (roomId?: number, options?: UseChatOptions) => { - const user = useSuspenseAuth(); +export const useChat = ( + user: User, + roomId?: number, + options?: UseChatOptions, +) => { const [socket, setSocket] = useState(null); const [isConnected, setIsConnected] = useState(false); const [isAiThinking, setIsAiThinking] = useState(false); From b42a66fb8ad5722dcffcdc73e8ece5b71883e213 Mon Sep 17 00:00:00 2001 From: GuDoYoon Date: Tue, 2 Dec 2025 14:44:48 +0900 Subject: [PATCH 6/6] =?UTF-8?q?feat:=20=ED=97=A4=EB=8D=94=20=EB=B0=8F=20?= =?UTF-8?q?=ED=83=AD=EC=97=90=20=EB=8B=A4=ED=81=AC=EB=AA=A8=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/app/chat-rooms/_components/ChatHeader.tsx | 2 +- .../src/app/chat-rooms/_components/ChatRoomCard.tsx | 2 +- .../src/app/chat-rooms/_components/ChatRoomTab.tsx | 10 +++++----- packages/front/src/app/chat-rooms/page.tsx | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/front/src/app/chat-rooms/_components/ChatHeader.tsx b/packages/front/src/app/chat-rooms/_components/ChatHeader.tsx index 64a04e3..40b7394 100644 --- a/packages/front/src/app/chat-rooms/_components/ChatHeader.tsx +++ b/packages/front/src/app/chat-rooms/_components/ChatHeader.tsx @@ -9,7 +9,7 @@ import { export default function ChatHeader() { return ( -
    +
    로고
    diff --git a/packages/front/src/app/chat-rooms/_components/ChatRoomCard.tsx b/packages/front/src/app/chat-rooms/_components/ChatRoomCard.tsx index bd76830..ed75abf 100644 --- a/packages/front/src/app/chat-rooms/_components/ChatRoomCard.tsx +++ b/packages/front/src/app/chat-rooms/_components/ChatRoomCard.tsx @@ -9,7 +9,7 @@ export default function ChatRoomCard({ id, persona }: ChatRoom) {
    diff --git a/packages/front/src/app/chat-rooms/_components/ChatRoomTab.tsx b/packages/front/src/app/chat-rooms/_components/ChatRoomTab.tsx index b96b52e..8b38fd0 100644 --- a/packages/front/src/app/chat-rooms/_components/ChatRoomTab.tsx +++ b/packages/front/src/app/chat-rooms/_components/ChatRoomTab.tsx @@ -23,13 +23,13 @@ export default function ChatRoomTab() { }; return ( -
    +