Skip to content

Commit

Permalink
Merge pull request #29 from Help-M-Ssaem/feat/#10
Browse files Browse the repository at this point in the history
[Feat] 카카오 소셜 로그인, 유저 페이지 UI
  • Loading branch information
uiop5809 authored Aug 12, 2024
2 parents 36e9924 + 5de09df commit 8d45492
Show file tree
Hide file tree
Showing 42 changed files with 875 additions and 177 deletions.
2 changes: 1 addition & 1 deletion next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
reactStrictMode: false,
images: {
domains: ['mssaem-bucket.s3.ap-northeast-2.amazonaws.com'],
},
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"eslint-config-prettier": "^9.1.0",
"marked": "^13.0.3",
"next": "14.2.5",
"next-auth": "^4.24.7",
"node-sass": "^9.0.0",
"postcss-loader": "^8.1.1",
"prettier": "^3.3.2",
Expand Down
13 changes: 13 additions & 0 deletions src/app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import NextAuth from 'next-auth'
import GoogleProvider from 'next-auth/providers/google'

const handler = NextAuth({
providers: [
GoogleProvider({
clientId: process.env.NEXT_PUBLIC_APP_GOOGLE_API_KEY || '',
clientSecret: process.env.NEXT_PUBLIC_APP_GOOGLE_SECRET_KEY || '',
}),
],
})

export { handler as GET, handler as POST }
50 changes: 50 additions & 0 deletions src/app/kakao/login/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use client'

import axios from 'axios'
import { useRouter } from 'next/navigation'
import { useEffect } from 'react'

const KakaoLogin = () => {
const router = useRouter()
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL

useEffect(() => {
const getToken = async () => {
const AUTHORIZATION_CODE = new URL(window.location.href).searchParams.get(
'code',
)
if (!AUTHORIZATION_CODE) {
console.error('Authorization Code is missing')
return
}

try {
const res = await axios.post(`${API_BASE_URL}/kakao/login`, {
idToken: AUTHORIZATION_CODE,
})

if (res.data.code === 'MEMBER_002') {
// 에러 코드가 MEMBER_002일 경우 이메일을 localStorage에 저장하고 회원가입 페이지로 이동
localStorage.setItem('email', res.data.message)
router.push('/signin/terms')
} else if (res.data.accessToken) {
// 정상적으로 토큰을 받은 경우 /로 리다이렉트
localStorage.setItem('access_token', res.data.accessToken)
router.push('/')
} else {
// 토큰이 없을 경우에도 /signin/terms로 리다이렉트
router.push('/signin/terms')
}
} catch (error) {
console.error('Error during Kakao login:', error)
router.push('/signin/terms')
}
}

getToken()
}, [router])

return null
}

export default KakaoLogin
64 changes: 53 additions & 11 deletions src/app/signin/info/page.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,43 @@
'use client'

import React, { useState, useEffect } from 'react'
import MbtiSelect from '@/components/auth/MbtiSelect'
import Button from '@/components/common/Button'
import Input from '@/components/common/Input'
import { useRouter } from 'next/navigation'
import { useState } from 'react'
import { usePostSignup } from '@/service/auth/useAuthService'

const Info = () => {
const router = useRouter()
const [nickName, setNickName] = useState('')
const [mbti, setMbti] = useState<string[]>(['E', 'S', 'T', 'J'])
const [email, setEmail] = useState('')

const { mutate } = usePostSignup()

useEffect(() => {
const storedEmail = localStorage.getItem('email')
if (storedEmail) {
setEmail(storedEmail)
}
}, [])

const handleNicknameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setNickName(e.target.value)
}

const handleMbtiChange = (index: number, selectedMbti: string) => {
const updatedMbti = [...mbti]
updatedMbti[index] = selectedMbti
setMbti(updatedMbti)
}

const handleSignupClick = () => {
const upperMbti = mbti.map((char) => char.toUpperCase()).join('')
const caseSensitivity = mbti
.map((char) => (char === char.toLowerCase() ? '0' : '1'))
.join('')
mutate({ email, nickName, mbti: upperMbti, caseSensitivity })
}

