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
12 changes: 8 additions & 4 deletions src/app/(auth)/login/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,14 @@ export default function LoginForm() {
return;
}

const { user } = await response.json();
// 소속 팀이 있으면 해당 팀 페이지로, 없으면 팀 추가 페이지로
if (user?.teamId) {
router.push(`/${user.teamId}`);
// 로그인 API는 teamId(프로젝트 식별자 문자열)만 반환하므로
// 실제 그룹 페이지 경로에 필요한 숫자 groupId를 얻기 위해
// 유저 정보를 한 번 더 조회한다
const userRes = await fetch('/api/proxy/user');
if (userRes.ok) {
const userData = await userRes.json();
const groupId = userData?.memberships?.[0]?.group?.id;
router.push(groupId !== undefined ? `/${groupId}` : '/addteam');
} else {
router.push('/addteam');
}
Comment on lines 59 to 61

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

로그인 후 사용자 정보를 가져오는 데 실패했을 때, 에러를 던져서 catch 블록에서 처리하도록 하는 것이 더 안전해 보입니다. 현재 코드는 사용자 정보 조회에 실패하면 무조건 팀 추가 페이지로 보내는데, 이는 일시적인 네트워크 오류인 경우 사용자에게 혼란을 줄 수 있습니다. 에러를 발생시켜 catch 블록에서 '네트워크 오류' 메시지를 보여주는 것이 더 나은 사용자 경험을 제공할 것입니다.

Suggested change
} else {
router.push('/addteam');
}
} else {
throw new Error('Failed to fetch user data after login.');
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

이 코드리뷰는 고치지 않아도 됩니다. /addteam으로 보내는 이유는 로그인 성공 상태에서 토큰도 쿠키에 저장된 상태에서 유저 정보 조회가 일시적으로 실패한 경우 /addteam으로 보내면 거기서 다시 유저 정보를 받아오고, 팀이 있으면 자동으로 팀 페이지로 이동하도록 설계됐습니다. 즉 최악의 경우에도 로그인 상태는 유지되고 정상 경로로 복구됩니다.

AI 제안처럼 에러를 던지면:

로그인 성공 → 유저 정보 조회 실패 → 에러 → catch 블록 → setError('email', ...) → 사용자는 로그인 실패로 인식
이건 실제로 로그인은 됐는데 사용자한테 실패처럼 보여주는 거라 더 혼란스러울 수 있습니다.

Expand Down
4 changes: 3 additions & 1 deletion src/app/(auth)/signup/SignupForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,9 @@ export default function SignupForm() {
type="button"
className={styles.kakaoButton}
aria-label="카카오톡으로 회원가입"
disabled
onClick={() => {
window.location.href = '/api/auth/kakao';
}}
>
<Image src={kakaotalkButton} alt="카카오톡 회원가입" width={42} height={42} />
</button>
Expand Down
41 changes: 41 additions & 0 deletions src/app/(root)/landing/components/CtaButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
'use client';

import { useRouter } from 'next/navigation';
import { useCurrentUserQuery } from '@/shared/queries/user/useCurrentUserQuery';
import BaseButton from '@/components/Button/base/BaseButton';

type CtaButtonProps = {
className?: string;
};

/**
* 랜딩페이지 "지금 시작하기" 버튼
*
* 로그인 상태 분기:
* - 비로그인 → /login
* - 로그인 → /{teamId} (팀이 있는 경우) 또는 /addteam
*/
export default function CtaButton({ className }: CtaButtonProps) {
const router = useRouter();
const { data: user, isPending } = useCurrentUserQuery({ retry: false });

const handleClick = () => {
if (isPending) return;

if (!user) {
router.push('/login');
return;
}

// user.teamId는 프로젝트 식별자 문자열('20-1' 등)이라 팀 페이지 경로에 사용 불가
// 실제 그룹 페이지 경로는 숫자 group.id를 사용해야 함
const groupId = user.memberships?.[0]?.group?.id;
router.push(groupId !== undefined ? `/${groupId}` : '/addteam');
};

return (
<div className={className}>
<BaseButton onClick={handleClick}>지금 시작하기</BaseButton>
</div>
);
}
8 changes: 2 additions & 6 deletions src/app/(root)/landing/components/CtaSection/CtaSection.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import Link from 'next/link';

import BaseButton from '@/components/Button/base/BaseButton';
import CtaButton from '../CtaButton';
import styles from './CtaSection.module.css';

export default function CtaSection() {
Expand All @@ -9,9 +7,7 @@ export default function CtaSection() {
<div className={styles.inner}>
<h2 className={styles.title}>지금 바로 시작해보세요</h2>
<p className={styles.desc}>팀원 모두와 같은 방향, 같은 속도로 나아가는 가장 쉬운 방법</p>
<Link href="/login" className={styles.ctaLink}>
<BaseButton>지금 시작하기</BaseButton>
</Link>
<CtaButton className={styles.ctaLink} />
</div>
</section>
);
Expand Down
7 changes: 2 additions & 5 deletions src/app/(root)/landing/components/HeroSection/HeroSection.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import Image from 'next/image';
import Link from 'next/link';

import BaseButton from '@/components/Button/base/BaseButton';
import CtaButton from '../CtaButton';
import gradationLogo from '@/assets/icons/landing/gradation_logo.svg';
import landingPC01 from '@/assets/img/landing/pc/landingPC_01.svg';
import landingTablet01 from '@/assets/img/landing/tablet/landingTablet_01.svg';
Expand Down Expand Up @@ -29,9 +28,7 @@ export default function HeroSection() {
</div>
</div>

<Link href="/login" className={styles.ctaLink}>
<BaseButton>지금 시작하기</BaseButton>
</Link>
<CtaButton className={styles.ctaLink} />
</div>

{/*
Expand Down
14 changes: 7 additions & 7 deletions src/app/(root)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import Image from 'next/image';
import { usePathname, useRouter } from 'next/navigation';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { useCurrentUserQuery } from '@/shared/queries/user/useCurrentUserQuery';
import { Sidebar, MobileHeader } from '@/components/sidebar';
import TeamSidebarDropdown from './[teamid]/_domain/components/Team/TeamSidebarDropdown';
import humanBig from '@/assets/buttons/human/humanBig.svg';
Expand All @@ -11,12 +11,12 @@
export default function RootLayout({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
const router = useRouter();
const { data: user, isPending } = useCurrentUser();
const { data: user, isPending } = useCurrentUserQuery({ retry: false });

Check warning on line 14 in src/app/(root)/layout.tsx

View workflow job for this annotation

GitHub Actions / build

'isPending' is assigned a value but never used

// isPending: 최초 로딩 중 (undefined)
// user === null: 로딩 완료 후 비로그인
// user !== null: 로그인
const isLoggedIn = !isPending && user !== null && user !== undefined;
// user가 존재하면 로그인, 없으면 비로그인
// isPending 중엔도 캐시된 데이터가 있으면 user는 정의되므로
// isPending으로 차단하지 않아 캐시 히트 시 깨박임 방지
const isLoggedIn = !!user;
const isLanding = pathname === '/';

// 자체 사이드바가 없는 페이지에서만 root layout 사이드바 표시
Expand Down Expand Up @@ -46,7 +46,7 @@
isLoggedIn={isLoggedIn}
onProfileClick={handleProfileClick}
onLogout={handleLogout}
onLogoClick={() => router.push('/addteam')}
onLogoClick={() => router.push('/')}
profileImage={
user?.image ? (
<Image
Expand Down
3 changes: 2 additions & 1 deletion src/app/api/auth/logout/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ export async function POST() {
try {
await clearAuthCookies();
return NextResponse.json({ message: '로그아웃 되었습니다.' });
} catch {
} catch (error) {
console.error('[logout] 로그아웃 처리 중 오류:', error);
return NextResponse.json({ message: '로그아웃 중 오류가 발생했습니다.' }, { status: 500 });
}
}
2 changes: 1 addition & 1 deletion src/components/sidebar/MobileHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export default function MobileHeader({

const handleLogout = onLogout ?? defaultLogout;
const handleProfileClick = onProfileClick ?? (() => router.push('/mypage'));
const handleLogoClick = onLogoClick ?? (() => router.push('/addteam'));
const handleLogoClick = onLogoClick ?? (() => router.push('/'));

useEffect(() => {
if (!showProfileMenu) return;
Expand Down
2 changes: 1 addition & 1 deletion src/components/sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export default function Sidebar({

const handleLogout = onLogout ?? defaultLogout;
const handleProfileClick = onProfileClick ?? (() => router.push('/mypage'));
const handleLogoClick = onLogoClick ?? (() => router.push('/addteam'));
const handleLogoClick = onLogoClick ?? (() => router.push('/'));

useEffect(() => {
if (!showProfileMenu) return;
Expand Down
4 changes: 2 additions & 2 deletions src/components/sidebar/styles/MobileHeader.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
background: var(--color-background-inverse);
}

@media (max-width: 1199px) {
@media (max-width: 1200px) {
.header {
display: flex;
}
}

@media (min-width: 768px) and (max-width: 1199px) {
@media (min-width: 768px) and (max-width: 1200px) {
.header {
height: 72px;
padding: 0 24px;
Expand Down
2 changes: 1 addition & 1 deletion src/components/sidebar/styles/Sidebar.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
z-index: 50;
}

@media (max-width: 1199px) {
@media (max-width: 1200px) {
.sidebar {
display: none;
}
Expand Down
4 changes: 2 additions & 2 deletions src/shared/queries/user/useCurrentUserQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export function currentUserQueryOptions() {
});
}

export function useCurrentUserQuery() {
return useQuery(currentUserQueryOptions());
export function useCurrentUserQuery(options?: { retry?: boolean | number }) {
return useQuery({ ...currentUserQueryOptions(), ...options });
}
Comment on lines +17 to 19

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

현재 options 타입이 { retry?: boolean | number }로 매우 제한적입니다. PR 설명에 'retry 등'이라고 언급된 것을 보면 앞으로 다른 옵션도 사용될 가능성이 있어 보입니다. currentUserQueryOptions의 반환 타입을 활용하여 타입을 더 유연하게 만들면 재사용성과 확장성이 향상될 것입니다. 이렇게 하면 retry 외에 staleTime, enabledreact-query가 제공하는 모든 옵션을 타입 안전하게 사용할 수 있습니다.

Suggested change
export function useCurrentUserQuery(options?: { retry?: boolean | number }) {
return useQuery({ ...currentUserQueryOptions(), ...options });
}
export function useCurrentUserQuery(options?: Partial<ReturnType<typeof currentUserQueryOptions>>) {
return useQuery({ ...currentUserQueryOptions(), ...options });
}


export function useSuspenseCurrentUserQuery() {
Expand Down
Loading