From 5c24f7deecf62c8cd2b4cd4e7bc78bbe2bd07b36 Mon Sep 17 00:00:00 2001 From: yyypearl Date: Sun, 4 May 2025 16:52:23 +0900 Subject: [PATCH 1/9] =?UTF-8?q?=E2=99=BB=20refactor(#165):=20=EC=86=8C?= =?UTF-8?q?=EC=85=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20OauthButton=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/login/page.tsx | 74 ++++++------------ src/components/signup/OauthButton.tsx | 102 +++++++++++++++++++++++++ src/components/signup/SocialGoogle.tsx | 84 -------------------- src/components/signup/SocialKakao.tsx | 95 ----------------------- src/constants/oauth.ts | 20 +++++ src/types/login.ts | 1 + 6 files changed, 145 insertions(+), 231 deletions(-) create mode 100644 src/components/signup/OauthButton.tsx delete mode 100644 src/components/signup/SocialGoogle.tsx delete mode 100644 src/components/signup/SocialKakao.tsx create mode 100644 src/constants/oauth.ts create mode 100644 src/types/login.ts diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 1815cdb0..aa9a34b6 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -1,45 +1,17 @@ 'use client'; -import SocialKakao from '@/components/signup/SocialKakao'; -import SocialGoogle from '@/components/signup/SocialGoogle'; +import Loader, { LoaderContainer } from '@/components/common/Loader'; +import OauthButton from '@/components/signup/OauthButton'; +import { OAUTH } from '@/constants/oauth'; import { theme } from '@/styles/theme'; +import { loginType } from '@/types/login'; +import { Suspense } from 'react'; import styled from 'styled-components'; -import Image from 'next/image'; - -interface OauthButtonProps { - bgColor: string; -} 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 ( @@ -48,11 +20,23 @@ export default function Login() { 편지로 수놓는 나의 스페이스 - {oauthButtons.map(({ key, bgColor, component }) => ( - - {component} - - ))} + + + + } + > + {OAUTH.map((item) => ( + + ))} + 로그인 없이 편지 작성해보기 @@ -157,20 +141,6 @@ const OauthWrapper = styled.div` justify-content: center; `; -const OauthButton = styled.button` - width: 69px; - height: 69px; - border-radius: 50%; - border: none; - background-color: ${({ bgColor }) => bgColor}; - display: flex; - align-items: center; - overflow: hidden; - justify-content: center; - cursor: pointer; - transition: background-color 0.3s; -`; - const LetterBtnText = styled.div` ${(props) => props.theme.fonts.caption02}; color: ${theme.colors.gray400}; diff --git a/src/components/signup/OauthButton.tsx b/src/components/signup/OauthButton.tsx new file mode 100644 index 00000000..3936add7 --- /dev/null +++ b/src/components/signup/OauthButton.tsx @@ -0,0 +1,102 @@ +import React, { Suspense, useEffect, useState } from 'react'; +import Image from 'next/image'; +import styled from 'styled-components'; +import { useSearchParams } from 'next/navigation'; +import { setLetterUrl } from '@/utils/storage'; +import { loginType } from '@/types/login'; + +interface OauthButtonProps { + loginType: loginType; + bgColor: string; + icon: string; + size: number; +} +const OauthButton = (props: OauthButtonProps) => { + const { loginType, bgColor, icon, size } = props; + + const searchParams = useSearchParams(); + const url = searchParams.get('url'); + const [redirectUri, setRedirectUri] = useState(''); + + useEffect(() => { + if (typeof window !== 'undefined') { + setRedirectUri( + window.location.protocol + + '//' + + window.location.host + + `/login/auth?type=${loginType}` + ); + } + }, []); + + const handleLogin = () => { + if (url) { + setLetterUrl(url); + } + let authUrl = ''; + + switch (loginType) { + case 'google': { + const GOOGLE_CLIENT_ID = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID; + const scope = 'openid profile email'; + authUrl = [ + 'https://accounts.google.com/o/oauth2/v2/auth', + `?client_id=${GOOGLE_CLIENT_ID}`, + `&redirect_uri=${encodeURIComponent(redirectUri)}`, + `&response_type=code`, + `&scope=${encodeURIComponent(scope)}`, + `&access_type=offline`, + `&prompt=consent` + ].join(''); + break; + } + + case 'kakao': { + const KAKAO_CLIENT_ID = process.env.NEXT_PUBLIC_KAKAO_CLIENT_ID; + authUrl = [ + 'https://kauth.kakao.com/oauth/authorize', + `?client_id=${KAKAO_CLIENT_ID}`, + `&redirect_uri=${encodeURIComponent(redirectUri)}`, + `&response_type=code` + ].join(''); + break; + } + + case 'naver': { + break; + } + } + + if (authUrl) { + window.location.href = authUrl; + } + }; + + return ( + + + + ); +}; + +export default OauthButton; + +const SocialButton = styled.button<{ $bgColor: string }>` + width: 69px; + height: 69px; + display: flex; + justify-content: center; + align-items: center; + border-radius: 50%; + border: none; + background-color: ${({ $bgColor }) => $bgColor}; + cursor: pointer; + transition: background-color 0.3s; +`; + +const SocialIcon = styled(Image)` + width: 100%; + height: 100%; + padding: 0 16px; + object-fit: contain; +`; diff --git a/src/components/signup/SocialGoogle.tsx b/src/components/signup/SocialGoogle.tsx deleted file mode 100644 index 4d46f065..00000000 --- a/src/components/signup/SocialGoogle.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import React, { Suspense, useEffect, useState } from 'react'; -import Image from 'next/image'; -import styled from 'styled-components'; -import { useSearchParams } from 'next/navigation'; -import { setLetterUrl } from '@/utils/storage'; -import Loader, { LoaderContainer } from '../common/Loader'; - -const SocialGoogle = () => { - const searchParams = useSearchParams(); - const url = searchParams.get('url'); - const [absoluteUrl, setabsoluteUrl] = useState(''); - - useEffect(() => { - if (typeof window !== 'undefined') { - setabsoluteUrl( - window.location.protocol + - '//' + - window.location.host + - '/login/auth?type=google' - ); - } - }, []); - - const handleLogin = () => { - if (url) { - setLetterUrl(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 ( - - - - ); -}; - -export default function SocialGooglePaging() { - return ( - - - - } - > - - - ); -} - -const SocialLoginBox = styled.div` - display: flex; - box-sizing: border-box; - width: 100%; - cursor: pointer; - display: flex; - justify-content: center; - align-items: center; -`; - -const StyledImage = styled(Image)` - width: 100%; - height: 100%; - padding: 0 16px; - object-fit: contain; -`; diff --git a/src/components/signup/SocialKakao.tsx b/src/components/signup/SocialKakao.tsx deleted file mode 100644 index 6128cb43..00000000 --- a/src/components/signup/SocialKakao.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React, { Suspense, useEffect, useState } from 'react'; -import Image from 'next/image'; -import styled from 'styled-components'; -import { useSearchParams } from 'next/navigation'; -import { setLetterUrl } from '@/utils/storage'; -import Loader, { LoaderContainer } from '../common/Loader'; - -const SocialKakao = () => { - const searchParams = useSearchParams(); - const url = searchParams.get('url'); - const REST_API_KEY = process.env.NEXT_PUBLIC_REST_API_KEY; - const [redirectUrl, setRedirectUrl] = useState(''); - const KAKAO_URL = `https://kauth.kakao.com/oauth/authorize?client_id=${REST_API_KEY}&redirect_uri=${redirectUrl}&response_type=code&url=${url}`; - - useEffect(() => { - if (typeof window !== 'undefined') { - setRedirectUrl( - window.location.protocol + - '//' + - window.location.host + - '/login/auth?type=kakao' - ); - } - }, []); - - const handleLogin = () => { - //이때 localStorage에 저장된 accessToken이 만료되었는지 확인해야함. - // if (accessToken) { - // if (url) { - // router.push(`/verify?url=${url}`); - // } else { - // router.push("/"); - // } - // setAccessToken(accessToken); - // } else { - //받은 편지를 통해 들어올 경우 url를 저장한다. - if (url) { - setLetterUrl(url); - } - // redirectUri가 준비된 뒤에 인가 URL 생성 - const params = new URLSearchParams({ - client_id: REST_API_KEY, - redirect_uri: redirectUrl, - response_type: 'code' - }); - if (url) { - params.set('url', url); - } - - window.location.href = `https://kauth.kakao.com/oauth/authorize?${params.toString()}`; - window.location.href = KAKAO_URL; - }; - - return ( - - - - ); -}; - -export default function SocialKakaoPaging() { - return ( - - - - } - > - - - ); -} - -const SocialLoginBox = styled.div` - display: flex; - box-sizing: border-box; - width: 100%; - cursor: pointer; - display: flex; - justify-content: center; - align-items: center; -`; - -const StyledImage = styled(Image)` - width: 100%; - height: 100%; - padding: 0 16px; - object-fit: contain; -`; diff --git a/src/constants/oauth.ts b/src/constants/oauth.ts new file mode 100644 index 00000000..f8b42de7 --- /dev/null +++ b/src/constants/oauth.ts @@ -0,0 +1,20 @@ +export const OAUTH = [ + { + key: 'naver', + bgColor: '#03CF5D', + icon: '/assets/icons/ic_naver.svg', + size: 26 + }, + { + key: 'google', + bgColor: '#FFFFFF', + icon: '/assets/icons/ic_google.svg', + size: 32 + }, + { + key: 'kakao', + bgColor: '#FEE500', + icon: '/assets/icons/ic_kakaotalk.svg', + size: 38 + } +]; \ No newline at end of file diff --git a/src/types/login.ts b/src/types/login.ts new file mode 100644 index 00000000..2be99043 --- /dev/null +++ b/src/types/login.ts @@ -0,0 +1 @@ +export type loginType = 'naver' | 'google' | 'kakao'; \ No newline at end of file From c58036fb52467a4da5a78ed7c7c1f714fce07d10 Mon Sep 17 00:00:00 2001 From: yyypearl Date: Sun, 4 May 2025 16:56:16 +0900 Subject: [PATCH 2/9] =?UTF-8?q?=E2=9A=99=20chore(#165):=20=EC=B9=B4?= =?UTF-8?q?=EC=B9=B4=EC=98=A4=20env=20=EB=B3=80=EC=88=98=EB=AA=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/signup/OauthButton.tsx | 2 +- src/hooks/useKakaoSDK.tsx | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/signup/OauthButton.tsx b/src/components/signup/OauthButton.tsx index 3936add7..9e4ea48b 100644 --- a/src/components/signup/OauthButton.tsx +++ b/src/components/signup/OauthButton.tsx @@ -52,7 +52,7 @@ const OauthButton = (props: OauthButtonProps) => { } case 'kakao': { - const KAKAO_CLIENT_ID = process.env.NEXT_PUBLIC_KAKAO_CLIENT_ID; + const KAKAO_CLIENT_ID = process.env.NEXT_PUBLIC_KAKAO_REST_API_KEY; authUrl = [ 'https://kauth.kakao.com/oauth/authorize', `?client_id=${KAKAO_CLIENT_ID}`, diff --git a/src/hooks/useKakaoSDK.tsx b/src/hooks/useKakaoSDK.tsx index 747e31a1..6bf54fa8 100644 --- a/src/hooks/useKakaoSDK.tsx +++ b/src/hooks/useKakaoSDK.tsx @@ -1,17 +1,17 @@ -import { useEffect, useState } from "react"; +import { useEffect, useState } from 'react'; const useKakaoSDK = () => { const [isKakaoLoaded, setIsKakaoLoaded] = useState(false); - const JS_KEY = process.env.NEXT_PUBLIC_JAVASCRIPT_KEY; + const JS_KEY = process.env.NEXT_PUBLIC_KAKAO_JAVASCRIPT_KEY; useEffect(() => { if (!JS_KEY) { - console.error("Kakao JavaScript key is missing"); + console.error('Kakao JavaScript key is missing'); return; } - const script = document.createElement("script"); - script.src = "https://developers.kakao.com/sdk/js/kakao.js"; + const script = document.createElement('script'); + script.src = 'https://developers.kakao.com/sdk/js/kakao.js'; script.async = true; script.onload = () => { From 3cd88562bcf75949ffa65dc9d0d8f64f563fc29a Mon Sep 17 00:00:00 2001 From: yyypearl Date: Sun, 4 May 2025 17:20:26 +0900 Subject: [PATCH 3/9] =?UTF-8?q?=E2=9C=A8=20feat(#165):=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B2=84=20=EB=A1=9C=EA=B7=B8=EC=9D=B8,=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/login/user.tsx | 3 ++- src/app/login/auth/page.tsx | 33 ++++++++++++++++++++++----- src/app/login/page.tsx | 4 ++-- src/components/signup/OauthButton.tsx | 13 +++++++++-- src/types/login.ts | 3 ++- 5 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/api/login/user.tsx b/src/api/login/user.tsx index 8ca3476d..b8788c4e 100644 --- a/src/api/login/user.tsx +++ b/src/api/login/user.tsx @@ -1,9 +1,10 @@ import { RegisterDataType } from '@/types/user'; import client, { authClient } from '../client'; import { getRefreshToken, setTokens } from '@/utils/storage'; +import { Provider } from '@/types/login'; // 로그인 -export const login = async (loginType: string, accessToken: string) => { +export const login = async (loginType: Provider, accessToken: string) => { return await client.post(`/api/v1/auth/login/${loginType}`, { accessToken: accessToken }); diff --git a/src/app/login/auth/page.tsx b/src/app/login/auth/page.tsx index d8c715b0..b8541a4a 100644 --- a/src/app/login/auth/page.tsx +++ b/src/app/login/auth/page.tsx @@ -3,6 +3,7 @@ import { login } from '@/api/login/user'; import Loader from '@/components/common/Loader'; import { signupState } from '@/recoil/signupStore'; +import { Provider } from '@/types/login'; import { clearLetterUrl, setOnboarding, setTokens } from '@/utils/storage'; import axios from 'axios'; import { useRouter } from 'next/navigation'; @@ -13,12 +14,10 @@ import styled from 'styled-components'; const Auth = () => { const [registerToken, setRegisterToken] = useRecoilState(signupState); const router = useRouter(); - const REST_API_KEY = process.env.NEXT_PUBLIC_REST_API_KEY; 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') { @@ -50,13 +49,12 @@ const Auth = () => { //type에 따라 다른 토큰 url 지정 switch (TYPE) { case '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, + client_id: process.env.NEXT_PUBLIC_KAKAO_REST_API_KEY, redirect_uri: absoluteUrl, code: AUTHORIZATION_CODE }), @@ -73,7 +71,6 @@ const Auth = () => { break; case 'google': - setProvider('GOOGLE'); try { const body = new URLSearchParams({ grant_type: 'authorization_code', @@ -100,6 +97,30 @@ const Auth = () => { } break; case 'naver': + try { + // 백엔드 서버로부터 요청해서 받아오는 방식으로 변경하기 + // const body = new URLSearchParams({ + // grant_type: 'authorization_code', + // client_id: process.env.NEXT_PUBLIC_NAVER_CLIENT_ID!, + // client_secret: process.env.NEXT_PUBLIC_NAVER_CLIENT_SECRET!, + // redirect_uri: absoluteUrl, + // code: AUTHORIZATION_CODE, + // }); + // const response = await axios.post( + // 'https://nid.naver.com/oauth2.0/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; default: console.error('Unknown OAuth type:', TYPE); @@ -112,7 +133,7 @@ const Auth = () => { useEffect(() => { try { if (oauthAccessToken) { - login(provider, oauthAccessToken) + login(type?.toUpperCase() as Provider, oauthAccessToken) .then((res) => { console.log('accessToken', res.data.accessToken); setTokens(res.data.accessToken, res.data.refreshToken); diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index aa9a34b6..760717e3 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -4,7 +4,7 @@ import Loader, { LoaderContainer } from '@/components/common/Loader'; import OauthButton from '@/components/signup/OauthButton'; import { OAUTH } from '@/constants/oauth'; import { theme } from '@/styles/theme'; -import { loginType } from '@/types/login'; +import { OAuthType } from '@/types/login'; import { Suspense } from 'react'; import styled from 'styled-components'; @@ -30,7 +30,7 @@ export default function Login() { {OAUTH.map((item) => ( { } case 'naver': { + const NAVER_CLIENT_ID = process.env.NEXT_PUBLIC_NAVER_CLIENT_ID; + const state = Math.random().toString(36).substring(2); + authUrl = [ + 'https://nid.naver.com/oauth2.0/authorize', + `?client_id=${NAVER_CLIENT_ID}`, + `&redirect_uri=${redirectUri}`, + `&response_type=code`, + `&state=${state}` + ].join(''); break; } } diff --git a/src/types/login.ts b/src/types/login.ts index 2be99043..13fa2fca 100644 --- a/src/types/login.ts +++ b/src/types/login.ts @@ -1 +1,2 @@ -export type loginType = 'naver' | 'google' | 'kakao'; \ No newline at end of file +export type OAuthType = 'naver' | 'google' | 'kakao'; +export type Provider = 'GOOGLE' | 'KAKAO' | 'NAVER'; \ No newline at end of file From 0c104c6a529245633f54892f27131184b7e6aaeb Mon Sep 17 00:00:00 2001 From: yyypearl Date: Sun, 4 May 2025 17:36:54 +0900 Subject: [PATCH 4/9] =?UTF-8?q?=E2=9C=A8=20feat(#165):=20=EC=B5=9C?= =?UTF-8?q?=EA=B7=BC=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=B0=A9=EC=8B=9D=20?= =?UTF-8?q?=EC=95=88=EB=82=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/login/auth/page.tsx | 9 +++++- src/components/signup/OauthButton.tsx | 46 ++++++++++++++++++++++++--- src/styles/animation.ts | 12 +++++++ src/utils/storage.ts | 15 +++++++++ 4 files changed, 77 insertions(+), 5 deletions(-) diff --git a/src/app/login/auth/page.tsx b/src/app/login/auth/page.tsx index b8541a4a..574bfabc 100644 --- a/src/app/login/auth/page.tsx +++ b/src/app/login/auth/page.tsx @@ -4,7 +4,12 @@ import { login } from '@/api/login/user'; import Loader from '@/components/common/Loader'; import { signupState } from '@/recoil/signupStore'; import { Provider } from '@/types/login'; -import { clearLetterUrl, setOnboarding, setTokens } from '@/utils/storage'; +import { + clearLetterUrl, + setOnboarding, + setRecentLogin, + setTokens +} from '@/utils/storage'; import axios from 'axios'; import { useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; @@ -139,6 +144,8 @@ const Auth = () => { setTokens(res.data.accessToken, res.data.refreshToken); /* 온보딩 여부 저장 */ setOnboarding(res.data.isProcessedOnboarding); + /* 최근 로그인 정보 저장 */ + setRecentLogin(type); if (storeUrl) { router.push(`/verify/letter?url=${storeUrl}`); clearLetterUrl(); diff --git a/src/components/signup/OauthButton.tsx b/src/components/signup/OauthButton.tsx index 8e72a01c..6854f8bd 100644 --- a/src/components/signup/OauthButton.tsx +++ b/src/components/signup/OauthButton.tsx @@ -2,8 +2,10 @@ import React, { Suspense, useEffect, useState } from 'react'; import Image from 'next/image'; import styled from 'styled-components'; import { useSearchParams } from 'next/navigation'; -import { setLetterUrl } from '@/utils/storage'; +import { getRecentLogin, setLetterUrl } from '@/utils/storage'; import { OAuthType } from '@/types/login'; +import { theme } from '@/styles/theme'; +import { float } from '@/styles/animation'; interface OauthButtonProps { loginType: OAuthType; @@ -82,14 +84,24 @@ const OauthButton = (props: OauthButtonProps) => { }; return ( - - - + + {loginType === getRecentLogin() && 최근에 로그인했어요} + + + + ); }; export default OauthButton; +const Wrapper = styled.div` + position: relative; + display: flex; + flex-direction: column; + align-items: center; +`; + const SocialButton = styled.button<{ $bgColor: string }>` width: 69px; height: 69px; @@ -109,3 +121,29 @@ const SocialIcon = styled(Image)` padding: 0 16px; object-fit: contain; `; + +const Bubble = styled.div` + height: 28px; + position: absolute; + top: -45px; + background-color: #3399ff; + color: ${theme.colors.white}; + padding: 5px 10px; + border-radius: 8px; + ${theme.fonts.caption03}; + white-space: nowrap; + animation: ${float} 2s ease-in-out infinite; + + &::after { + content: ''; + position: absolute; + bottom: -11.5px; + left: 50%; + transform: translateX(-50%); + width: 0; + height: 0; + border-width: 6px; + border-style: solid; + border-color: #3399ff transparent transparent transparent; + } +`; diff --git a/src/styles/animation.ts b/src/styles/animation.ts index c636eaa5..e6800433 100644 --- a/src/styles/animation.ts +++ b/src/styles/animation.ts @@ -11,3 +11,15 @@ export const flipAnimation = keyframes` transform: rotateY(0deg); } `; + +export const float = keyframes` + 0% { + transform: translate(-50%, 0); + } + 50% { + transform: translate(-50%, -6px); + } + 100% { + transform: translate(-50%, 0); + } +`; \ No newline at end of file diff --git a/src/utils/storage.ts b/src/utils/storage.ts index d96834aa..7324835a 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -47,6 +47,21 @@ export const clearOnboarding = () => { removeCookie("lettering-onboarding") } +/* 최근 로그인 방식 */ +export const setRecentLogin = (loginType: string) => { + if (typeof window !== "undefined") { + localStorage.setItem('recent_login', loginType); + } + return null; +}; + +export const getRecentLogin = () => { + if (typeof window !== 'undefined') { + return localStorage.getItem('recent_login'); + } + return null; +}; + /* letter URL */ export const setLetterUrl = (url: string) => { if (typeof window !== "undefined") { From 96f6929471d829e39e4506097fea4f4b18eb20c4 Mon Sep 17 00:00:00 2001 From: yyypearl Date: Sun, 4 May 2025 21:43:45 +0900 Subject: [PATCH 5/9] =?UTF-8?q?=F0=9F=92=84=20design(#165):=20=EC=B5=9C?= =?UTF-8?q?=EA=B7=BC=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=A7=90=ED=92=8D?= =?UTF-8?q?=EC=84=A0=20=EC=9C=84=EC=B9=98=20=EC=88=98=EC=A0=95,=20blue=20t?= =?UTF-8?q?heme=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/signup/OauthButton.tsx | 12 ++++++--- src/styles/theme.ts | 37 ++++++++++++++------------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/components/signup/OauthButton.tsx b/src/components/signup/OauthButton.tsx index 6854f8bd..d47af93b 100644 --- a/src/components/signup/OauthButton.tsx +++ b/src/components/signup/OauthButton.tsx @@ -96,6 +96,8 @@ const OauthButton = (props: OauthButtonProps) => { export default OauthButton; const Wrapper = styled.div` + width: 69px; + height: 69px; position: relative; display: flex; flex-direction: column; @@ -126,7 +128,9 @@ const Bubble = styled.div` height: 28px; position: absolute; top: -45px; - background-color: #3399ff; + left: 50%; + transform: translateX(-50%); + background-color: ${theme.colors.blue}; color: ${theme.colors.white}; padding: 5px 10px; border-radius: 8px; @@ -137,13 +141,13 @@ const Bubble = styled.div` &::after { content: ''; position: absolute; - bottom: -11.5px; + bottom: -9px; left: 50%; transform: translateX(-50%); width: 0; height: 0; - border-width: 6px; + border-width: 10px 6px 0 6px; border-style: solid; - border-color: #3399ff transparent transparent transparent; + border-color: ${theme.colors.blue} transparent transparent transparent; } `; diff --git a/src/styles/theme.ts b/src/styles/theme.ts index ef813049..b2836ca1 100644 --- a/src/styles/theme.ts +++ b/src/styles/theme.ts @@ -1,26 +1,27 @@ import { DefaultTheme } from "styled-components"; const colors = { - main01: "#424DA0", - sub01: "#2C3361", - sub02: "#565C81", - sub03: "#7783C5", + main01: '#424DA0', + sub01: '#2C3361', + sub02: '#565C81', + sub03: '#7783C5', - gray900: "#181B29", - gray800: "#202232", - gray700: "#2E3040", - gray600: "#3E4151", - gray500: "#5B5F70", - gray400: "#818491", - gray300: "#9FA1AC", - gray200: "#BEC0C8", - gray100: "#D5D7DE", - gray50: "#F7F8F9", + gray900: '#181B29', + gray800: '#202232', + gray700: '#2E3040', + gray600: '#3E4151', + gray500: '#5B5F70', + gray400: '#818491', + gray300: '#9FA1AC', + gray200: '#BEC0C8', + gray100: '#D5D7DE', + gray50: '#F7F8F9', - bg: "#060812", - white: "#FFFFFF", - black: "#000000", - red: "#E1303E", + bg: '#060812', + white: '#FFFFFF', + black: '#000000', + red: '#E1303E', + blue: '#3399FF' } as const; interface Font { From d143fa403add23bc75072b0e61af38f795efba61 Mon Sep 17 00:00:00 2001 From: yyypearl Date: Sun, 4 May 2025 21:48:31 +0900 Subject: [PATCH 6/9] =?UTF-8?q?=F0=9F=90=9B=20fix(#165):=20=EC=B5=9C?= =?UTF-8?q?=EA=B7=BC=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=ED=81=B4=EB=9D=BC=EC=9D=B4=EC=96=B8=ED=8A=B8=20=EB=A0=8C?= =?UTF-8?q?=EB=8D=94=EB=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/signup/OauthButton.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/signup/OauthButton.tsx b/src/components/signup/OauthButton.tsx index d47af93b..d327a28d 100644 --- a/src/components/signup/OauthButton.tsx +++ b/src/components/signup/OauthButton.tsx @@ -19,9 +19,11 @@ const OauthButton = (props: OauthButtonProps) => { const searchParams = useSearchParams(); const url = searchParams.get('url'); const [redirectUri, setRedirectUri] = useState(''); + const [recentLogin, setRecentLogin] = useState(null); useEffect(() => { if (typeof window !== 'undefined') { + setRecentLogin(getRecentLogin()); setRedirectUri( window.location.protocol + '//' + @@ -85,7 +87,7 @@ const OauthButton = (props: OauthButtonProps) => { return ( - {loginType === getRecentLogin() && 최근에 로그인했어요} + {recentLogin === loginType && 최근에 로그인했어요} From 5ea16060b0436723b9f0be521c72d72eef878fdf Mon Sep 17 00:00:00 2001 From: yyypearl Date: Sun, 4 May 2025 23:09:57 +0900 Subject: [PATCH 7/9] =?UTF-8?q?=E2=9C=A8=20feat(#165):=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B2=84=20=EC=95=A1=EC=84=B8=EC=8A=A4=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=EC=84=9C=EB=B2=84=EB=A1=9C=EB=B6=80=ED=84=B0=20?= =?UTF-8?q?=EB=B0=9B=EC=95=84=EC=98=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/login/oauth.tsx | 15 +++++++++++++++ src/app/login/auth/page.tsx | 26 ++++++++------------------ 2 files changed, 23 insertions(+), 18 deletions(-) create mode 100644 src/api/login/oauth.tsx diff --git a/src/api/login/oauth.tsx b/src/api/login/oauth.tsx new file mode 100644 index 00000000..0f436b73 --- /dev/null +++ b/src/api/login/oauth.tsx @@ -0,0 +1,15 @@ +import { Provider } from '@/types/login'; +import client from '../client'; + +export const getOauthAccessToken = async ( + provider: Provider, + code: string, + state: string +) => { + const response = await client.post(`/api/v1/auth/token/${provider}`, { + code: code, + state: state + }); + const { accessToken } = response.data; + return accessToken; +}; diff --git a/src/app/login/auth/page.tsx b/src/app/login/auth/page.tsx index 574bfabc..2c44e80e 100644 --- a/src/app/login/auth/page.tsx +++ b/src/app/login/auth/page.tsx @@ -1,5 +1,6 @@ 'use client'; +import { getOauthAccessToken } from '@/api/login/oauth'; import { login } from '@/api/login/user'; import Loader from '@/components/common/Loader'; import { signupState } from '@/recoil/signupStore'; @@ -43,6 +44,7 @@ const Auth = () => { const AUTHORIZATION_CODE = new URL(window.location.href).searchParams.get( 'code' ); + const STATE = new URL(window.location.href).searchParams.get('state'); const TYPE = new URL(window.location.href).searchParams.get('type'); @@ -103,24 +105,12 @@ const Auth = () => { break; case 'naver': try { - // 백엔드 서버로부터 요청해서 받아오는 방식으로 변경하기 - // const body = new URLSearchParams({ - // grant_type: 'authorization_code', - // client_id: process.env.NEXT_PUBLIC_NAVER_CLIENT_ID!, - // client_secret: process.env.NEXT_PUBLIC_NAVER_CLIENT_SECRET!, - // redirect_uri: absoluteUrl, - // code: AUTHORIZATION_CODE, - // }); - // const response = await axios.post( - // 'https://nid.naver.com/oauth2.0/token', - // body.toString(), - // { - // headers: { - // 'Content-Type': 'application/x-www-form-urlencoded' - // } - // } - // ); - // setOauthAccessToken(response.data.access_token); + const response = await getOauthAccessToken( + 'NAVER' as Provider, + AUTHORIZATION_CODE, + STATE + ); + setOauthAccessToken(response); } catch (error) { console.error('Unsupported OAuth type:', type); clearLetterUrl(); From 144e75f3964091a3346857f8e5ead804168ebc5e Mon Sep 17 00:00:00 2001 From: yyypearl Date: Sun, 4 May 2025 23:10:28 +0900 Subject: [PATCH 8/9] =?UTF-8?q?=E2=99=BB=20refactor(#165):=20=EB=A7=88?= =?UTF-8?q?=EC=9D=B4=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9D=B4=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20=EC=95=84=EC=9D=B4=EC=BD=98=20constant=EB=A1=9C=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/mypage/page.tsx | 36 ++++++++---------------------------- src/constants/oauth.ts | 9 ++++++--- 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/src/app/mypage/page.tsx b/src/app/mypage/page.tsx index 0119427f..6f894ccb 100644 --- a/src/app/mypage/page.tsx +++ b/src/app/mypage/page.tsx @@ -2,9 +2,9 @@ import { getLetterCount } from '@/api/letter/letter'; import { getUserInfo, logout } from '@/api/mypage/user'; -import Button from '@/components/common/Button'; import Loader, { LoaderContainer } from '@/components/common/Loader'; import NavigatorBar from '@/components/common/NavigatorBar'; +import { OAUTH } from '@/constants/oauth'; import { theme } from '@/styles/theme'; import { clearOnboarding, clearTokens, getRefreshToken } from '@/utils/storage'; import { useRouter } from 'next/navigation'; @@ -65,7 +65,6 @@ const MyPage = () => { setName(response.data.name); setEmail(response.data.email); setPlatform(response.data.socialPlatform); - // console.log('회원정보 조회 성공:', response.data); } catch (error) { console.error('회원정보 조회 실패:', error); } @@ -81,18 +80,9 @@ 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 ''; - } - }; + const profileSrc = OAUTH.find( + (oauth) => oauth.key === platform.toLowerCase() + )?.profile; return ( @@ -112,11 +102,7 @@ const MyPage = () => { {name}님의 스페이스 - +
{email}
@@ -258,15 +244,9 @@ 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 StyledIcon = styled.img` + width: 20px; + height: 20px; `; const ProfileInfo = styled.div` diff --git a/src/constants/oauth.ts b/src/constants/oauth.ts index f8b42de7..dbbd033f 100644 --- a/src/constants/oauth.ts +++ b/src/constants/oauth.ts @@ -3,18 +3,21 @@ export const OAUTH = [ key: 'naver', bgColor: '#03CF5D', icon: '/assets/icons/ic_naver.svg', - size: 26 + size: 26, + profile: "/assets/icons/ic_naver.svg" }, { key: 'google', bgColor: '#FFFFFF', icon: '/assets/icons/ic_google.svg', - size: 32 + size: 32, + profile: "/assets/icons/ic_googler.svg" }, { key: 'kakao', bgColor: '#FEE500', icon: '/assets/icons/ic_kakaotalk.svg', - size: 38 + size: 38, + profile: '/assets/icons/ic_kakao_profile.svg', } ]; \ No newline at end of file From 73b113979887aded95a541c9bd6dca3a99f2c9c5 Mon Sep 17 00:00:00 2001 From: yyypearl Date: Sun, 4 May 2025 23:12:05 +0900 Subject: [PATCH 9/9] =?UTF-8?q?=F0=9F=92=84=20design(#165):=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=EA=B0=80=EC=9E=85=20=EC=99=84=EB=A3=8C=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20min-height=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/signup/complete/page.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app/signup/complete/page.tsx b/src/app/signup/complete/page.tsx index db62f2ee..9f911e79 100644 --- a/src/app/signup/complete/page.tsx +++ b/src/app/signup/complete/page.tsx @@ -68,9 +68,7 @@ const Container = styled.div` width: 100%; max-width: 393px; height: 100%; - max-height: 853px; flex-direction: column; - //overflow: scroll; justify-content: space-between; background-image: url('/assets/signup/signup_bg.png'); background-size: cover;