Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/service/src/apis/controller/auth/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import postLogin from './postLogin';
import postKakaoLogin from './postKakaoLogin';

export { postLogin };
export { postLogin, postKakaoLogin };
54 changes: 54 additions & 0 deletions apps/service/src/apis/controller/auth/postKakaoLogin.ts
Original file line number Diff line number Diff line change
@@ -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;
4 changes: 2 additions & 2 deletions apps/service/src/app/(home)/page.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -36,7 +36,7 @@ const Page = () => {

return (
<>
<HomeHeader name='홍길동' grade={2} />
<HomeHeader />
<main className='flex flex-col px-[2rem] pt-[6rem]'>
<p className='font-medium-12 text-lightgray500 pt-[1.6rem]'>
아직은 고등학교 2학년 대상으로만 서비스를 하고 있어요!
Expand Down
20 changes: 20 additions & 0 deletions apps/service/src/app/api/auth/callback/kakao/page.tsx
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 1 addition & 1 deletion apps/service/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ export default function RootLayout({
<html lang='ko'>
<body className={`antialiased`}>
<Providers>
<div>{children}</div>
<Suspense fallback={<></>}>
<div>{children}</div>
<div>{modal}</div>
</Suspense>
<div id='modal'></div>
Expand Down
70 changes: 13 additions & 57 deletions apps/service/src/app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -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<LoginType>();
const { trackEvent } = useTrackEvent();

const onSubmitLogin: SubmitHandler<LoginType> = 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 (
Expand All @@ -41,41 +27,11 @@ const Page = () => {
<LogoLogin width={250} height={57} className='mt-[2.4rem]' />
<h1 className='text-main font-bold-24 mt-[1.6rem]'>포인터</h1>
</div>
{/* <div className='mt-auto flex flex-col items-center gap-[1.6rem]'>
<div className='mt-auto flex flex-col items-center gap-[1.6rem]'>
<p className='font-medium-12 text-lightgray500'>포인터는 태블릿의 스플릿뷰를 권장해요</p>
<KakaoButton />
<AppleButton />
</div> */}
<form
onSubmit={handleSubmit(onSubmitLogin)}
className='mt-[4.8rem] flex flex-col items-start justify-center gap-[2.4rem]'>
<Input
type='email'
placeholder='이메일을 입력해주세요'
autoComplete='username'
{...register('email', {
required: true,
})}
/>
<Input
type='password'
placeholder='비밀번호를 입력해주세요'
autoComplete='current-password'
{...register('password', {
required: true,
pattern: {
value: /^[A-Za-z0-9]*$/,
message: '비밀번호는 영문자와 숫자만 입력 가능합니다.',
},
})}
/>
{errors.password && (
<p className='font-medium-16 text-red mt-[1.2rem]' role='alert'>
{errors.password.message}
</p>
)}
<Button variant='blue'>로그인</Button>
</form>
<KakaoButton onClick={handleLoginClick} />
{/* <AppleButton /> */}
</div>
</div>
);
};
Expand Down
20 changes: 14 additions & 6 deletions apps/service/src/components/home/HomeHeader.tsx
Original file line number Diff line number Diff line change
@@ -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<string | null>(null);

useEffect(() => {
setName(localStorage.getItem('name'));
}, []);

const HomeHeader = ({ name }: HomeHeaderProps) => {
return (
<header className='bg-background fixed inset-0 z-40 flex h-[6rem] items-center justify-between px-[2rem]'>
<Link href='/'>
Expand All @@ -19,7 +23,11 @@ const HomeHeader = ({ name }: HomeHeaderProps) => {
{grade}학년
</div> */}
<div className='font-medium-14 text-black'>
<span className='text-main mr-[0.4rem]'>{name}</span>님
{name && (
<>
<span className='text-main mr-[0.4rem]'>{name}</span>님
</>
)}
</div>
<Link href='/my-page'>
<IcSetting width={24} height={24} />
Expand Down
10 changes: 8 additions & 2 deletions apps/service/src/components/login/KakaoButton.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { IcKakao } from '@svg';

const KakaoButton = () => {
interface KakaoButtonProps {
onClick: () => void;
}

const KakaoButton = ({ onClick }: KakaoButtonProps) => {
return (
<button className='flex h-[5.6rem] w-full items-center justify-center gap-[0.8rem] rounded-[1.6rem] bg-[#FEE500]'>
<button
className='flex h-[5.6rem] w-full items-center justify-center gap-[0.8rem] rounded-[1.6rem] bg-[#FEE500]'
onClick={onClick}>
<IcKakao width={24} height={24} />
<p className='font-medium-16 text-black'>카카오로 계속하기</p>
</button>
Expand Down
6 changes: 6 additions & 0 deletions apps/service/src/contexts/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export interface AuthContextType {
const tokenStore = {
accessToken: null as string | null,
setAccessToken: (_: string | null) => {},
name: '',
setName: (_: string) => {},
};

export const AuthContext = createContext<AuthContextType | undefined>(undefined);
Expand All @@ -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,
Expand All @@ -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);
1 change: 1 addition & 0 deletions apps/service/src/types/api/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,7 @@ export interface components {
LoginResponse: {
/** Format: int64 */
memberId?: number;
name?: string;
email?: string;
accessToken?: string;
refreshToken?: string;
Expand Down