Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@
"sockjs-client": "^1.6.1",
"swiper": "^12.0.3",
"tailwind-merge": "^3.3.1",
"zod": "^4.1.13"
"zod": "^4.1.13",
"zustand": "^5.0.11"
},
"devDependencies": {
"@commitlint/cli": "^20.1.0",
Expand Down
28 changes: 28 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/components/layout/gnb/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import Link from 'next/link';
import { usePathname } from 'next/navigation';

import { Icon } from '@/components/icon';
import { useAuth } from '@/providers';
import { useAuthStore } from '@/stores';

export const GNB = () => {
const pathname = usePathname();

const { isAuthenticated } = useAuth();
const { isAuthenticated } = useAuthStore();

const highLightPath = (path: string) => {
if (path === '/') {
Expand Down
4 changes: 2 additions & 2 deletions src/components/layout/header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import Link from 'next/link';
import { Icon } from '@/components/icon';
import { CowBell } from '@/components/layout/header/cow-bell';
import { HeaderLogin } from '@/components/layout/header/header-login';
import { useAuth } from '@/providers';
import { useAuthStore } from '@/stores';

export const Header = () => {
const { isAuthenticated } = useAuth();
const { isAuthenticated } = useAuthStore();

return (
<header className={`sticky top-0 z-100 w-full bg-white`}>
Expand Down
4 changes: 2 additions & 2 deletions src/components/pages/auth/login/login-toast-effect/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import { useEffect, useRef } from 'react';

import { Toast } from '@/components/ui';
import { useToast } from '@/components/ui/toast/core';
import { useAuth } from '@/providers';
import { useAuthStore } from '@/stores';

type Props = {
error?: string | string[];
};

export const LoginToastEffect = ({ error }: Props) => {
const { run } = useToast();
const { setIsAuthenticated } = useAuth();
const { setIsAuthenticated } = useAuthStore();
const lastErrorRef = useRef<string>('');

useEffect(() => {
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/use-auth/use-auth-login/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import axios, { AxiosError } from 'axios';

import { API } from '@/api';
import { normalizePath } from '@/lib/auth/utils';
import { useAuth } from '@/providers';
import { useAuthStore } from '@/stores';
import { LoginRequest } from '@/types/service/auth';
import { CommonErrorResponse } from '@/types/service/common';

Expand All @@ -29,7 +29,7 @@ export const useLogin = () => {
const [loginError, setLoginError] = useState<string | null>(null);
const clearLoginError = useCallback(() => setLoginError(null), []);

const { setIsAuthenticated } = useAuth();
const { setIsAuthenticated } = useAuthStore();

const handleLogin = async (payload: LoginRequest, formApi: { reset: () => void }) => {
setLoginError(null);
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/use-auth/use-auth-logout/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { useQueryClient } from '@tanstack/react-query';

import { API } from '@/api';
import { userKeys } from '@/lib/query-key/query-key-user';
import { useAuth } from '@/providers';
import { useAuthStore } from '@/stores';

export const useLogout = () => {
const queryClient = useQueryClient();

const { setIsAuthenticated } = useAuth();
const { setIsAuthenticated } = useAuthStore();

const handleLogout = async () => {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { useQuery } from '@tanstack/react-query';

import { API } from '@/api';
import { notificationKeys } from '@/lib/query-key/query-key-notification';
import { useAuth } from '@/providers';
import { useAuthStore } from '@/stores';
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 24, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n --type=ts --type=tsx "useGetNotificationUnreadCount" -C3

Repository: WeGo-Together/WeGo_FrontEnd

Length of output: 98


🏁 Script executed:

rg -n "useGetNotificationUnreadCount" -C3

Repository: WeGo-Together/WeGo_FrontEnd

Length of output: 2441


🏁 Script executed:

cat -n src/hooks/use-notification/use-notification-get-unread-count/index.ts

Repository: WeGo-Together/WeGo_FrontEnd

Length of output: 873


🏁 Script executed:

rg -n "useGetNotificationUnreadCount" --no-filename -B5 -A5 | grep -E "(data|unReadCount|useGetNotificationUnreadCount)" | head -30

Repository: WeGo-Together/WeGo_FrontEnd

Length of output: 487


useGetNotificationUnreadCount 훅의 반환 필드 정책 통일 필요

훅이 이제 명시적인 unReadCount 필드를 제공하므로, 기존 data 필드를 사용하던 소비자는 새로운 필드로 업데이트해야 합니다. src/providers/provider-notification/index.tsx 라인 24에서 const { data: unReadCount = 0 }const { unReadCount }로 변경해 주세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/use-notification/use-notification-get-unread-count/index.ts` at
line 5, 훅 useGetNotificationUnreadCount가 이제 명시적 필드 unReadCount를 반환하므로 기존 소비 코드에서
`const { data: unReadCount = 0 }`로 가져오던 부분을 업데이트해야 합니다; 해당 소비자(예:
provider-notification의 destructure)에서 `const { data: unReadCount = 0 }`를 `const
{ unReadCount }`로 바꾸고, 필요하면 기본값 처리(예: undefined -> 0)는 소비자 쪽에서 안전하게 처리하도록 변경하여
필드 이름 정책을 통일하세요.

Copy link
Member Author

Choose a reason for hiding this comment

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

unReadCount는 React Query로만 관리하고, 전역상태에 저장하지 않을 예정입니다.
사용되는 컴포넌트 마다 api를 호출하는 형태이지만, 캐시된 데이터를 호출하기 때문에 문제가 없습니다.

주요 포인트는 "서버상태는 React Query 로만 관리한다" 입니다.

Copy link
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the consumer code in provider-notification to see if it needs updating
cat -n src/providers/provider-notification/index.tsx | grep -A5 -B5 "useGetNotificationUnreadCount"

Length of output: 772


@Chiman2937, 설명 감사합니다! React Query로 서버 상태를 관리하는 아키텍처 결정에 동의합니다. 이는 서버 상태와 클라이언트 상태를 명확하게 분리하는 좋은 패턴입니다.

제 코멘트는 unReadCount를 Zustand로 옮기자는 의도가 아니었고, 단순히 필드명 일관성에 관한 것이었습니다. 현재 훅이 명시적으로 unReadCount 필드를 반환하는데, provider-notification/index.tsx의 24번째 라인에서 여전히 const { data: unReadCount = 0 }로 사용하고 있어서요. const { unReadCount }로 변경하면 훅의 의도가 더 명확해집니다.

다만 큰 이슈는 아니므로, 팀의 판단에 맡기겠습니다. 아키텍처 원칙 잘 이해했습니다! 👍


✏️ Learnings added
Learnt from: Chiman2937
Repo: WeGo-Together/WeGo_FrontEnd PR: 393
File: src/hooks/use-notification/use-notification-get-unread-count/index.ts:5-5
Timestamp: 2026-02-24T05:52:50.560Z
Learning: In WeGo_FrontEnd repository: Server state (e.g., unReadCount from API) should be managed only by React Query, not stored in Zustand global state. Each component calls the API but uses cached data from React Query. Principle: "서버상태는 React Query 로만 관리한다" (Server state managed only by React Query).

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Member Author

Choose a reason for hiding this comment

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

그 부분은 명확하게 이해하고 있고, 해당 부분 자체가 삭제될 예정이기 때문에 말씀드렸습니다

Copy link
Contributor

Choose a reason for hiding this comment

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

@Chiman2937, 알겠습니다! 해당 코드가 후속 PR에서 삭제될 예정이라면 지금 수정할 필요가 없네요. 말씀해주셔서 감사합니다. 👍


🧠 Learnings used
Learnt from: Chiman2937
Repo: WeGo-Together/WeGo_FrontEnd PR: 393
File: src/hooks/use-notification/use-notification-get-unread-count/index.ts:5-5
Timestamp: 2026-02-24T05:52:50.560Z
Learning: In the WeGo_FrontEnd repository, server state (e.g., unread counts from APIs) must be managed exclusively by React Query and not stored in Zustand or other client state. Components may call the API but should rely on React Query's cache for rendering. Apply this guideline to all hook implementations that deal with server state; avoid writing server-state data to Zustand and prefer React Query data selectors or hooks to access cached results.


export const useGetNotificationUnreadCount = () => {
const { isAuthenticated } = useAuth();
const { isAuthenticated } = useAuthStore();
const queryResult = useQuery({
queryKey: notificationKeys.unReadCount(),
queryFn: () => API.notificationService.getUnreadCount(),
Expand All @@ -14,10 +14,10 @@ export const useGetNotificationUnreadCount = () => {
retry: false,
});

const finalData = isAuthenticated ? (queryResult.data ?? 0) : 0;
const unReadCount = isAuthenticated ? (queryResult.data ?? 0) : 0;

return {
...queryResult,
data: finalData,
unReadCount,
};
};
2 changes: 1 addition & 1 deletion src/providers/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { AuthProvider, useAuth } from './provider-auth';
export { AuthProvider } from './provider-auth';
export { LazyMotionProvider } from './provider-lazy-motion';
export { MSWProvider } from './provider-msw';
export { NotificationProvider, useNotification } from './provider-notification';
Expand Down
28 changes: 9 additions & 19 deletions src/providers/provider-auth/index.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,19 @@
import React, { createContext, SetStateAction, useContext, useState } from 'react';
'use client';
import { useEffect } from 'react';

interface AuthContextType {
isAuthenticated: boolean;
setIsAuthenticated: React.Dispatch<SetStateAction<boolean>>;
}

const AuthContext = createContext<AuthContextType | null>(null);

export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) throw new Error('useAuth must be used in AuthProvider');
return context;
};
import { useAuthStore } from '@/stores';

interface Props {
children: React.ReactNode;
hasRefreshToken: boolean;
}

export const AuthProvider = ({ children, hasRefreshToken }: Props) => {
const [isAuthenticated, setIsAuthenticated] = useState(hasRefreshToken);
const { setIsAuthenticated } = useAuthStore();

useEffect(() => {
setIsAuthenticated(hasRefreshToken);
}, [hasRefreshToken, setIsAuthenticated]);

return (
<AuthContext.Provider value={{ isAuthenticated, setIsAuthenticated }}>
{children}
</AuthContext.Provider>
);
return <>{children}</>;
};
2 changes: 2 additions & 0 deletions src/stores/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { useAuthStore } from './useAuthStore';
export { useNotificationStore } from './useNotificationStore';
11 changes: 11 additions & 0 deletions src/stores/useAuthStore/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { create } from 'zustand';

interface AuthState {
isAuthenticated: boolean;
setIsAuthenticated: (value: boolean) => void;
}

export const useAuthStore = create<AuthState>((set) => ({
isAuthenticated: false,
setIsAuthenticated: (value) => set({ isAuthenticated: value }),
}));
17 changes: 17 additions & 0 deletions src/stores/useNotificationStore/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { create } from 'zustand';

import { NotificationItem } from '@/types/service/notification';

interface NotificationState {
receivedData: NotificationItem | null;
setReceivedData: (value: NotificationItem | null) => void;
hasNewNotification: boolean;
setHasNewNotification: (value: boolean) => void;
}

export const useNotificationStore = create<NotificationState>((set) => ({
receivedData: null,
setReceivedData: (value) => set({ receivedData: value }),
hasNewNotification: false,
setHasNewNotification: (value) => set({ hasNewNotification: value }),
}));