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/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/page.tsx b/src/app/login/page.tsx index cb27b349..b19a2296 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -1,8 +1,14 @@ -"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 SocialGoogle from '@/components/signup/SocialGoogle'; +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 +17,23 @@ export default function Login() { 편지로 수놓는 나의 스페이스 - - - + + + Naver + + + + + + + + + 로그인 없이 편지 작성해보기 ); @@ -25,7 +45,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 +124,35 @@ 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: 123px; + 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}; position: absolute; - bottom: 60px; - left: 50%; - transform: translate(-50%); + 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..ee8a6ae0 --- /dev/null +++ b/src/components/signup/SocialGoogle.tsx @@ -0,0 +1,81 @@ +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 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; +`; diff --git a/src/components/signup/SocialKakao.tsx b/src/components/signup/SocialKakao.tsx index 073a790b..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 Loader, { LoaderContainer } from "../common/Loader"; +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 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 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" + if (typeof window !== 'undefined') { + setRedirectUrl( + window.location.protocol + + '//' + + window.location.host + + '/login/auth?type=kakao' ); } }, []); @@ -35,18 +33,30 @@ const SocialKakao = () => { // } // 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 ( @@ -80,6 +90,6 @@ const SocialLoginBox = styled.div` const StyledImage = styled(Image)` width: 100%; height: 100%; - padding: 0 20px; + padding: 0 16px; object-fit: contain; `;