-
Notifications
You must be signed in to change notification settings - Fork 1
✨Feat : 로그인 디자인 수정 & api 연결 완료 #100
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
Changes from 8 commits
0c399de
900f904
352888d
01199c9
b6f0735
9191d8d
eefa22f
e2c0397
0a1560a
f4ac547
70e4c51
bc7356d
d326c3d
1602a4f
1dd6934
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| 'use client'; | ||
| import { useEffect } from 'react'; | ||
| import { useRouter } from 'next/router'; | ||
| import { exchangeTempToken } from '@/shared/api/auth'; | ||
| import Loading from '@/pages/loading'; | ||
|
|
||
| export default function AuthCallbackPage() { | ||
| const router = useRouter(); | ||
|
|
||
| useEffect(() => { | ||
| if (!router.isReady) return; | ||
|
|
||
| const searchParams = new URLSearchParams(router.asPath.split('?')[1]); | ||
| const tempToken = searchParams.get('temp_token'); | ||
| console.log('tempToken:', tempToken); | ||
|
|
||
| if (!tempToken) return; | ||
|
|
||
| const handleLogin = async () => { | ||
| try { | ||
| const res = await exchangeTempToken(tempToken); | ||
| console.log('API 응답:', res); | ||
|
|
||
| if (res.success) { | ||
| console.log('redirect to main 페이지'); | ||
| router.replace('/main'); | ||
| } else { | ||
| alert(res.message || '로그인에 실패했습니다.'); | ||
| router.replace('/auth'); | ||
| } | ||
| } catch (error) { | ||
| console.error('오류:', error); | ||
| router.replace('/auth'); | ||
| } | ||
| }; | ||
|
|
||
| handleLogin(); | ||
| }, [router.isReady, router.asPath]); | ||
|
|
||
| return <Loading />; | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,17 +1,33 @@ | ||||||||||||||
| 'use client'; | ||||||||||||||
KongMezu marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
| import { useState } from 'react'; | ||||||||||||||
| import { Icon } from '@/shared/icons'; | ||||||||||||||
| import Image from 'next/image'; | ||||||||||||||
| import { useRouter } from 'next/router'; | ||||||||||||||
| import { cn } from '@/shared/lib'; | ||||||||||||||
| import LoginButton from '@/pages/auth/components/LoginButton'; | ||||||||||||||
| import RecentLoginBubble from '@/pages/auth/components/RecentLoginBubble'; | ||||||||||||||
| import LoginButton from '@/shared/components/auth/LoginButton'; | ||||||||||||||
| import RecentLoginBubble from '@/shared/components/auth/RecentLoginBubble'; | ||||||||||||||
| import { useRecentLogin } from '@/shared/hooks/useRecentLogin'; | ||||||||||||||
|
|
||||||||||||||
| export default function LoginPage() { | ||||||||||||||
| const [recentPlatform, setRecentPlatform] = useState<string | null>(null); | ||||||||||||||
| const { recentPlatform, saveRecentPlatform } = useRecentLogin(); | ||||||||||||||
| const router = useRouter(); | ||||||||||||||
|
|
||||||||||||||
| //로그인 | ||||||||||||||
| const handleLoginClick = (platform: string) => { | ||||||||||||||
| alert(`${platform} 로그인 준비중`); | ||||||||||||||
| console.log(`${platform} 로그인 버튼 클릭`); | ||||||||||||||
| setRecentPlatform(platform); | ||||||||||||||
| saveRecentPlatform(platform); | ||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 최근 로그인 말풍선이 항상 숨겨집니다 - saveRecentPlatform(platform);
+ saveRecentPlatform(platformDisplay);🤖 Prompt for AI Agents |
||||||||||||||
| const base = process.env.NEXT_PUBLIC_BACKEND_URL; | ||||||||||||||
| const url = | ||||||||||||||
| platform === '카카오' | ||||||||||||||
| ? `${base}/oauth2/authorization/kakao` | ||||||||||||||
| : `${base}/oauth2/authorization/google`; | ||||||||||||||
| window.location.href = url; | ||||||||||||||
KongMezu marked this conversation as resolved.
Show resolved
Hide resolved
Comment on lines
+32
to
+37
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 환경 변수 누락 시 사용자 피드백 추가 필요 - if (!base) {
- console.error('NEXT_PUBLIC_BACKEND_URL is not defined');
- return;
- }
+ if (!base) {
+ const message = '로그인 서버 설정이 누락되어 로그인을 진행할 수 없습니다.';
+ if (process.env.NODE_ENV === 'development') {
+ console.error(message);
+ }
+ alert(message); // 앱의 알림/토스트 유틸이 있다면 그쪽을 사용해 주세요.
+ return;
+ }🤖 Prompt for AI Agents |
||||||||||||||
| }; | ||||||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
|
|
||||||||||||||
| //비회원 로그인 | ||||||||||||||
| const handleGuestClick = () => { | ||||||||||||||
| if (document.referrer && document.referrer !== window.location.href) { | ||||||||||||||
| router.back(); | ||||||||||||||
| } else { | ||||||||||||||
| router.push('/main'); | ||||||||||||||
| } | ||||||||||||||
| }; | ||||||||||||||
|
Comment on lines
+41
to
47
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. document.referrer 사용의 신뢰성 문제
이전 페이지 정보를 라우터 쿼리로 전달하는 방식을 권장합니다: // 로그인 페이지로 이동할 때
router.push({ pathname: '/auth', query: { returnUrl: router.asPath } });
// handleGuestClick에서
const handleGuestClick = () => {
const returnUrl = router.query.returnUrl as string;
if (returnUrl) {
router.push(returnUrl);
} else {
router.push('/main');
}
};🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
| return ( | ||||||||||||||
|
|
@@ -48,25 +64,27 @@ export default function LoginPage() { | |||||||||||||
| <section | ||||||||||||||
| className={cn( | ||||||||||||||
| 'w-full flex flex-col items-center text-center', | ||||||||||||||
| 'px-[6.8rem] pt-[5.2rem]', | ||||||||||||||
| 'px-[6.8rem] pt-[6rem]', | ||||||||||||||
| )} | ||||||||||||||
| > | ||||||||||||||
| {/* 타이틀 */} | ||||||||||||||
| <div className='flex flex-col items-center gap-[3.3rem] mb-[3.2rem]'> | ||||||||||||||
| <h1 className='text-headline-lg-serif text-mint-900'>글다</h1> | ||||||||||||||
| <p className='text-label-md text-mint-900'> | ||||||||||||||
| {/* 로고 영역 */} | ||||||||||||||
| <div className='flex flex-col items-center'> | ||||||||||||||
| <Image | ||||||||||||||
| src='/assets/Logo.svg' | ||||||||||||||
| alt='글다 로고' | ||||||||||||||
| width={112} | ||||||||||||||
| height={140} | ||||||||||||||
| className='mb-[2.8rem]' | ||||||||||||||
| priority | ||||||||||||||
| /> | ||||||||||||||
| <p className='text-label-serif text-mint-900 mt-[5rem] mb-[2.8rem]'> | ||||||||||||||
| 만화 속 부천 여행 | ||||||||||||||
| <br /> | ||||||||||||||
| 10개 명소를 탐험하고 엽서를 모아보세요! | ||||||||||||||
| </p> | ||||||||||||||
| </div> | ||||||||||||||
|
|
||||||||||||||
| {/* 로고 */} | ||||||||||||||
| <div className='p-[3.2rem] mb-[3.2rem]'> | ||||||||||||||
| <Icon name='Stamp' size={132} color='mint-400' /> | ||||||||||||||
| </div> | ||||||||||||||
|
|
||||||||||||||
| <div className='flex flex-col items-center gap-[2.1rem]'> | ||||||||||||||
| <div className='flex flex-col items-center mt-[5rem] gap-[2.1rem]'> | ||||||||||||||
| <p className='text-label-lg text-gray-400'>start with</p> | ||||||||||||||
|
|
||||||||||||||
| <div className='flex gap-[1.5rem] relative'> | ||||||||||||||
|
|
@@ -78,7 +96,7 @@ export default function LoginPage() { | |||||||||||||
| /> | ||||||||||||||
| {recentPlatform === '카카오' && ( | ||||||||||||||
| <div | ||||||||||||||
| className='absolute -top-[3.8rem] left-1/2 -translate-x-1/2 | ||||||||||||||
| className='absolute -top-[2.5rem] left-1/2 -translate-x-1/2 | ||||||||||||||
| w-auto min-w-max h-auto flex-shrink-0 pointer-events-none' | ||||||||||||||
| > | ||||||||||||||
| <RecentLoginBubble /> | ||||||||||||||
|
|
@@ -94,7 +112,7 @@ export default function LoginPage() { | |||||||||||||
| /> | ||||||||||||||
| {recentPlatform === '구글' && ( | ||||||||||||||
| <div | ||||||||||||||
| className='absolute -top-[3.8rem] left-1/2 -translate-x-1/2 | ||||||||||||||
| className='absolute -top-[2.5rem] left-1/2 -translate-x-1/2 | ||||||||||||||
| w-auto min-w-max h-auto flex-shrink-0 pointer-events-none' | ||||||||||||||
| > | ||||||||||||||
| <RecentLoginBubble /> | ||||||||||||||
|
|
@@ -103,14 +121,18 @@ export default function LoginPage() { | |||||||||||||
| </div> | ||||||||||||||
| </div> | ||||||||||||||
|
|
||||||||||||||
| <p className='text-label-md text-gray-400 cursor-pointer underline underline-offset-[0.25rem]'> | ||||||||||||||
| <p | ||||||||||||||
| className='text-label-md text-gray-400 cursor-pointer underline underline-offset-[0.25rem]' | ||||||||||||||
| onClick={handleGuestClick} | ||||||||||||||
| > | ||||||||||||||
| 비회원 로그인 | ||||||||||||||
| </p> | ||||||||||||||
| </div> | ||||||||||||||
|
|
||||||||||||||
| {/* 안내문 */} | ||||||||||||||
| <p className='mt-[5rem] text-label-sm text-gray-400'> | ||||||||||||||
| 비회원은 스탬프 저장과 공유 기능을 사용할 수 없습니다. | ||||||||||||||
| </p> | ||||||||||||||
| <div className='mt-[5rem] text-label-md text-gray-400 whitespace-nowrap text-ellipsis overflow-hidden text-center'> | ||||||||||||||
| <p>비회원은 스탬프 저장과 공유 기능을 사용할 수 없습니다.</p> | ||||||||||||||
| </div> | ||||||||||||||
|
Comment on lines
+144
to
+146
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 중요 안내문이 잘릴 수 있음
다음 diff를 적용하여 텍스트를 여러 줄로 표시하도록 수정하세요: - <div className='mt-[5rem] text-label-md text-gray-400 whitespace-nowrap text-ellipsis overflow-hidden text-center'>
+ <div className='mt-[5rem] text-label-md text-gray-400 text-center'>
<p>비회원은 스탬프 저장과 공유 기능을 사용할 수 없습니다.</p>
</div>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
| </section> | ||||||||||||||
| </main> | ||||||||||||||
| ); | ||||||||||||||
|
|
||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import { apiAuth } from '@/shared/api/instance'; | ||
| import type { ApiResponse, TokenData } from '@/shared/types/authtypes'; | ||
| import { setTokens } from '@/shared/utils/token'; | ||
|
|
||
| export const exchangeTempToken = async (tempToken: string) => { | ||
| const { data } = await apiAuth.post<ApiResponse<TokenData>>( | ||
| '/api/auth/temp-token/exchange', | ||
| { tempToken }, | ||
| ); | ||
|
|
||
| if (data.success) { | ||
| const { accessToken, refreshToken } = data.data; | ||
| setTokens(accessToken, refreshToken); | ||
| } | ||
|
|
||
| return data; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,67 +1,82 @@ | ||
| // SVGO 해결후 Image → Icon으로 교체 예정 | ||
| 'use client'; | ||
| import Image from 'next/image'; | ||
| import { cva, type VariantProps } from 'class-variance-authority'; | ||
| import { cn } from '@/shared/lib'; | ||
|
|
||
|
|
||
| const loginButtonVariants = cva( | ||
| ` | ||
| flex justify-center items-center flex-shrink-0 | ||
| w-[5rem] h-[5rem] rounded-full | ||
| shadow-[0_0_4px_rgba(0,0,0,0.30)] | ||
| transition-all duration-150 active:scale-95 | ||
| `, | ||
| { | ||
| variants: { | ||
| platform: { | ||
| google: 'bg-white', | ||
| kakao: 'bg-[#FEE500]', | ||
| }, | ||
| }, | ||
| defaultVariants: { | ||
| platform: 'google', | ||
| }, | ||
| } | ||
| ); | ||
|
|
||
| interface LoginButtonProps extends VariantProps<typeof loginButtonVariants> { | ||
| onClick: () => void; | ||
| className?: string; | ||
| } | ||
|
|
||
| export default function LoginButton({ | ||
| onClick, | ||
| platform, | ||
| className, | ||
| }: LoginButtonProps) { | ||
| const iconData = { | ||
| google: { | ||
| src: '/svgs/GoogleIcon.svg', | ||
| alt: 'Google Logo', | ||
| width: 36, | ||
| height: 36, | ||
| label: '구글 로그인', | ||
| }, | ||
| kakao: { | ||
| src: '/svgs/KakaoIcon.svg', | ||
| alt: 'Kakao Logo', | ||
| width: 28, | ||
| height: 28, | ||
| label: '카카오 로그인', | ||
| }, | ||
| }; | ||
|
|
||
| const { src, alt, width, height, label } = iconData[platform ?? 'google']; | ||
|
|
||
| return ( | ||
| <button | ||
| type="button" | ||
| onClick={onClick} | ||
| aria-label={label} | ||
| className={cn(loginButtonVariants({ platform }), className)} | ||
| > | ||
| <Image src={src} alt={alt} width={width} height={height} priority /> | ||
| </button> | ||
| ); | ||
| } | ||
| 'use client'; | ||
| import Image from 'next/image'; | ||
| import { useEffect } from 'react'; | ||
| import { useRecentLogin } from '@/shared/hooks/useRecentLogin'; | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| import { cva, type VariantProps } from 'class-variance-authority'; | ||
| import { cn } from '@/shared/lib'; | ||
|
|
||
|
|
||
| const loginButtonVariants = cva( | ||
| ` | ||
| flex justify-center items-center flex-shrink-0 | ||
| w-[5rem] h-[5rem] rounded-full | ||
| shadow-[0_0_4px_rgba(0,0,0,0.30)] | ||
| transition-all duration-150 active:scale-95 | ||
| `, | ||
| { | ||
| variants: { | ||
| platform: { | ||
| google: 'bg-white', | ||
| kakao: 'bg-[#FEE500]', | ||
| }, | ||
| }, | ||
| defaultVariants: { | ||
| platform: 'google', | ||
| }, | ||
| } | ||
| ); | ||
|
|
||
| interface LoginButtonProps extends VariantProps<typeof loginButtonVariants> { | ||
| onClick: () => void; | ||
| className?: string; | ||
| } | ||
KongMezu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| export default function LoginButton({ | ||
| onClick, | ||
| platform, | ||
| className, | ||
| }: LoginButtonProps) { | ||
| const iconData = { | ||
| google: { | ||
| src: '/assets/GoogleIcon.svg', | ||
| alt: 'Google Logo', | ||
| width: 36, | ||
| height: 36, | ||
| label: '구글 로그인', | ||
| }, | ||
| kakao: { | ||
| src: '/assets/KakaoIcon.svg', | ||
| alt: 'Kakao Logo', | ||
| width: 28, | ||
| height: 28, | ||
| label: '카카오 로그인', | ||
| }, | ||
| }; | ||
|
|
||
| if (!platform || !(platform in iconData)) { | ||
| throw new Error(`Invalid platform: ${platform}`); | ||
| } | ||
KongMezu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| const { src, alt, width, height, label } = iconData[platform as keyof typeof iconData]; | ||
|
|
||
| const handleClick = () => { | ||
| if (onClick) return onClick(); | ||
| const base = process.env.NEXT_PUBLIC_BACKEND_URL; | ||
| const url = | ||
| platform === 'kakao' | ||
| ? `${base}/oauth2/authorization/kakao` | ||
| : `${base}/oauth2/authorization/google`; | ||
|
|
||
| window.location.href = url; | ||
| }; | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
KongMezu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| return ( | ||
| <button | ||
| type="button" | ||
| onClick={handleClick} | ||
| aria-label={label} | ||
| className={cn(loginButtonVariants({ platform }), className)} | ||
| > | ||
| <Image src={src} alt={alt} width={width} height={height} priority /> | ||
| </button> | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'use client' 디렉티브 제거 필요
이 프로젝트는 Next.js Pages Router를 사용하고 있어 'use client' 디렉티브가 불필요합니다. App Router에서만 필요한 디렉티브입니다.
Based on learnings
이 diff를 적용하세요:
-'use client'; import { useEffect } from 'react';🤖 Prompt for AI Agents