diff --git a/client/src/api/services/rss.ts b/client/src/api/services/rss.ts index a655a23..bf7a84e 100644 --- a/client/src/api/services/rss.ts +++ b/client/src/api/services/rss.ts @@ -1,8 +1,7 @@ import { BLOG } from "@/constants/endpoints"; import { axiosInstance } from "@/api/instance"; -import { RegisterRss } from "@/types/rss"; -import { RegisterResponse } from "@/types/rss"; +import { RegisterRss, RegisterResponse } from "@/types/rss"; export const registerRss = async (data: RegisterRss): Promise => { const response = await axiosInstance.post(BLOG.RSS.REGISTRER_RSS, data); diff --git a/client/src/components/RssRegistration/RssUrlInput.tsx b/client/src/components/RssRegistration/RssUrlInput.tsx index c6e34af..644a722 100644 --- a/client/src/components/RssRegistration/RssUrlInput.tsx +++ b/client/src/components/RssRegistration/RssUrlInput.tsx @@ -7,7 +7,6 @@ interface RssUrlInputProps { platform: PlatformType; value: string; onChange: (e: React.ChangeEvent) => void; - placeholder?: string; } export const RssUrlInput = ({ platform, value, onChange }: RssUrlInputProps) => { diff --git a/client/src/components/admin/layout/AdminMember.tsx b/client/src/components/admin/layout/AdminMember.tsx index b64a55c..6e30e9d 100644 --- a/client/src/components/admin/layout/AdminMember.tsx +++ b/client/src/components/admin/layout/AdminMember.tsx @@ -25,7 +25,7 @@ export default function AdminMember() { const onError = (error: AxiosError) => { const errorMessage = typeof error.response?.data === "string" ? error.response.data : error.response?.data || error.message; - alert(`관리자 등록 실패: ${errorMessage}`); + alert(`관리자 등록 실패: ${JSON.stringify(errorMessage)}`); }; const handleChange = (e: React.ChangeEvent, name: string) => { diff --git a/client/src/components/chart/PieChartItem.tsx b/client/src/components/chart/PieChartItem.tsx index 74c51bf..4066ff8 100644 --- a/client/src/components/chart/PieChartItem.tsx +++ b/client/src/components/chart/PieChartItem.tsx @@ -43,7 +43,7 @@ export default function PieChartItem({ data, title }: BarType) { } /> {data.map((entry, index) => ( - + ))} diff --git a/client/src/components/chat/ChatButton.tsx b/client/src/components/chat/ChatButton.tsx index 5f0cafa..30ece7b 100644 --- a/client/src/components/chat/ChatButton.tsx +++ b/client/src/components/chat/ChatButton.tsx @@ -1,5 +1,4 @@ -import { MessageCircleMore } from "lucide-react"; -import { X } from "lucide-react"; +import { MessageCircleMore, X } from "lucide-react"; import { useSidebar } from "@/components/ui/sidebar"; diff --git a/client/src/components/chat/ChatItem.tsx b/client/src/components/chat/ChatItem.tsx index f5af9fb..2879d7c 100644 --- a/client/src/components/chat/ChatItem.tsx +++ b/client/src/components/chat/ChatItem.tsx @@ -18,18 +18,16 @@ export default function ChatItem({ chatItem, isSameUser }: ChatItemProps) { return (
{!isSameUser ? ( - <> - - - - - {/* 이름, 시간 */} - - {chatItem.username} - {formatTime(chatItem.timestamp)} - + + + + + {/* 이름, 시간 */} + + {chatItem.username} + {formatTime(chatItem.timestamp)} - + ) : ( <> )} diff --git a/client/src/components/chat/layout/ChatFooter.tsx b/client/src/components/chat/layout/ChatFooter.tsx index 55a54ea..8417f61 100644 --- a/client/src/components/chat/layout/ChatFooter.tsx +++ b/client/src/components/chat/layout/ChatFooter.tsx @@ -6,11 +6,10 @@ import { SheetFooter } from "@/components/ui/sheet"; import { useKeyboardShortcut } from "@/hooks/common/useKeyboardShortcut"; -import { useChatValueStroe } from "@/store/useChatStore"; -import { useChatStore } from "@/store/useChatStore"; +import { useChatValueStore, useChatStore } from "@/store/useChatStore"; export default function ChatFooter() { - const { message, setMessage } = useChatValueStroe(); + const { message, setMessage } = useChatValueStore(); const { sendMessage } = useChatStore(); const handleSendMessage = () => { diff --git a/client/src/components/chat/layout/ChatSection.tsx b/client/src/components/chat/layout/ChatSection.tsx index c808380..55c25c1 100644 --- a/client/src/components/chat/layout/ChatSection.tsx +++ b/client/src/components/chat/layout/ChatSection.tsx @@ -7,43 +7,49 @@ import ChatSkeleton from "@/components/chat/layout/ChatSkeleton"; import { ScrollArea } from "@/components/ui/scroll-area"; import { useChatStore } from "@/store/useChatStore"; +import { ChatType } from "@/types/chat"; + +const FullChatWarning = () => ( +
+ +

채팅창 인원이 500명 이상입니다

+

잠시 기다렸다가 새로고침을 해주세요

+
+); + +const RenderHistory = ({ chatHistory, isFull }: { chatHistory: ChatType[]; isFull: boolean }) => { + if (chatHistory.length === 0) return ; + return isFull ? ( + + ) : ( + + {chatHistory.map((item, index) => { + const isSameUser = index > 0 && chatHistory[index - 1]?.username === item.username; + return ; + })} + + ); +}; export default function ChatSection({ isFull }: { isFull: boolean }) { const scrollRef = useRef(null); const { chatHistory } = useChatStore(); + useEffect(() => { if (scrollRef.current) { const scrollContent = scrollRef.current.querySelector("[data-radix-scroll-area-viewport]"); - if (scrollContent) { + if (scrollContent && chatHistory.length > 0) { scrollContent.scrollTo({ top: scrollContent.scrollHeight, behavior: "smooth", }); } } - }, [chatHistory]); - - const RenderHistory = () => { - if (chatHistory.length === 0) return ; - return isFull ? ( -
- -

채팅창 인원이 500명 이상입니다

-

잠시 기다렸다가 새로고침을 해주세요

-
- ) : ( - - {chatHistory.map((item, index) => { - const isSameUser = index > 0 && chatHistory[index - 1].username === item.username; - return ; - })} - - ); - }; + }, [chatHistory.length]); return ( - {RenderHistory()} + ); } diff --git a/client/src/components/layout/Header.tsx b/client/src/components/layout/Header.tsx index cbc5be0..cc83e4e 100644 --- a/client/src/components/layout/Header.tsx +++ b/client/src/components/layout/Header.tsx @@ -43,9 +43,9 @@ export default function Header() {
{/* 로고 영역 */} -
- Logo location.reload()} /> -
+ {/* 중앙 검색 버튼 */}
diff --git a/client/src/components/layout/SideButton.tsx b/client/src/components/layout/SideButton.tsx index 2295c33..8a1f31d 100644 --- a/client/src/components/layout/SideButton.tsx +++ b/client/src/components/layout/SideButton.tsx @@ -1,6 +1,4 @@ -import { Home } from "lucide-react"; -import { ArrowUp } from "lucide-react"; -import { ChartArea } from "lucide-react"; +import { Home, ArrowUp, ChartArea } from "lucide-react"; import { Chat } from "@/components/chat/Chat"; import { OpenChat } from "@/components/chat/ChatButton"; diff --git a/client/src/components/search/SearchButton.tsx b/client/src/components/search/SearchButton.tsx index e13e2c2..25ed438 100644 --- a/client/src/components/search/SearchButton.tsx +++ b/client/src/components/search/SearchButton.tsx @@ -2,7 +2,7 @@ import { Search } from "lucide-react"; export default function SearchButton({ handleSearchModal }: { handleSearchModal: () => void }) { return ( -
검색 -
+ ); } diff --git a/client/src/components/search/SearchFilters/FilterButton.tsx b/client/src/components/search/SearchFilters/FilterButton.tsx index 2e601c7..159a918 100644 --- a/client/src/components/search/SearchFilters/FilterButton.tsx +++ b/client/src/components/search/SearchFilters/FilterButton.tsx @@ -34,14 +34,14 @@ export default function FilterButton() {
{filterOptions.map(({ label, filter, icon }) => ( -
handleFilterClick(filter)} > {icon} {label} -
+ ))}
diff --git a/client/src/components/search/SearchHigilight.tsx b/client/src/components/search/SearchHigilight.tsx index a224833..1b3af00 100644 --- a/client/src/components/search/SearchHigilight.tsx +++ b/client/src/components/search/SearchHigilight.tsx @@ -1,3 +1,5 @@ +import { useMemo } from "react"; + interface SearchHighlightProps { text: string; highlight: string; @@ -8,8 +10,11 @@ function escapeRegExp(string: string): string { } export default function SearchHighlight({ text, highlight }: SearchHighlightProps) { - const escapedHighlight = escapeRegExp(highlight); - const parts = text.split(new RegExp(`(${escapedHighlight})`, "gi")); + const parts = useMemo(() => { + if (!highlight) return [text]; + const escapedHighlight = escapeRegExp(highlight); + return text.split(new RegExp(`(${escapedHighlight})`, "gi")); + }, [text, highlight]); return ( <> diff --git a/client/src/components/search/SearchResults/SearchResultList.tsx b/client/src/components/search/SearchResults/SearchResultList.tsx index 740ff55..7ad837b 100644 --- a/client/src/components/search/SearchResults/SearchResultList.tsx +++ b/client/src/components/search/SearchResults/SearchResultList.tsx @@ -18,8 +18,8 @@ export default function SearchResults() { page, pageSize: RESULT_PER_PAGE, }); - const totalItems = data?.data.totalCount || 0; - const totalPages = data?.data.totalPages || 0; + const totalItems = data?.data.totalCount ?? 0; + const totalPages = data?.data.totalPages ?? 0; const results = data?.data.result || []; const renderContent = { //검색 전 diff --git a/client/src/hooks/queries/useInfiniteScrollQuery.ts b/client/src/hooks/queries/useInfiniteScrollQuery.ts index 2587b93..9f9d001 100644 --- a/client/src/hooks/queries/useInfiniteScrollQuery.ts +++ b/client/src/hooks/queries/useInfiniteScrollQuery.ts @@ -19,7 +19,7 @@ export function useInfiniteScrollQuery({ queryFn: ({ pageParam = 0 }) => fetchFn({ limit: 12, - lastId: pageParam as number, + lastId: pageParam, }), getNextPageParam: (lastPage) => { if (!lastPage.hasMore) return undefined; diff --git a/client/src/hooks/queries/useSearch.ts b/client/src/hooks/queries/useSearch.ts index 6b35a1f..d9be53b 100644 --- a/client/src/hooks/queries/useSearch.ts +++ b/client/src/hooks/queries/useSearch.ts @@ -2,7 +2,6 @@ import { useState, useEffect } from "react"; import { debounce } from "@/utils/debounce"; -// import axios from "axios"; //mockAPI사용시 import { getSearch } from "@/api/services/search"; import { SearchRequest } from "@/types/search"; import { useQuery, useQueryClient } from "@tanstack/react-query"; diff --git a/client/src/store/useChatStore.ts b/client/src/store/useChatStore.ts index 5971d7c..2d8ebb5 100644 --- a/client/src/store/useChatStore.ts +++ b/client/src/store/useChatStore.ts @@ -17,6 +17,39 @@ interface ChatStore { export const useChatStore = create((set) => { let socket: Socket | null = null; + // 소켓 초기화 함수 + const initializeSocket = () => { + if (socket) return socket; // 이미 존재하면 그대로 반환 + + socket = io(CHAT_SERVER_URL, { + path: "/chat", + transports: ["websocket"], + reconnection: true, // 자동 재연결 활성화 + reconnectionAttempts: 5, // 최대 5번 재시도 + reconnectionDelay: 1000, // 1초 간격으로 재시도 + }); + + // 서버 연결 성공 시 + socket.on("connect", () => {}); + + // 서버로부터 메시지 받기 + socket.on("message", (data) => { + set((state) => ({ + chatHistory: [...state.chatHistory, data], + })); + }); + + // 사용자 수 업데이트 받기 + socket.on("updateUserCount", (data) => { + set({ userCount: data.userCount }); + }); + + // 서버 연결 해제 시 + socket.on("disconnect", () => {}); + + return socket; + }; + return { chatHistory: [], userCount: 0, @@ -24,25 +57,7 @@ export const useChatStore = create((set) => { // Socket 연결 함수 connect: () => { if (socket) return; // 이미 연결된 경우 중복 방지 - - socket = io(CHAT_SERVER_URL, { path: "/chat", transports: ["websocket"] }); - - // 서버 연결 성공 시 - socket.on("connect", () => {}); - // 서버로부터 메시지 받기 - socket.on("message", (data) => { - set((state) => ({ - chatHistory: [...state.chatHistory, data], - })); - }); - - // 사용자 수 업데이트 받기 - socket.on("updateUserCount", (data) => { - set({ userCount: data.userCount }); - }); - - // 서버 연결 해제 시 - socket.on("disconnect", () => {}); + initializeSocket(); }, // Socket 연결 해제 함수 @@ -54,28 +69,39 @@ export const useChatStore = create((set) => { // 이전 채팅 기록 받아오기 getHistory: () => { if (socket) { + socket.emit("getHistory"); socket.on("chatHistory", (data) => { set(() => ({ chatHistory: data, })); }); } else { + const newSocket = initializeSocket(); + newSocket.emit("getHistory"); } }, + // 메시지 전송 함수 sendMessage: (message: string) => { if (socket) { socket.emit("message", { message }); } else { + // 소켓이 없으면 연결 후 메시지 전송 + const newSocket = initializeSocket(); + newSocket.on("connect", () => { + newSocket.emit("message", { message }); + }); } }, }; }); + interface ChatValue { message: string; setMessage: (newMessage: string) => void; } -export const useChatValueStroe = create((set) => ({ + +export const useChatValueStore = create((set) => ({ message: "", setMessage: (newMessage: string) => set({ message: newMessage }), }));