Skip to content

Commit

Permalink
[feat/#16] 사용자 로그인 기능 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
lkhoony committed Aug 19, 2024
1 parent 25e9137 commit fbfc46a
Show file tree
Hide file tree
Showing 20 changed files with 394 additions and 91 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ jobs:
run: yarn install

- name: Generate build
env:
VITE_API_BASE_URL: ${{ secrets.VITE_API_BASE_URL }}
VITE_OAUTH_KAKAO_REST_API_KEY: ${{ secrets.VITE_OAUTH_KAKAO_REST_API_KEY }}
VITE_OAUTH_KAKAO_CLIENT_SECRET_CODE: ${{ secrets.VITE_OAUTH_KAKAO_CLIENT_SECRET_CODE }}
VITE_OAUTH_KAKAO_REDIRECT_URI: ${{ secrets.VITE_OAUTH_KAKAO_REDIRECT_URI }}
run: yarn build

- name: Deploy
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
"lint:fix": "eslint \"src/**/*.{js,jsx,ts,tsx}\" --fix"
},
"dependencies": {
"@tanstack/react-query": "^5.51.23",
"axios": "1.7.3",
"core-js": "^3.28.0",
"p5": "^1.9.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.8.1"
"react-router-dom": "^6.8.1",
"zustand": "^4.5.5"
},
"devDependencies": {
"@types/node": "^20.7.1",
Expand Down
12 changes: 10 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
// dependencies
import { Router } from "@/routes"

const App: React.FC = () => {
return <Router></Router>
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"

const queryClient = new QueryClient()

const App = (): React.ReactElement => {
return (
<QueryClientProvider client={queryClient}>
<Router></Router>
</QueryClientProvider>
)
}

export default App
87 changes: 87 additions & 0 deletions src/api/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// src/api/auth.ts
import axiosInstance, { setAccessToken } from "@/api/axiosInstance"
import qs from "qs"

const REST_API_KEY = import.meta.env.VITE_OAUTH_KAKAO_REST_API_KEY
const CLIENT_SECRET = import.meta.env.VITE_OAUTH_KAKAO_CLIENT_SECRET_CODE
const REDIRECT_URI = import.meta.env.VITE_OAUTH_KAKAO_REDIRECT_URI

export interface authUser {
uid: number
nickname: string
accessToken: string
}

export interface oauthUser {
nickname: string
}

export const oauth = async (code: string): Promise<string> => {
const formData = {
grant_type: "authorization_code",
client_id: REST_API_KEY,
client_secret: CLIENT_SECRET,
redirect_uri: REDIRECT_URI,
code,
}

try {
const res = await axiosInstance.post(`https://kauth.kakao.com/oauth/token?${qs.stringify(formData)}`, null, {
headers: { "Content-type": "application/x-www-form-urlencoded" },
})
return res.data.access_token
} catch (e) {
throw e
}
}

export const getOauthUser = async (accessToken: string): Promise<oauthUser> => {
const kakaoUser = await axiosInstance.get(`https://kapi.kakao.com/v2/user/me`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
})
const { nickname } = kakaoUser.data.kakao_account.profile

return { nickname }
}

export const signIn = async (_accessToken: string): Promise<authUser> => {
try {
const res = await axiosInstance.post(`/oauth/kakao/sign-in`, { accessToken: _accessToken })
const { uid, nickname, accessToken } = res.data.data

// 로그인 성공 후 엑세스 토큰을 설정
setAccessToken(accessToken)

return { uid, nickname, accessToken }
} catch (e) {
throw e
}
}

export const signUp = async (_accessToken: string): Promise<authUser> => {
try {
const res = await axiosInstance.post(`/oauth/kakao/sign-up`, { accessToken: _accessToken })
const { uid, nickname, accessToken } = res.data.data

// 회원가입 성공 후 엑세스 토큰을 설정
setAccessToken(accessToken)

return { uid, nickname, accessToken }
} catch (e) {
throw e
}
}

export const getIsSignUp = async (accessToken: string): Promise<boolean> => {
try {
const res = await axiosInstance.get(`/oauth/kakao/sign-up/check`, {
params: { accessToken },
})
console.log(res.data)
return res.data.data.isExistsUser
} catch (e) {
throw e
}
}
31 changes: 31 additions & 0 deletions src/api/axiosInstance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// src/services/axiosInstance.ts
import axios from "axios"

const API_BASE_URL = import.meta.env.VITE_API_BASE_URL

const axiosInstance = axios.create({
baseURL: API_BASE_URL,
headers: {
"Content-Type": "application/json",
},
})

// localStorage에서 토큰 가져오기
const token = localStorage.getItem("accessToken")
if (token) {
axiosInstance.defaults.headers.common["Authorization"] = `Bearer ${token}`
}

// 엑세스 토큰 설정 함수
export const setAccessToken = (token: string) => {
axiosInstance.defaults.headers.common["Authorization"] = `Bearer ${token}`
localStorage.setItem("accessToken", token)
}

// 엑세스 토큰 제거 함수
export const clearAccessToken = () => {
delete axiosInstance.defaults.headers.common["Authorization"]
localStorage.removeItem("accessToken")
}

export default axiosInstance
1 change: 1 addition & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./auth"
34 changes: 17 additions & 17 deletions src/components/Camera.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,22 @@ export default function Camera(props: CameraProps): React.ReactElement {
position: "relative",
}}
>
<div style={{ position: "relative", width: "100%", height: "100%" }}>
<video
className="rounded-lg"
ref={videoRef}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
objectFit: "fill",
transform: "scaleX(-1)", // 비디오를 좌우 반전시키는 CSS 속성 추가
}}
/>
</div>
<canvas
<div style={{ position: "relative", width: "100%", height: "100%" }}>
<video
className="rounded-lg"
ref={videoRef}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
objectFit: "fill",
transform: "scaleX(-1)", // 비디오를 좌우 반전시키는 CSS 속성 추가
}}
/>
</div>
<canvas
ref={canvasRef}
width="1280"
height="720"
Expand All @@ -80,4 +80,4 @@ export default function Camera(props: CameraProps): React.ReactElement {
/>
</div>
)
}
}
6 changes: 5 additions & 1 deletion src/components/SideNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import MainCraftIcon from "public/icons/posture-craft-side-nav-icon.svg?react"
import MonitoringIcon from "public/icons/side-nav-monitor-icon.svg?react"
import AnalysisIcon from "public/icons/side-nav-analysis-icon.svg?react"
import CrewIcon from "public/icons/side-nav-crew-icon.svg?react"
import { useAuthStore } from "@/store/AuthStore"
import { useEffect } from "react"