return (
<div className="flex flex-col items-start my-18 w-full max-w-95 mx-auto gap-10">
<div className="w-full text-center">
Expand All @@ -35,25 +59,43 @@ const Info = () => {
/>
</div>

<div className="flex flex-col gap-5 w-full">
<div className="text-gray2 text-headline font-semibold">
당신의 이메일은 {email}입니다.
</div>
</div>

<div className="flex flex-col gap-5 w-full">
<div className="text-gray2 text-headline font-semibold">
당신의 MBTI는 무엇인가요?
</div>
<div className="flex justify-between align-center">
<MbtiSelect options={['E', 'e', 'i', 'I']} />
<MbtiSelect options={['S', 's', 'n', 'N']} />
<MbtiSelect options={['T', 't', 'f', 'F']} />
<MbtiSelect options={['J', 'j', 'p', 'P']} />
<div className="flex justify-between align-center gap-2">
<MbtiSelect
options={['E', 'e', 'I', 'i']}
onSelect={(selected) => handleMbtiChange(0, selected)}
/>
<MbtiSelect
options={['S', 's', 'N', 'n']}
onSelect={(selected) => handleMbtiChange(1, selected)}
/>
<MbtiSelect
options={['T', 't', 'F', 'f']}
onSelect={(selected) => handleMbtiChange(2, selected)}
/>
<MbtiSelect
options={['J', 'j', 'P', 'p']}
onSelect={(selected) => handleMbtiChange(3, selected)}
/>
</div>
</div>

<Button
text="회원가입"
color="GRAY"
size="login"
onClick={() => {
router.push('/')
}}
onClick={handleSignupClick}
disabled={nickName === ''}
className={`${nickName !== '' ? 'bg-main2' : ''}`}
/>
</div>
)
Expand Down
60 changes: 60 additions & 0 deletions src/app/signin/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use client'

import Image from 'next/image'
import { useRouter } from 'next/navigation'

const Signin = () => {
const router = useRouter()
const KAKAO_API_KEY = process.env.NEXT_PUBLIC_APP_KAKAO_API_KEY
const KAKAO_REDIRECT_URI = process.env.NEXT_PUBLIC_APP_KAKAO_REDIRECT_URI
const GOOGLE_API_KEY = process.env.NEXT_PUBLIC_APP_GOOGLE_API_KEY
const GOOGLE_REDIRECT_URI = process.env.NEXT_PUBLIC_APP_GOOGLE_REDIRECT_URI

const KakaoSigninBtnClick = () => {
const URL = `https://kauth.kakao.com/oauth/authorize?client_id=${KAKAO_API_KEY}&redirect_uri=${KAKAO_REDIRECT_URI}&response_type=code&prompt=login`
router.push(URL)
}

const GoogleSigninBtnClick = () => {
const URL = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${GOOGLE_API_KEY}&redirect_uri=${GOOGLE_REDIRECT_URI}&response_type=code&scope=https://www.googleapis.com/auth/userinfo.email`
router.push(URL)
}

return (
<div className="flex flex-col items-center my-18 w-full max-w-95 mx-auto gap-5">
<Image
src="/images/common/cat_logo.svg"
alt="google_btn"
width={83}
height={73}
/>
<div className="text-maindark text-title1 font-bold">
로그인 / 회원가입
</div>
<div className="text-gray2 text-headline font-semibold">
소셜 로그인로 가입할 수 있습니다.
</div>
<div className="h-[1px] bg-gray4 w-full" />
<div className="flex flex-col gap-5">
<Image
src="/images/auth/google_btn.svg"
alt="google_btn"
width={380}
height={50}
className="cursor-pointer"
onClick={GoogleSigninBtnClick}
/>
<Image
src="/images/auth/kakao_btn.svg"
alt="kako_btn"
width={380}
height={50}
className="cursor-pointer"
onClick={KakaoSigninBtnClick}
/>
</div>
</div>
)
}

export default Signin
66 changes: 33 additions & 33 deletions src/app/signin/terms/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,21 @@ import Button from '@/components/common/Button'
import LabeledButton from '@/components/common/LabeledButton'
import Textarea from '@/components/common/Textarea'
import { useRouter } from 'next/navigation'
import { useTerms } from '@/service/user/useUserService'

// TODO: 약관 내용 DB 넣기
const TERMS = [
'회원 가입 시 이름, 생년월일, 휴대전화번호 등의 정보를 허위로 기재해서는 안 됩니다. 회원 계정에 등록된 정보는 항상 정확한 최신 정보가 유지될 수 있도록 관리해 주세요. 자신의 계정을 다른 사람에게 판매, 양도, 대여 또는 담보로 제공하거나 다른 사람에게 그 사용을 허락해서는 안 됩니다. 아울러 자신의 계정이 아닌 타인의 계정을 무단으로 사용해서는 안 됩니다. 이에 관한 상세한 내용은 계정 운영 정책을 참고해 주시기 바랍니다.',
'회원 가입 시 이름, 생년월일, 휴대전화번호 등의 정보를 허위로 기재해서는 안 됩니다. 회원 계정에 등록된 정보는 항상 정확한 최신 정보가 유지될 수 있도록 관리해 주세요. 자신의 계정을 다른 사람에게 판매, 양도, 대여 또는 담보로 제공하거나 다른 사람에게 그 사용을 허락해서는 안 됩니다. 아울러 자신의 계정이 아닌 타인의 계정을 무단으로 사용해서는 안 됩니다. 이에 관한 상세한 내용은 계정 운영 정책을 참고해 주시기 바랍니다.',
'회원 가입 시 이름, 생년월일, 휴대전화번호 등의 정보를 허위로 기재해서는 안 됩니다. 회원 계정에 등록된 정보는 항상 정확한 최신 정보가 유지될 수 있도록 관리해 주세요. 자신의 계정을 다른 사람에게 판매, 양도, 대여 또는 담보로 제공하거나 다른 사람에게 그 사용을 허락해서는 안 됩니다. 아울러 자신의 계정이 아닌 타인의 계정을 무단으로 사용해서는 안 됩니다. 이에 관한 상세한 내용은 계정 운영 정책을 참고해 주시기 바랍니다.',
]
interface Term {
content: string
}

const Terms = () => {
const router = useRouter()
const [checkedTerms, setCheckedTerms] = useState([false, false, false])
const { data: terms } = useTerms()

const [checkedTerms, setCheckedTerms] = useState<boolean[]>([
false,
false,
false,
])
const [isAllChecked, setIsAllChecked] = useState(false)

const handleTermChange = (index: number) => {
Expand All @@ -27,37 +31,31 @@ const Terms = () => {

const handleAllChange = () => {
const newState = !isAllChecked
setCheckedTerms([newState, newState, newState])
const updatedCheckedTerms = new Array(terms.length).fill(newState)
setCheckedTerms(updatedCheckedTerms)
setIsAllChecked(newState)
}

const handleNextClick = () => {
if (checkedTerms.every((term) => term)) {
router.push('/signin/info')
}
}

return (
<div className="flex flex-col justify-center gap-10 my-18 w-full max-w-95 mx-auto text-center">
<div className="text-maindark text-title1 font-bold">이용약관</div>
<div className="flex flex-col gap-5">
<LabeledButton
label="[필수] 회원가입"
isClicked={checkedTerms[0]}
onClick={() => handleTermChange(0)}
/>
<Textarea value={TERMS[0]} color="gray" size="large" />
</div>
<div className="flex flex-col gap-5">
<LabeledButton
label="[필수] 회원가입"
isClicked={checkedTerms[1]}
onClick={() => handleTermChange(1)}
/>
<Textarea value={TERMS[1]} color="gray" size="large" />
</div>
<div className="flex flex-col gap-5">
<LabeledButton
label="[필수] 회원가입"
isClicked={checkedTerms[2]}
onClick={() => handleTermChange(2)}
/>
<Textarea value={TERMS[2]} color="gray" size="large" />
</div>
{terms &&
terms.map((term: Term, index: number) => (
<div key={index} className="flex flex-col gap-5">
<LabeledButton
label="[필수] 회원가입"
isClicked={checkedTerms[index]}
onClick={() => handleTermChange(index)}
/>
<Textarea value={term.content} color="gray" size="large" />
</div>
))}
<div className="flex flex-col gap-5">
<LabeledButton
label="전체동의"
Expand All @@ -68,7 +66,9 @@ const Terms = () => {
text="다음"
color="GRAY"
size="login"
onClick={() => router.push('/signin/info')}
onClick={handleNextClick}
disabled={!checkedTerms.every((term) => term)}
className={`${checkedTerms.every((term) => term) ? 'bg-main2' : ''}`}
/>
</div>
</div>
Expand Down
Loading

0 comments on commit 8d45492

Please sign in to comment.