From 47e2e63541ef50f7d0e692536a32a7314d306d1f Mon Sep 17 00:00:00 2001 From: Seunghyo Date: Tue, 29 Apr 2025 11:31:52 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=92=84design(#163):=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=ED=99=94=EB=A9=B4=20=EC=86=8C=EC=85=9C=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=B2=84=ED=8A=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/assets/icons/ic_google.svg | 6 +++ public/assets/icons/ic_kakaotalk.svg | 4 ++ public/assets/icons/ic_naver.svg | 3 ++ src/app/login/google/page.tsx | 50 ++++++++++++++++++++++ src/app/login/page.tsx | 60 +++++++++++++++++++++------ src/components/signup/SocialKakao.tsx | 33 ++++++++------- 6 files changed, 128 insertions(+), 28 deletions(-) create mode 100644 public/assets/icons/ic_google.svg create mode 100644 public/assets/icons/ic_kakaotalk.svg create mode 100644 public/assets/icons/ic_naver.svg create mode 100644 src/app/login/google/page.tsx diff --git a/public/assets/icons/ic_google.svg b/public/assets/icons/ic_google.svg new file mode 100644 index 00000000..f742d521 --- /dev/null +++ b/public/assets/icons/ic_google.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/assets/icons/ic_kakaotalk.svg b/public/assets/icons/ic_kakaotalk.svg new file mode 100644 index 00000000..0e6423de --- /dev/null +++ b/public/assets/icons/ic_kakaotalk.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/assets/icons/ic_naver.svg b/public/assets/icons/ic_naver.svg new file mode 100644 index 00000000..c67628c6 --- /dev/null +++ b/public/assets/icons/ic_naver.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/app/login/google/page.tsx b/src/app/login/google/page.tsx new file mode 100644 index 00000000..a8332171 --- /dev/null +++ b/src/app/login/google/page.tsx @@ -0,0 +1,50 @@ +'use client'; + +import Loader from '@/components/common/Loader'; +import styled from 'styled-components'; + +const Auth = () => { + return ( + + + + + 로그인 중입니다 +
+ 잠시만 기다려주세요... +
+
+
+ ); +}; + +export default Auth; + +const Container = styled.div` + display: flex; + box-sizing: border-box; + flex-direction: column; + height: 100%; + padding: 25px; + background: ${(props) => props.theme.colors.bg}; +`; + +const LoaderContainer = styled.div` + width: 100%; + height: 100%; + min-height: 600px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +`; + +const Guidetext = styled.div` + width: 100%; + display: flex; + text-align: center; + justify-content: center; + ${(props) => props.theme.fonts.regular16}; + color: ${(props) => props.theme.colors.gray300}; + padding-top: 10px; +`; diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index cb27b349..935025c8 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -1,8 +1,13 @@ -"use client"; +'use client'; -import SocialKakao from "@/components/signup/SocialKakao"; -import { theme } from "@/styles/theme"; -import styled from "styled-components"; +import SocialKakao from '@/components/signup/SocialKakao'; +import { theme } from '@/styles/theme'; +import styled from 'styled-components'; +import Image from 'next/image'; + +interface OauthButtonProps { + bgColor: string; +} export default function Login() { return ( @@ -11,9 +16,27 @@ export default function Login() { 편지로 수놓는 나의 스페이스 - - - + + + Naver + + + Google + + + + + ); @@ -25,7 +48,7 @@ const Container = styled.div` height: 100vh; flex-direction: column; justify-content: space-between; - background-image: url("/assets/login/login_bg.png"); + background-image: url('/assets/login/login_bg.png'); background-size: cover; background-position: center; background-repeat: no-repeat; @@ -104,13 +127,26 @@ const ImageWrapper = styled.div` overflow: hidden; `; -const SocialKakaoWrapper = styled.div` +const OauthWrapper = styled.div` width: 100%; max-width: 393px; - padding: 0 4px; + gap: 24px; display: flex; position: absolute; bottom: 60px; - left: 50%; - transform: translate(-50%); + 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; `; diff --git a/src/components/signup/SocialKakao.tsx b/src/components/signup/SocialKakao.tsx index 073a790b..e7c033e2 100644 --- a/src/components/signup/SocialKakao.tsx +++ b/src/components/signup/SocialKakao.tsx @@ -1,26 +1,26 @@ -import React, { Suspense, useEffect, useState } from "react"; -import Image from "next/image"; -import styled from "styled-components"; -import { useRouter, useSearchParams } from "next/navigation"; -import { useRecoilState } from "recoil"; -import { accessState } from "@/recoil/accessStore"; -import { getAccessToken, setLetterUrl } from "@/utils/storage"; -import Loader, { LoaderContainer } from "../common/Loader"; +import React, { Suspense, useEffect, useState } from 'react'; +import Image from 'next/image'; +import styled from 'styled-components'; +import { useRouter, useSearchParams } from 'next/navigation'; +import { useRecoilState } from 'recoil'; +import { accessState } from '@/recoil/accessStore'; +import { getAccessToken, setLetterUrl } from '@/utils/storage'; +import Loader, { LoaderContainer } from '../common/Loader'; const SocialKakao = () => { const router = useRouter(); const searchParams = useSearchParams(); - const url = searchParams.get("url"); + const url = searchParams.get('url'); const REST_API_KEY = process.env.NEXT_PUBLIC_REST_API_KEY; const accessToken = getAccessToken(); - const [absoluteUrl, setabsoluteUrl] = useState(""); + const [absoluteUrl, setabsoluteUrl] = useState(''); const KAKAO_URL = `https://kauth.kakao.com/oauth/authorize?client_id=${REST_API_KEY}&redirect_uri=${absoluteUrl}&response_type=code&url=${url}`; const [localAccessToken, setAccessToken] = useRecoilState(accessState); useEffect(() => { - if (typeof window !== "undefined") { + if (typeof window !== 'undefined') { setabsoluteUrl( - window.location.protocol + "//" + window.location.host + "/login/kakao" + window.location.protocol + '//' + window.location.host + '/login/kakao' ); } }, []); @@ -35,6 +35,7 @@ const SocialKakao = () => { // } // setAccessToken(accessToken); // } else { + //받은 편지를 통해 들어올 경우 url를 저장한다. if (url) { setLetterUrl(url); } @@ -44,9 +45,9 @@ const SocialKakao = () => { return ( @@ -80,6 +81,6 @@ const SocialLoginBox = styled.div` const StyledImage = styled(Image)` width: 100%; height: 100%; - padding: 0 20px; + padding: 0 16px; object-fit: contain; `; From fbf49ca516a0caf598238cf8feebd66ec6cbf5ef Mon Sep 17 00:00:00 2001 From: Seunghyo Date: Tue, 29 Apr 2025 11:47:48 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=92=84design(#163):=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=97=86=EC=9D=B4=20=ED=8E=B8=EC=A7=80=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20=EB=B2=84=ED=8A=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/login/page.tsx | 20 ++++--- src/components/signup/SocialGoogle.tsx | 83 ++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 src/components/signup/SocialGoogle.tsx diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 935025c8..b19a2296 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -1,6 +1,7 @@ 'use client'; import SocialKakao from '@/components/signup/SocialKakao'; +import SocialGoogle from '@/components/signup/SocialGoogle'; import { theme } from '@/styles/theme'; import styled from 'styled-components'; import Image from 'next/image'; @@ -26,17 +27,13 @@ export default function Login() { /> - Google + + 로그인 없이 편지 작성해보기 ); @@ -133,7 +130,7 @@ const OauthWrapper = styled.div` gap: 24px; display: flex; position: absolute; - bottom: 60px; + bottom: 123px; justify-content: center; `; @@ -150,3 +147,12 @@ const OauthButton = styled.button` cursor: pointer; transition: background-color 0.3s; `; + +const LetterBtnText = styled.div` + ${(props) => props.theme.fonts.caption02}; + color: ${theme.colors.gray400}; + position: absolute; + bottom: 69px; + text-decoration-line: underline; + cursor: pointer; +`; diff --git a/src/components/signup/SocialGoogle.tsx b/src/components/signup/SocialGoogle.tsx new file mode 100644 index 00000000..9dc36b32 --- /dev/null +++ b/src/components/signup/SocialGoogle.tsx @@ -0,0 +1,83 @@ +import React, { Suspense, useEffect, useState } from 'react'; +import Image from 'next/image'; +import styled from 'styled-components'; +import { useRouter, useSearchParams } from 'next/navigation'; +import { useRecoilState } from 'recoil'; +import { accessState } from '@/recoil/accessStore'; +import { getAccessToken, setLetterUrl } from '@/utils/storage'; +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' + ); + } + }, []); + + 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; + }; + + 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; +`; From 11f8908d69d8f5502c78c5528b8996ceb5a266c0 Mon Sep 17 00:00:00 2001 From: Seunghyo Date: Tue, 29 Apr 2025 20:27:56 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=E2=99=BB=EF=B8=8Frefactor(#163):kakao?= =?UTF-8?q?=EB=A5=BC=20auth=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/login/{kakao => auth}/page.tsx | 88 +++++++++++++++----------- src/app/login/google/page.tsx | 50 --------------- src/components/signup/SocialGoogle.tsx | 6 +- src/components/signup/SocialKakao.tsx | 31 +++++---- 4 files changed, 73 insertions(+), 102 deletions(-) rename src/app/login/{kakao => auth}/page.tsx (54%) delete mode 100644 src/app/login/google/page.tsx diff --git a/src/app/login/kakao/page.tsx b/src/app/login/auth/page.tsx similarity index 54% rename from src/app/login/kakao/page.tsx rename to src/app/login/auth/page.tsx index 5d97135c..21499ca4 100644 --- a/src/app/login/kakao/page.tsx +++ b/src/app/login/auth/page.tsx @@ -1,31 +1,30 @@ -"use client"; +'use client'; -import { login } from "@/api/login/user"; -import Loader from "@/components/common/Loader"; -import { signupState } from "@/recoil/signupStore"; -import { - clearLetterUrl, - getLetterUrl, - setOnboarding, - setTokens, -} from "@/utils/storage"; -import axios from "axios"; -import { useRouter } from "next/navigation"; -import { useEffect, useState } from "react"; -import { useRecoilState } from "recoil"; -import styled from "styled-components"; +import { login } from '@/api/login/user'; +import Loader from '@/components/common/Loader'; +import { signupState } from '@/recoil/signupStore'; +import { clearLetterUrl, setOnboarding, setTokens } from '@/utils/storage'; +import axios from 'axios'; +import { useRouter } from 'next/navigation'; +import { useEffect, useState } from 'react'; +import { useRecoilState } from 'recoil'; +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 [absoluteUrl, setAbsoluteUrl] = useState(''); + const [storeUrl, setstoreUrl] = useState(''); + const [type, setType] = useState(''); useEffect(() => { - if (typeof window !== "undefined") { - const url = `${window.location.protocol}//${window.location.host}/login/kakao`; - const letterId = localStorage.getItem("letter_url"); + if (typeof window !== 'undefined') { + const params = new URL(window.location.href).searchParams; + const typeParam = params.get('type'); + setType(typeParam); + const url = `${window.location.protocol}//${window.location.host}/login/auth?type=${typeParam}`; + const letterId = localStorage.getItem('letter_url'); setAbsoluteUrl(url); if (letterId) setstoreUrl(letterId); } @@ -37,28 +36,43 @@ const Auth = () => { } const getToken = async () => { const AUTHORIZATION_CODE = new URL(window.location.href).searchParams.get( - "code" + 'code' ); + const TYPE = new URL(window.location.href).searchParams.get('type'); - if (!AUTHORIZATION_CODE) { - console.error("Authorization Code is missing"); + let tokenUrl = ''; + let provider: 'KAKAO' | 'GOOGLE' | 'NAVER'; + + if (!AUTHORIZATION_CODE || !TYPE) { + console.error('Authorization Code or Type is missing'); return; } + 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'; + break; + case 'google': + break; + case 'naver': + break; + default: + console.error('Unknown OAuth type:', TYPE); + return; + } + try { - const response = await axios.post( - `https://kauth.kakao.com/oauth/token?grant_type=authorization_code&client_id=${REST_API_KEY}&redirect_uri=${absoluteUrl}&code=${AUTHORIZATION_CODE}`, - { - headers: { "Content-Type": "application/json" }, - } - ); + const response = await axios.post(tokenUrl, { + headers: { 'Content-Type': 'application/json' } + }); + + const oauthAccessToken = response.data.access_token; - const kakao_accessToken = response.data.access_token; - console.log(kakao_accessToken); - if (kakao_accessToken) { - login("KAKAO", kakao_accessToken) + if (oauthAccessToken) { + login(provider, oauthAccessToken) .then((res) => { - console.log("accessToken", res.data.accessToken); + console.log('accessToken', res.data.accessToken); setTokens(res.data.accessToken, res.data.refreshToken); /* 온보딩 여부 저장 */ setOnboarding(res.data.isProcessedOnboarding); @@ -66,18 +80,18 @@ const Auth = () => { router.push(`/verify/letter?url=${storeUrl}`); clearLetterUrl(); } else { - router.push("/planet"); + router.push('/planet'); } }) .catch((error) => { if (error.response && error.response.status === 401) { - console.log("registerToken", error.response.data.registerToken); + 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"); + router.push('/signup/step1'); } } }); diff --git a/src/app/login/google/page.tsx b/src/app/login/google/page.tsx deleted file mode 100644 index a8332171..00000000 --- a/src/app/login/google/page.tsx +++ /dev/null @@ -1,50 +0,0 @@ -'use client'; - -import Loader from '@/components/common/Loader'; -import styled from 'styled-components'; - -const Auth = () => { - return ( - - - - - 로그인 중입니다 -
- 잠시만 기다려주세요... -
-
-
- ); -}; - -export default Auth; - -const Container = styled.div` - display: flex; - box-sizing: border-box; - flex-direction: column; - height: 100%; - padding: 25px; - background: ${(props) => props.theme.colors.bg}; -`; - -const LoaderContainer = styled.div` - width: 100%; - height: 100%; - min-height: 600px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; -`; - -const Guidetext = styled.div` - width: 100%; - display: flex; - text-align: center; - justify-content: center; - ${(props) => props.theme.fonts.regular16}; - color: ${(props) => props.theme.colors.gray300}; - padding-top: 10px; -`; diff --git a/src/components/signup/SocialGoogle.tsx b/src/components/signup/SocialGoogle.tsx index 9dc36b32..ee8a6ae0 100644 --- a/src/components/signup/SocialGoogle.tsx +++ b/src/components/signup/SocialGoogle.tsx @@ -1,10 +1,8 @@ import React, { Suspense, useEffect, useState } from 'react'; import Image from 'next/image'; import styled from 'styled-components'; -import { useRouter, useSearchParams } from 'next/navigation'; -import { useRecoilState } from 'recoil'; -import { accessState } from '@/recoil/accessStore'; -import { getAccessToken, setLetterUrl } from '@/utils/storage'; +import { useSearchParams } from 'next/navigation'; +import { setLetterUrl } from '@/utils/storage'; import Loader, { LoaderContainer } from '../common/Loader'; const SocialGoogle = () => { diff --git a/src/components/signup/SocialKakao.tsx b/src/components/signup/SocialKakao.tsx index e7c033e2..6128cb43 100644 --- a/src/components/signup/SocialKakao.tsx +++ b/src/components/signup/SocialKakao.tsx @@ -1,26 +1,24 @@ import React, { Suspense, useEffect, useState } from 'react'; import Image from 'next/image'; import styled from 'styled-components'; -import { useRouter, useSearchParams } from 'next/navigation'; -import { useRecoilState } from 'recoil'; -import { accessState } from '@/recoil/accessStore'; -import { getAccessToken, setLetterUrl } from '@/utils/storage'; +import { useSearchParams } from 'next/navigation'; +import { setLetterUrl } from '@/utils/storage'; import Loader, { LoaderContainer } from '../common/Loader'; const SocialKakao = () => { - const router = useRouter(); const searchParams = useSearchParams(); const url = searchParams.get('url'); const REST_API_KEY = process.env.NEXT_PUBLIC_REST_API_KEY; - const accessToken = getAccessToken(); - const [absoluteUrl, setabsoluteUrl] = useState(''); - const KAKAO_URL = `https://kauth.kakao.com/oauth/authorize?client_id=${REST_API_KEY}&redirect_uri=${absoluteUrl}&response_type=code&url=${url}`; - const [localAccessToken, setAccessToken] = useRecoilState(accessState); + 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') { - setabsoluteUrl( - window.location.protocol + '//' + window.location.host + '/login/kakao' + setRedirectUrl( + window.location.protocol + + '//' + + window.location.host + + '/login/auth?type=kakao' ); } }, []); @@ -39,6 +37,17 @@ const SocialKakao = () => { 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; };