const navItems = [
{
Expand All @@ -25,6 +27,8 @@ const navItems = [
const footerLinks = ["이용약관", "의견보내기", "로그아웃"]

export default function SideNav() {
const nickname = useAuthStore((state) => state.user?.nickname)

return (
<aside className="w-[224px] flex-none bg-[#1C1D20]">
<div className="flex h-full flex-col justify-between text-white">
Expand All @@ -40,7 +44,7 @@ export default function SideNav() {
<div className="pl-6 pt-2">
<div className="text-sm text-gray-400">바른자세 똑딱똑딱</div>
<div>
<span className="text-sm font-bold">조하은</span>
<span className="text-sm font-bold">{nickname}</span>
<span className="text-sm text-gray-400"></span>
</div>
</div>
Expand Down
3 changes: 2 additions & 1 deletion src/constants/routes.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"MONITORING": "/monitoring",
"LOGIN": "/login",
"AUTH" : "/auth",
"SOCKET": "/socket"
"SOCKET": "/socket",
"HOME" : "/home"
}
61 changes: 61 additions & 0 deletions src/hooks/useAuthMutation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { authUser, getIsSignUp, getOauthUser, oauth, oauthUser, signIn, signUp } from "@/api"
import { useMutation, UseMutationResult } from "@tanstack/react-query"

export const useOauth = (): UseMutationResult<string, unknown, string, unknown> => {
return useMutation({
mutationFn: (code: string) => {
return oauth(code)
},
onSuccess: (data) => {
console.log(data)
},
})
}

export const useGetOauthUser = (): UseMutationResult<oauthUser, unknown, string, unknown> => {
return useMutation({
mutationFn: (accessToken: string) => getOauthUser(accessToken),
onSuccess: (data) => {
console.log("Oauth user data:", data)
},
onError: (error) => {
console.error("Error fetching oauth user:", error)
},
})
}

export const useSignIn = (): UseMutationResult<authUser, unknown, string, unknown> => {
return useMutation({
mutationFn: (_accessToken: string) => signIn(_accessToken),
onSuccess: (data) => {
console.log("User signed in:", data)
},
onError: (error) => {
console.error("Error signing in:", error)
},
})
}

export const useSignUp = (): UseMutationResult<authUser, unknown, string, unknown> => {
return useMutation({
mutationFn: (_accessToken: string) => signUp(_accessToken),
onSuccess: (data) => {
console.log("User signed up:", data)
},
onError: (error) => {
console.error("Error signing up:", error)
},
})
}

export const useGetIsSignUp = (): UseMutationResult<boolean, unknown, string, unknown> => {
return useMutation({
mutationFn: (accessToken: string) => getIsSignUp(accessToken),
onSuccess: (data) => {
console.log("User signup status:", data)
},
onError: (error) => {
console.error("Error checking signup status:", error)
},
})
}
Loading

0 comments on commit fbfc46a

Please sign in to comment.