From 31ab6062f376f28b03f94f6164b8caf4e8ca7e86 Mon Sep 17 00:00:00 2001 From: Seunghyo Date: Sat, 3 May 2025 17:07:18 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=A8feat(#163):=20google=20login=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/login/auth/page.tsx | 129 ++++++++++++++++--------- src/components/signup/SocialGoogle.tsx | 31 +++--- 2 files changed, 103 insertions(+), 57 deletions(-) diff --git a/src/app/login/auth/page.tsx b/src/app/login/auth/page.tsx index 21499ca..34f0b51 100644 --- a/src/app/login/auth/page.tsx +++ b/src/app/login/auth/page.tsx @@ -17,9 +17,12 @@ const Auth = () => { const [absoluteUrl, setAbsoluteUrl] = useState(''); const [storeUrl, setstoreUrl] = useState(''); const [type, setType] = useState(''); + const [oauthAccessToken, setOauthAccessToken] = useState(''); + const [provider, setProvider] = useState(''); useEffect(() => { if (typeof window !== 'undefined') { + //oauth 타입을 state에 저장 const params = new URL(window.location.href).searchParams; const typeParam = params.get('type'); setType(typeParam); @@ -31,29 +34,72 @@ const Auth = () => { }, []); useEffect(() => { - if (!absoluteUrl) { - return; - } + if (!absoluteUrl || !type) return; const getToken = async () => { const AUTHORIZATION_CODE = new URL(window.location.href).searchParams.get( 'code' ); + const TYPE = new URL(window.location.href).searchParams.get('type'); let tokenUrl = ''; - let provider: 'KAKAO' | 'GOOGLE' | 'NAVER'; if (!AUTHORIZATION_CODE || !TYPE) { console.error('Authorization Code or Type is missing'); return; } + //type에 따라 다른 토큰 url 지정 switch (TYPE) { case 'kakao': - tokenUrl = `https://kauth.kakao.com/oauth/token?grant_type=authorization_code&client_id=${REST_API_KEY}&redirect_uri=${absoluteUrl}&code=${AUTHORIZATION_CODE}`; - provider = 'KAKAO'; + setProvider('KAKAO'); + try { + const response = await axios.post( + 'https://kauth.kakao.com/oauth/token', + new URLSearchParams({ + grant_type: 'authorization_code', + client_id: REST_API_KEY, + redirect_uri: absoluteUrl, + code: AUTHORIZATION_CODE + }), + { + headers: { 'Content-Type': 'application/x-www-form-urlencoded' } + } + ); + setOauthAccessToken(response.data.access_token); + } catch (error) { + console.error(error); + clearLetterUrl(); + return; + } + break; case 'google': + setProvider('GOOGLE'); + try { + const body = new URLSearchParams({ + grant_type: 'authorization_code', + client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID!, + client_secret: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_SECRET!, + redirect_uri: absoluteUrl, + code: AUTHORIZATION_CODE + }); + + const response = await axios.post( + 'https://oauth2.googleapis.com/token', + body.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + } + } + ); + setOauthAccessToken(response.data.access_token); + } catch (error) { + console.error('Unsupported OAuth type:', type); + clearLetterUrl(); + return; + } break; case 'naver': break; @@ -61,49 +107,46 @@ const Auth = () => { console.error('Unknown OAuth type:', TYPE); return; } + }; + getToken(); + }, [absoluteUrl, type]); - try { - const response = await axios.post(tokenUrl, { - headers: { 'Content-Type': 'application/json' } - }); - - const oauthAccessToken = response.data.access_token; - - if (oauthAccessToken) { - login(provider, oauthAccessToken) - .then((res) => { - console.log('accessToken', res.data.accessToken); - setTokens(res.data.accessToken, res.data.refreshToken); - /* 온보딩 여부 저장 */ - setOnboarding(res.data.isProcessedOnboarding); + useEffect(() => { + try { + if (oauthAccessToken) { + login(provider, oauthAccessToken) + .then((res) => { + console.log('accessToken', res.data.accessToken); + setTokens(res.data.accessToken, res.data.refreshToken); + /* 온보딩 여부 저장 */ + setOnboarding(res.data.isProcessedOnboarding); + if (storeUrl) { + router.push(`/verify/letter?url=${storeUrl}`); + clearLetterUrl(); + } else { + router.push('/planet'); + } + }) + .catch((error) => { + if (error.response && error.response.status === 401) { + console.log('registerToken', error.response.data.registerToken); + setRegisterToken(error.response.data.registerToken); if (storeUrl) { - router.push(`/verify/letter?url=${storeUrl}`); + router.push(`/signup/step1?url=${storeUrl}`); clearLetterUrl(); } else { - router.push('/planet'); + router.push('/signup/step1'); } - }) - .catch((error) => { - if (error.response && error.response.status === 401) { - console.log('registerToken', error.response.data.registerToken); - setRegisterToken(error.response.data.registerToken); - if (storeUrl) { - router.push(`/signup/step1?url=${storeUrl}`); - clearLetterUrl(); - } else { - router.push('/signup/step1'); - } - } - }); - } - } catch (error) { - console.error(error); - clearLetterUrl(); - return; + } + }); } - }; - getToken(); - }, [absoluteUrl]); + } catch (error) { + console.log('oauth token 에러'); + console.error(error); + clearLetterUrl(); + return; + } + }, [oauthAccessToken]); return ( diff --git a/src/components/signup/SocialGoogle.tsx b/src/components/signup/SocialGoogle.tsx index ee8a6ae..4d46f06 100644 --- a/src/components/signup/SocialGoogle.tsx +++ b/src/components/signup/SocialGoogle.tsx @@ -8,33 +8,36 @@ import Loader, { LoaderContainer } from '../common/Loader'; const SocialGoogle = () => { const searchParams = useSearchParams(); const url = searchParams.get('url'); - const REST_API_KEY = process.env.NEXT_PUBLIC_REST_API_KEY; - const GOOGLE_URL = '/login/google'; const [absoluteUrl, setabsoluteUrl] = useState(''); useEffect(() => { if (typeof window !== 'undefined') { setabsoluteUrl( - window.location.protocol + '//' + window.location.host + '/login/kakao' + window.location.protocol + + '//' + + window.location.host + + '/login/auth?type=google' ); } }, []); const handleLogin = () => { - //이때 localStorage에 저장된 accessToken이 만료되었는지 확인해야함. - // if (accessToken) { - // if (url) { - // router.push(`/verify?url=${url}`); - // } else { - // router.push("/"); - // } - // setAccessToken(accessToken); - // } else { - //받은 편지를 통해 들어올 경우 url를 저장한다. if (url) { setLetterUrl(url); } - window.location.href = GOOGLE_URL; + const GOOGLE_CLIENT_ID = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID; + const scope = 'openid profile email'; + + const authUrl = [ + 'https://accounts.google.com/o/oauth2/v2/auth', + `?client_id=${GOOGLE_CLIENT_ID}`, + `&redirect_uri=${encodeURIComponent(absoluteUrl)}`, + `&response_type=code`, + `&scope=${encodeURIComponent(scope)}`, + `&access_type=offline`, + `&prompt=consent` + ].join(''); + window.location.href = authUrl; }; return ( From d0d6f42cced20891a1009dd47a6899aab2a917ff Mon Sep 17 00:00:00 2001 From: Seunghyo Date: Sat, 3 May 2025 23:05:33 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=E2=9C=A8feat(#163):=20mypage=20platform=20?= =?UTF-8?q?icon=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/mypage/page.tsx | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/app/mypage/page.tsx b/src/app/mypage/page.tsx index 90db1b5..cd131fb 100644 --- a/src/app/mypage/page.tsx +++ b/src/app/mypage/page.tsx @@ -17,6 +17,8 @@ const MyPage = () => { const [email, setEmail] = useState(''); const [planetCount, setPlanetCount] = useState(0); const [letterCount, setLetterCount] = useState(0); + const [platform, setPlatform] = useState(''); + const [loading, setLoading] = useState(true); useEffect(() => { @@ -62,7 +64,8 @@ const MyPage = () => { const response = await getUserInfo(); setName(response.data.name); setEmail(response.data.email); - console.log('회원정보 조회 성공:', response.data); + setPlatform(response.data.socialPlatform); + // console.log('회원정보 조회 성공:', response.data); } catch (error) { console.error('회원정보 조회 실패:', error); } @@ -78,6 +81,19 @@ const MyPage = () => { } }; + const EmailType = (platform): string => { + switch (platform) { + case 'GOOGLE': + return '/assets/icons/ic_google.svg'; + case 'KAKAO': + return '/assets/icons/ic_kakao_profile.svg'; + case 'NAVER': + return '/assets/icons/ic_naver.svg'; + default: + return ''; + } + }; + return ( {loading ? ( @@ -96,7 +112,11 @@ const MyPage = () => { {name}님의 스페이스 - +
{email}
@@ -242,6 +262,17 @@ const ProfileImage = styled.img` } `; +const iconSizes = { + GOOGLE: 20, + KAKAO: 20, + NAVER: 20 +} as const; + +const StyledIcon = styled.img<{ platform: keyof typeof iconSizes }>` + width: ${({ platform }) => iconSizes[platform]}px; + height: ${({ platform }) => iconSizes[platform]}px; +`; + const ProfileInfo = styled.div` display: flex; flex-direction: column; From 60fecb05ef4fde74cba8070c6c09b78a3ed9fbcc Mon Sep 17 00:00:00 2001 From: Seunghyo Date: Sun, 4 May 2025 13:18:06 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=E2=9C=A8feat(#163):=20=EC=A4=80=EB=B9=84?= =?UTF-8?q?=EC=A4=91=20alert=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/login/page.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index b19a229..172f063 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -10,6 +10,10 @@ interface OauthButtonProps { bgColor: string; } +const notReady = () => { + alert('준비 중입니다.'); +}; + export default function Login() { return ( @@ -24,6 +28,7 @@ export default function Login() { alt="Naver" width={26} height={26} + onClick={notReady} /> @@ -33,7 +38,9 @@ export default function Login() { - 로그인 없이 편지 작성해보기 + + 로그인 없이 편지 작성해보기 + ); From 86d90b8c355c8db9ab5f4b21ec2fd5ed4ca66433 Mon Sep 17 00:00:00 2001 From: Seunghyo Date: Sun, 4 May 2025 13:25:17 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=E2=9C=A8feat(#163):=20=EB=A6=AC=EB=B7=B0?= =?UTF-8?q?=20=EB=B0=98=EC=98=81(login=20=ED=99=94=EB=A9=B4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/login/auth/page.tsx | 2 -- src/app/login/page.tsx | 46 +++++++++++++++++++++++++------------ 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/app/login/auth/page.tsx b/src/app/login/auth/page.tsx index 34f0b51..d8c715b 100644 --- a/src/app/login/auth/page.tsx +++ b/src/app/login/auth/page.tsx @@ -42,8 +42,6 @@ const Auth = () => { const TYPE = new URL(window.location.href).searchParams.get('type'); - let tokenUrl = ''; - if (!AUTHORIZATION_CODE || !TYPE) { console.error('Authorization Code or Type is missing'); return; diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 172f063..1815cdb 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -14,6 +14,32 @@ const notReady = () => { alert('준비 중입니다.'); }; +const oauthButtons = [ + { + key: 'naver', + bgColor: '#03CF5D', + component: ( + Naver alert('준비 중입니다.')} + /> + ) + }, + { + key: 'google', + bgColor: '#FFFFFF', + component: + }, + { + key: 'kakao', + bgColor: '#FEE500', + component: + } +]; + export default function Login() { return ( @@ -22,21 +48,11 @@ export default function Login() { 편지로 수놓는 나의 스페이스 - - Naver - - - - - - - + {oauthButtons.map(({ key, bgColor, component }) => ( + + {component} + + ))} 로그인 없이 편지 작성해보기