diff --git a/frontend/.gitignore b/frontend/.gitignore
index a547bf3..69258c8 100644
--- a/frontend/.gitignore
+++ b/frontend/.gitignore
@@ -22,3 +22,9 @@ dist-ssr
*.njsproj
*.sln
*.sw?
+
+.env
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
\ No newline at end of file
diff --git a/frontend/index.html b/frontend/index.html
index e4b78ea..c82c9b1 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -1,5 +1,5 @@
-
+
@@ -9,5 +9,6 @@
+
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index e68fa1a..0e03322 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -6,6 +6,8 @@ import ChattingListPage from './pages/ChattingListPage';
import SignupPage from './pages/SignupPage';
import SigninPage from './pages/SigninPage';
import ChattingRoomPage from './pages/ChattingRoomPage';
+import TestPage from './test/TestPage';
+import Signupnaver from './pages/Signupnaver';
import VideoCallPage from './pages/VideoCallPage';
import { Global } from '@emotion/react';
import { GlobalStyle } from './styles/GlobalStyle';
@@ -22,6 +24,8 @@ const router = createBrowserRouter([
{ path: 'signup', element: },
{ path: 'chattinglist', element: },
{ path: 'chatroom/:chatroom_id', element: },
+ { path: 'test', element: },
+ { path: 'signupnaver', element: },
{ path: 'videocall', element: },
],
},
diff --git a/frontend/src/components/Button.tsx b/frontend/src/components/Button.tsx
new file mode 100644
index 0000000..65df1ec
--- /dev/null
+++ b/frontend/src/components/Button.tsx
@@ -0,0 +1,7 @@
+export const Button = ({onClick,label,...rest})=>{
+ return(
+
+ )
+}
\ No newline at end of file
diff --git a/frontend/src/components/InputForm.tsx b/frontend/src/components/InputForm.tsx
new file mode 100644
index 0000000..1101d1d
--- /dev/null
+++ b/frontend/src/components/InputForm.tsx
@@ -0,0 +1,15 @@
+export const InputForm = ({ type, name, value, onChange, placeholder, title, ...rest }) => {
+ return (
+ <>
+ {title}
+
+ >
+ );
+};
diff --git a/frontend/src/components/auth/NaverLoginButton.tsx b/frontend/src/components/auth/NaverLoginButton.tsx
new file mode 100644
index 0000000..bec1db9
--- /dev/null
+++ b/frontend/src/components/auth/NaverLoginButton.tsx
@@ -0,0 +1,97 @@
+import styled from '@emotion/styled';
+import { useEffect, useRef } from 'react'
+
+const CLIENT_ID = import.meta.env.VITE_APP_CLIENT_ID;
+
+export const NaverLoginButton = () => {
+ const naverRef = useRef()
+ const { naver } = window
+ const REDIRECT_URI = "http://localhost:5173/signupnaver";
+
+ const initializeNaverLogin = () => {
+ const naverLogin = new naver.LoginWithNaverId({
+ clientId: CLIENT_ID,
+ callbackUrl: REDIRECT_URI,
+ isPopup: false,
+ loginButton: { color: 'green', type: 3, height: 58 },
+ callbackHandle: true,
+ })
+ naverLogin.init()
+
+ naverLogin.getLoginStatus(async function (status) {
+ if (status) {
+ const userid = naverLogin.user.getEmail()
+ const username = naverLogin.user.getName()
+ console.log(userid,username)
+ }
+ })
+ }
+
+ useEffect(() => {
+ initializeNaverLogin()
+ }, [])
+
+ const handleNaverLogin = () => {
+ naverRef.current.children[0].click()
+ }
+
+
+ return (
+ <>
+
+
+
+
+
+
+
+
네이버로 간편 가입
+
+
+
+ >
+ );
+}
+
+const NaverIdLogin = styled.div`
+ display: none`;
+
+const NaverButtonStyles = styled.div`
+ .NaverLoginButton-wrapper {
+ display: flex;
+ }
+
+ .Logo{
+ display: flex;
+ }
+
+ .btn-naver {
+ width: 300px;
+ height: 46px;
+ background: #03C75A;
+ border-radius: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 1rem;
+ transition: background-color 0.3s;
+ }
+
+ .text {
+ font-family: 'Noto Sans', sans-serif;
+ font-style: normal;
+ font-weight: 500;
+ font-size: 14px;
+ line-height: 24px;
+ text-align: center;
+ color: #ffffff;
+ }
+
+ .btn-naver:hover {
+ background-color: #029445;
+ }
+`;
\ No newline at end of file
diff --git a/frontend/src/hooks/useAuthQuery.ts b/frontend/src/hooks/useAuthQuery.ts
new file mode 100644
index 0000000..e4b2372
--- /dev/null
+++ b/frontend/src/hooks/useAuthQuery.ts
@@ -0,0 +1,163 @@
+import axios,{ AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
+import { UserInfo } from '../stores/UserInfoStore';
+import { useQuery } from '@tanstack/react-query';
+
+
+export const AuthApis = {
+ instance: axios.create({
+ baseURL: "http://localhost:8001/user-service/",
+ withCredentials: true,
+ }),
+
+ checkEmailDuplicate: async (userInfo: UserInfo): Promise => {
+ const response = await AuthApis.instance.get(`/signup/exists-email/${userInfo.email}`, {
+ email: userInfo.email,
+ });
+ return response.data.valid === true;
+ },
+
+ useCheckEmailDuplicateQuery: (userInfo: UserInfo) => {
+ const { data, isLoading, error } = useQuery({
+ queryKey: ['checkEmailDuplicateQuery', userInfo.email],
+ queryFn: () => AuthApis.checkEmailDuplicate(userInfo),
+ });
+ return { data, isLoading, error };
+ },
+
+ checkUsernameDuplicate: async (userInfo:UserInfo): Promise => {
+ const response = await AuthApis.instance.get(`/signup/exists-username/${userInfo.username}`, {
+ username: userInfo.username,
+ });
+ return response.data.valid === true;
+ },
+
+ checkUsernameDuplicateQuery: (userInfo: UserInfo) => {
+ const { data, isLoading, error } = useQuery({
+ queryKey: ['checkUsernameDuplicateQuery', userInfo.username],
+ queryFn: () => AuthApis.checkUsernameDuplicate(userInfo),
+ });
+ return { data, isLoading, error };
+ },
+
+ signup: async (userInfo:UserInfo, passwordConfirm: string) => {
+ try {
+ const response = await AuthApis.instance.post('/signup', {
+ username: userInfo.username,
+ email: userInfo.email,
+ password: userInfo.password,
+ passwordConfirm: passwordConfirm,
+ });
+ const data = response.data;
+
+ return data;
+ } catch (error) {
+ console.error("API 통신 에러:", error);
+ }
+ },
+
+ signupQuery: (userInfo: UserInfo,passwordConfirm:string) => {
+ const { data, isLoading, error } = useQuery({
+ queryKey: ['signupQuery', userInfo,passwordConfirm],
+ queryFn: () => AuthApis.signup(userInfo, passwordConfirm),
+ });
+ return { data, isLoading, error };
+ },
+
+
+ signin: async (userInfo:UserInfo) => {
+ try {
+ const response = await AuthApis.instance.post('/login', {
+ email: userInfo.email,
+ password: userInfo.password,
+ });
+ const data = response.data;
+
+ const rawAccessToken = response.headers.get('Accesstoken');
+ const rawRefreshToken = response.headers.get('RefreshToken');
+
+ const Accesstoken = rawAccessToken ? rawAccessToken.replace(/^Bearer\s+/i, '') : null;
+ const Refreshtoken = rawRefreshToken ? rawRefreshToken.replace(/^Bearer\s+/i, '') : null;
+
+ // console.log(`토큰 발급\n Accesstoken:${Accesstoken}\n Refreshtoken:${Refreshtoken}`)
+
+ localStorage.setItem('Accesstoken', Accesstoken);
+ localStorage.setItem('Refreshtoken', Refreshtoken);
+
+ return data;
+ } catch (error) {
+ console.error("API 통신 에러:", error);
+ }
+ },
+
+ //refreshToken
+ setupInterceptors: () => {
+ AuthApis.instance.interceptors.response.use(
+ (response: AxiosResponse) => response,
+ async (error) => {
+ const originalRequest = error.config;
+
+ if (error.response.status === 401 && !originalRequest._retry) {
+ originalRequest._retry = true;
+
+ try {
+ const refreshToken = localStorage.getItem('refreshToken');
+ // 재발급받는 경로
+ const response = await AuthApis.instance.get('/hello', {
+ headers: {Refreshtoken : `Bearer ${localStorage.getItem('Refreshtoken')}`}
+ });
+
+ const rawAccessToken = response.headers.get('Accesstoken');
+ const rawRefreshToken = response.headers.get('Refreshtoken');
+
+ const newAccesstoken = rawAccessToken ? rawAccessToken.replace(/^Bearer\s+/i, '') : null;
+ const newRefreshtoken = rawRefreshToken ? rawRefreshToken.replace(/^Bearer\s+/i, '') : null;
+
+ // console.log(`토큰 갱신\n newAccesstoken:${newAccesstoken}\n newRefreshtoken:${newRefreshtoken}`)
+
+ localStorage.setItem('Accesstoken',newAccesstoken);
+ localStorage.setItem('Refreshtoken',newRefreshtoken);
+
+ originalRequest.headers['Authorization'] = `Bearer ${newAccesstoken}`;
+
+ return AuthApis.instance(originalRequest);
+ } catch (refreshError) {
+ console.error("토큰 갱신 실패", refreshError);
+ localStorage.removeItem('Accesstoken');
+ localStorage.removeItem('Refreshtoken');
+ window.location.href = '/signin';
+ }
+ }
+
+ return Promise.reject(error);
+ }
+ );
+ },
+
+
+ // 리팩토링용 코드
+ fetchData: async (endpoint: string, type: string, data?: any) => {
+ let response;
+
+ switch (type) {
+ case 'get':
+ response = await AuthApis.instance.get(`http://localhost:8001/user-service/${endpoint}`, {
+ headers: { Authorization: `Bearer ${localStorage.getItem('jwtToken')}` },
+ });
+ break;
+
+ case 'post':
+ response = await AuthApis.instance.post(`http://localhost:8001/user-service/${endpoint}`, data, {
+ headers: { Authorization: `Bearer ${localStorage.getItem('jwtToken')}` },
+ });
+ break;
+
+ default:
+ console.error("Invalid type:", type);
+ }
+
+ return response;
+ },
+
+};
+
+
diff --git a/frontend/src/pages/SigninPage.tsx b/frontend/src/pages/SigninPage.tsx
index e1031c3..1177594 100644
--- a/frontend/src/pages/SigninPage.tsx
+++ b/frontend/src/pages/SigninPage.tsx
@@ -1,7 +1,93 @@
+import { useEffect } from "react";
+import { useNavigate } from "react-router-dom";
+import { InputForm } from "../components/InputForm";
+import { Button } from "../components/Button";
+import { NaverLoginButton } from "../components/auth/NaverLoginButton";
+import { useStore } from "zustand";
+import { UserInfoStore } from "../stores/UserInfoStore";
+
+import { AuthApis } from "../hooks/useAuthQuery";
+import axios from "axios";
+
+
+const fetchDataExample = async () => {
+ instance: axios.create({
+ baseURL: "http://localhost:8001/user-service/",
+ withCredentials: true,
+ })
+
+ try {
+ AuthApis.setupInterceptors();
+ const response = await AuthApis.instance.get('/hello', {
+ headers: { Accesstoken: `Bearer ${localStorage.getItem('Accesstoken')}` },
+ });
+ const data = response.data;
+ console.log("로그인 성공")
+ return data;
+ } catch (error) {
+ }
+ };
+
const SigninPage = () => {
+ const navigate = useNavigate();
+ const userInfo = useStore(UserInfoStore);
+
+ const onChange = (e: React.ChangeEvent) => {
+ const { value, name } = e.target;
+
+ if (name === 'email') {
+ userInfo.updateEmail(value);
+ } else if (name === 'password') {
+ userInfo.updatePassword(value);
+ }
+ };
+
+ const onButtonClick = (action:string) => {
+ switch(action){
+ case "onSignin":
+ AuthApis.signin(userInfo);
+ navigate("/");
+ break;
+ case "onSignup":
+ navigate("/signup")
+ break;
+ case "onSignupKakao":
+ fetchDataExample();
+ // navigate("/signupnaver")
+ break;
+ default:
+ console.error("error")
+ }
+ }
+
return(
- <>It's SigninPage!>
- )
+
+
+ TadakTadak Logo
+ <>It's SigninPage!>
+
+
+ {/* email input */}
+
+
+
+
+ {/* pw input */}
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+
};
-export default SigninPage;
+export default SigninPage;
\ No newline at end of file
diff --git a/frontend/src/pages/Signupnaver.tsx b/frontend/src/pages/Signupnaver.tsx
new file mode 100644
index 0000000..94c6806
--- /dev/null
+++ b/frontend/src/pages/Signupnaver.tsx
@@ -0,0 +1,42 @@
+import { useNavigate } from "react-router-dom";
+import { useEffect, useState } from "react";
+import axios from "axios";
+import { UserInfoStore } from '../stores/UserInfoStore';
+import { useStore } from 'zustand';
+
+const Signupnaver = () => {
+ const navigate = useNavigate();
+ const userInfo = useStore(UserInfoStore);
+
+ const userAccessToken = () => {
+ window.location.href.includes('access_token') && getToken()
+ navigate('/')
+ }
+
+ const getToken = async () => {
+ const token = window.location.href.split('=')[1].split('&')[0]
+ localStorage.setItem('Accesstoken', token)
+
+ // 네이버 API를 호출하여 사용자 정보 가져오기 proxy 적용중 수정필요
+ try {
+ const response = await axios.get('/api/v1/nid/me', {
+ headers: {
+ 'Authorization': `Bearer ${token}`
+ }}
+ )
+ console.log(response.data.response.email);
+ userInfo.updateEmail(response.data.response.email)
+ userInfo.updateUsername(response.data.response.nickname)
+ } catch (error) {
+ console.error('Error fetching user info', error);
+ }
+ }
+
+ useEffect(() => {
+ userAccessToken()
+ }, [])
+
+ return ;
+};
+
+export default Signupnaver;
diff --git a/frontend/src/stores/UserInfoStore.ts b/frontend/src/stores/UserInfoStore.ts
new file mode 100644
index 0000000..b4dfcc5
--- /dev/null
+++ b/frontend/src/stores/UserInfoStore.ts
@@ -0,0 +1,31 @@
+import { create } from "zustand";
+import { devtools, persist } from "zustand/middleware";
+
+export interface UserInfo {
+ email: string;
+ password: string;
+ username: string;
+ updateEmail: (email: UserInfo['email']) => void;
+ updatePassword: (password: UserInfo['password']) => void;
+ updateUsername: (username: UserInfo['username']) => void;
+}
+
+const createUserInfoStore = (set) => ({
+ email: '',
+ password: '',
+ username: '',
+ updateEmail: (email: string) => set({ email }),
+ updatePassword: (password: string) => set({ password }),
+ updateUsername: (username: string) => set({ username }),
+});
+
+let userInfoStoreTemp;
+
+//devtools
+if (import.meta.env.DEV) {
+ userInfoStoreTemp = create()(devtools(createUserInfoStore, { name: 'userInfo' }));
+} else {
+ userInfoStoreTemp = create()(createUserInfoStore);
+}
+
+export const UserInfoStore = userInfoStoreTemp;
\ No newline at end of file
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
index a7fc6fb..66eda50 100644
--- a/frontend/tsconfig.json
+++ b/frontend/tsconfig.json
@@ -5,6 +5,7 @@
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
+ "types": ["vite/client"],
/* Bundler mode */
"moduleResolution": "bundler",