diff --git a/apps/service/src/apis/controller/auth/index.ts b/apps/service/src/apis/controller/auth/index.ts index 38107e44..bdae8e16 100644 --- a/apps/service/src/apis/controller/auth/index.ts +++ b/apps/service/src/apis/controller/auth/index.ts @@ -1,3 +1,4 @@ import postLogin from './postLogin'; +import postKakaoLogin from './postKakaoLogin'; -export { postLogin }; +export { postLogin, postKakaoLogin }; diff --git a/apps/service/src/apis/controller/auth/postKakaoLogin.ts b/apps/service/src/apis/controller/auth/postKakaoLogin.ts new file mode 100644 index 00000000..e048dc49 --- /dev/null +++ b/apps/service/src/apis/controller/auth/postKakaoLogin.ts @@ -0,0 +1,54 @@ +'use client'; + +import { client } from '@/apis/client'; +import { setAccessToken } from '@/contexts/AuthContext'; + +const postKakaoAccessToken = async (code: string) => { + const response = await fetch(`https://kauth.kakao.com/oauth/token`, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + }, + body: `grant_type=authorization_code&client_id=${process.env.NEXT_PUBLIC_REST_API_KEY}&redirect_uri=${process.env.NEXT_PUBLIC_REDIRECT_URI}&code=${code}`, + }); + const jsonData = await response.json(); + return jsonData.access_token; +}; + +const postKakaoLogin = async (code: string) => { + const accessToken = await postKakaoAccessToken(code); + const response = await client.POST('/api/v1/auth/oauth/social-login', { + params: { + header: { + social_access_token: accessToken, + }, + query: { + provider: 'KAKAO', + }, + }, + }); + + try { + if ( + response && + response.data && + response.data.data && + response.data.data.name && + response.data.data.accessToken + ) { + const { accessToken, name } = response.data.data; + setAccessToken(accessToken); + localStorage.setItem('name', name); + + window.location.href = '/'; + } else { + console.error('accessToken을 찾을 수 없습니다:', response); + } + } catch (error) { + console.error('소셜 로그인 요청 오류:', error); + } + + return response; +}; + +export default postKakaoLogin; diff --git a/apps/service/src/app/(home)/page.tsx b/apps/service/src/app/(home)/page.tsx index 829743e9..ee8f383d 100644 --- a/apps/service/src/app/(home)/page.tsx +++ b/apps/service/src/app/(home)/page.tsx @@ -1,11 +1,11 @@ 'use client'; +import { useRouter } from 'next/navigation'; import { Button } from '@components'; import { IcCalendar } from '@svg'; import { getHomeFeed } from '@apis'; import dayjs from 'dayjs'; import { DailyProgress } from '@types'; import { useTrackEvent } from '@hooks'; -import { useRouter } from 'next/navigation'; import { GuideButton, @@ -36,7 +36,7 @@ const Page = () => { return ( <> - +

아직은 고등학교 2학년 대상으로만 서비스를 하고 있어요! diff --git a/apps/service/src/app/api/auth/callback/kakao/page.tsx b/apps/service/src/app/api/auth/callback/kakao/page.tsx new file mode 100644 index 00000000..b38e51e3 --- /dev/null +++ b/apps/service/src/app/api/auth/callback/kakao/page.tsx @@ -0,0 +1,20 @@ +'use client'; + +import { postKakaoLogin } from '@apis'; +import { useSearchParams } from 'next/navigation'; +import { useEffect } from 'react'; + +const Page = () => { + const searchParams = useSearchParams(); + const code = searchParams.get('code'); + + useEffect(() => { + if (code) { + postKakaoLogin(code); + } + }, []); + + return <>; +}; + +export default Page; diff --git a/apps/service/src/app/layout.tsx b/apps/service/src/app/layout.tsx index c172743d..2e43092d 100644 --- a/apps/service/src/app/layout.tsx +++ b/apps/service/src/app/layout.tsx @@ -52,8 +52,8 @@ export default function RootLayout({ -

{children}
}> +
{children}
{modal}
diff --git a/apps/service/src/app/login/page.tsx b/apps/service/src/app/login/page.tsx index 0ac3336f..23f070e1 100644 --- a/apps/service/src/app/login/page.tsx +++ b/apps/service/src/app/login/page.tsx @@ -1,33 +1,19 @@ 'use client'; -import { postLogin } from '@apis'; -import { Button, Input } from '@components'; -import { SubmitHandler, useForm } from 'react-hook-form'; -import { useRouter } from 'next/navigation'; +import { useTrackEvent } from '@hooks'; import { LogoLogin } from '@/assets/svg/logo'; -import { setAccessToken } from '@/contexts/AuthContext'; - -interface LoginType { - email: string; - password: string; -} +import { KakaoButton } from '@/components/login'; const Page = () => { - const router = useRouter(); - const { - register, - handleSubmit, - formState: { errors }, - } = useForm(); + const { trackEvent } = useTrackEvent(); - const onSubmitLogin: SubmitHandler = async (formData) => { - const { data } = await postLogin(formData.email, formData.password); + const kakaoLoginUrl = `https://kauth.kakao.com/oauth/authorize?client_id=${ + process.env.NEXT_PUBLIC_REST_API_KEY + }&redirect_uri=${process.env.NEXT_PUBLIC_REDIRECT_URI}&response_type=code`; - const { accessToken } = data?.data || {}; - if (accessToken) { - setAccessToken(accessToken); - router.push('/'); - } + const handleLoginClick = () => { + trackEvent('kakao_login_click'); + window.location.replace(kakaoLoginUrl); }; return ( @@ -41,41 +27,11 @@ const Page = () => {

포인터

- {/*
+

포인터는 태블릿의 스플릿뷰를 권장해요

- - -
*/} -
- - - {errors.password && ( -

- {errors.password.message} -

- )} - -
+ + {/* */} +
); }; diff --git a/apps/service/src/components/home/HomeHeader.tsx b/apps/service/src/components/home/HomeHeader.tsx index 024a590a..1807ab13 100644 --- a/apps/service/src/components/home/HomeHeader.tsx +++ b/apps/service/src/components/home/HomeHeader.tsx @@ -1,14 +1,18 @@ +'use client'; + import Link from 'next/link'; import { IcSetting } from '@svg'; +import { useEffect, useState } from 'react'; import { LogoHeader } from '@/assets/svg/logo'; -interface HomeHeaderProps { - grade: number; - name: string; -} +const HomeHeader = () => { + const [name, setName] = useState(null); + + useEffect(() => { + setName(localStorage.getItem('name')); + }, []); -const HomeHeader = ({ name }: HomeHeaderProps) => { return (
@@ -19,7 +23,11 @@ const HomeHeader = ({ name }: HomeHeaderProps) => { {grade}학년 */}
- {name}님 + {name && ( + <> + {name}님 + + )}
diff --git a/apps/service/src/components/login/KakaoButton.tsx b/apps/service/src/components/login/KakaoButton.tsx index d7893912..d958922c 100644 --- a/apps/service/src/components/login/KakaoButton.tsx +++ b/apps/service/src/components/login/KakaoButton.tsx @@ -1,8 +1,14 @@ import { IcKakao } from '@svg'; -const KakaoButton = () => { +interface KakaoButtonProps { + onClick: () => void; +} + +const KakaoButton = ({ onClick }: KakaoButtonProps) => { return ( - diff --git a/apps/service/src/contexts/AuthContext.tsx b/apps/service/src/contexts/AuthContext.tsx index f36450b6..a7034806 100644 --- a/apps/service/src/contexts/AuthContext.tsx +++ b/apps/service/src/contexts/AuthContext.tsx @@ -11,6 +11,8 @@ export interface AuthContextType { const tokenStore = { accessToken: null as string | null, setAccessToken: (_: string | null) => {}, + name: '', + setName: (_: string) => {}, }; export const AuthContext = createContext(undefined); @@ -21,6 +23,8 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { tokenStore.accessToken = accessToken; tokenStore.setAccessToken = setAccessTokenState; + tokenStore.name = name; + tokenStore.setName = setNameState; const contextValue = { accessToken, @@ -34,3 +38,5 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { export const getAccessToken = () => tokenStore.accessToken; export const setAccessToken = (token: string | null) => tokenStore.setAccessToken(token); +export const getName = () => tokenStore.name; +export const setName = (name: string) => tokenStore.setName(name); diff --git a/apps/service/src/types/api/schema.d.ts b/apps/service/src/types/api/schema.d.ts index f73b20bd..0f76b3e6 100644 --- a/apps/service/src/types/api/schema.d.ts +++ b/apps/service/src/types/api/schema.d.ts @@ -876,6 +876,7 @@ export interface components { LoginResponse: { /** Format: int64 */ memberId?: number; + name?: string; email?: string; accessToken?: string; refreshToken?: string;