From 11875ef5f09b6dc6585fd5462f87a0b6271c4776 Mon Sep 17 00:00:00 2001 From: seonghyuk1 Date: Wed, 25 Jun 2025 20:21:54 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[feat]=20=EC=84=B1=EB=B3=84=20=EB=9D=BC?= =?UTF-8?q?=EB=94=94=EC=98=A4=20=EB=B2=84=ED=8A=BC=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EB=B0=8F=20couple=20=ED=86=B5=EC=8B=A0=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 성별 라디오 버튼 구현 (남 BROOM / 여 BRIGE - 백엔드 명세) - API 통신 구현 --- src/features/sign-nickname/SignNickName.tsx | 71 +++++++++++++++++++-- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/src/features/sign-nickname/SignNickName.tsx b/src/features/sign-nickname/SignNickName.tsx index a5688fa..79ca0ac 100644 --- a/src/features/sign-nickname/SignNickName.tsx +++ b/src/features/sign-nickname/SignNickName.tsx @@ -8,14 +8,46 @@ import { TopBar } from '@/components/molecules/topBar/TopBar'; export default function SignNickName() { const [inputValue, setInputValue] = useState(''); + const [gender, setGender] = useState(''); const router = useRouter(); const handleInput = (value: string) => { setInputValue(value); }; - const handleConnect = () => { - if (inputValue.length > 0) router.push(`/home/${inputValue}`); + const handleConnect = async () => { + if (inputValue.length === 0 || gender === '') return; + + // GROOM: 신랑 / BRIDE: 신부 + const mappedGender = gender === 'male' ? 'GROOM' : 'BRIDE'; + + try { + const res = await fetch( + `${process.env.NEXT_PUBLIC_BACKEND_URL}api/couple`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify({ + gender: mappedGender, + nickName: inputValue, + }), + }, + ); + + if (!res.ok) { + throw new Error(`요청 실패: ${res.status}`); + } + + const result = await res.json(); + console.log('요청 성공:', result); + + router.push(`/home/${inputValue}`); + } catch (error) { + console.error('요청 중 오류 발생:', error); + } }; return ( @@ -28,6 +60,7 @@ export default function SignNickName() { router.push('/match-usercode'); }} /> +

연결 완료!

@@ -47,15 +80,45 @@ export default function SignNickName() { maxLength={2} />
+ +
+
+ {[ + { value: 'male', label: '남자' }, + { value: 'female', label: '여자' }, + ].map(({ value, label }) => { + const isSelected = gender === value; + + return ( + + ); + })} +
+
From 23f88acd1222bf616d4642cc6bc5210c86ee7afd Mon Sep 17 00:00:00 2001 From: seonghyuk1 Date: Wed, 23 Jul 2025 23:32:27 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[refactor]=20=EC=84=9C=EB=B2=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20token=20=EB=8F=99?= =?UTF-8?q?=EC=9E=91=20=EB=B0=A9=EC=8B=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - url에 저장되는 Redirection 코드값을 받아오도록 redirectUrl 수정 - localstorage 저장 방식을 통한 accessToken 핸들링 --- src/features/Redirection/Redirection.tsx | 25 +++++++++++++++++++----- src/features/components/LoginButton.tsx | 4 ++-- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/features/Redirection/Redirection.tsx b/src/features/Redirection/Redirection.tsx index b428e39..f2f6be8 100644 --- a/src/features/Redirection/Redirection.tsx +++ b/src/features/Redirection/Redirection.tsx @@ -1,29 +1,44 @@ 'use client'; import React, { useEffect } from 'react'; -import { useRouter } from 'next/navigation'; +import { useRouter, useSearchParams } from 'next/navigation'; const Redirection = () => { const router = useRouter(); + const searchParams = useSearchParams(); - // 쿠키 기반 로그인 상태 확인 useEffect(() => { + const accessToken = searchParams.get('accessToken'); + + if (accessToken) { + // accessToken 로컬스토리지에 저장 + localStorage.setItem('accessToken', accessToken); + console.log('accessToken 저장 완료:', accessToken); + } else { + console.warn('accessToken 없음, 홈으로 이동'); + router.replace('/'); + return; + } + const checkLoginStatus = async () => { try { const res = await fetch( `${process.env.NEXT_PUBLIC_BACKEND_URL}auth/status`, { credentials: 'include', + headers: { + Authorization: `Bearer ${accessToken}`, + }, }, ); const data = await res.json(); console.log('로그인 상태 응답:', data); - if (data.isLoggedIn) { + if (data.code === 200) { router.replace('/match-usercode'); } else { - console.log('로그인 실패 → 홈으로 이동'); + console.log('로그인 실패 → 홈으로'); router.replace('/'); } } catch (err) { @@ -33,7 +48,7 @@ const Redirection = () => { }; checkLoginStatus(); - }, [router]); + }, [router, searchParams]); return (
diff --git a/src/features/components/LoginButton.tsx b/src/features/components/LoginButton.tsx index 53e3a2b..8b1f64e 100644 --- a/src/features/components/LoginButton.tsx +++ b/src/features/components/LoginButton.tsx @@ -3,8 +3,8 @@ import React from 'react'; const LoginButton = () => { - const KAKAO_AUTH_URL = `${process.env.NEXT_PUBLIC_KAKAO_AUTH_URL}`; - + const redirectUrl = encodeURIComponent('http://localhost:3000/Redirection'); + const KAKAO_AUTH_URL = `${process.env.NEXT_PUBLIC_KAKAO_AUTH_URL}?redirectUrl=${redirectUrl}`; const loginHandler = () => { window.location.href = KAKAO_AUTH_URL; }; From c3ee10e74181981bbd9b40a3b059287a44f5b694 Mon Sep 17 00:00:00 2001 From: seonghyuk1 Date: Wed, 23 Jul 2025 23:34:50 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[refatcor]=20=EA=B0=9C=EB=B0=9C=20/=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20=ED=99=98=EA=B2=BD=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=20accessToken=20=EB=8F=99=EC=9E=91=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 서버 기획에 따른 개발계 / 운영계에서 token 저장 방식 수정 - 닉네임 매치의 경우 테스트 계정이 필요하여 임시적으로 필수이동으로 구현 --- src/features/match-usercode/MatchUserCode.tsx | 63 +++++++++++++++-- src/features/sign-nickname/SignNickName.tsx | 68 ++++++++++++------- 2 files changed, 100 insertions(+), 31 deletions(-) diff --git a/src/features/match-usercode/MatchUserCode.tsx b/src/features/match-usercode/MatchUserCode.tsx index 583fdf1..de9ebf7 100644 --- a/src/features/match-usercode/MatchUserCode.tsx +++ b/src/features/match-usercode/MatchUserCode.tsx @@ -1,13 +1,15 @@ 'use client'; + import { Button } from '@/components/atoms/button/Button'; import TextInput from '@/components/atoms/textInput/TextInput'; import { useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; +import Cookies from 'js-cookie'; import { TopBar } from '@/components/molecules/topBar/TopBar'; export default function MatchUserCode() { const [inputValue, setInputValue] = useState(''); - const [userCode, setUserCode] = useState('무서운츄러스145'); + const [userCode, setUserCode] = useState(''); const router = useRouter(); const handleInput = (value: string) => { @@ -15,25 +17,48 @@ export default function MatchUserCode() { }; const handleConnect = () => { - if (inputValue === '무서운츄러스145') router.push('/sign-nickname'); + if (inputValue === userCode) router.push('/sign-nickname'); + }; + + const getAccessToken = () => { + if (process.env.NODE_ENV === 'development') { + // 개발 환경에서는 localStorage + return localStorage.getItem('accessToken'); + } else { + // 배포 환경에서는 쿠키에서 읽기 + return Cookies.get('accessToken'); + } }; - // 쿠키 기반 로그인 상태 확인 + // 쿠키 기반 로그인 상태 확인 및 유저 코드 요청 useEffect(() => { + const accessToken = getAccessToken(); + + if (!accessToken) { + console.warn('Access token이 없습니다. 홈으로 이동합니다.'); + router.replace('/'); + return; + } + const checkLoginStatus = async () => { try { const res = await fetch( `${process.env.NEXT_PUBLIC_BACKEND_URL}auth/status`, { credentials: 'include', + headers: { + Authorization: `Bearer ${accessToken}`, + }, }, ); const data = await res.json(); + const isValid = data.code; + console.log('로그인 상태 응답:', data); - if (data.isLoggedIn) { - router.replace('/match-usercode'); + if (isValid === 200) { + fetchMatchCode(accessToken); } else { console.log('로그인 실패 → 홈으로 이동'); router.replace('/'); @@ -44,6 +69,32 @@ export default function MatchUserCode() { } }; + const fetchMatchCode = async (token: string) => { + try { + const res = await fetch( + `${process.env.NEXT_PUBLIC_BACKEND_URL}api/couple/match-code`, + { + credentials: 'include', + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ); + + const data = await res.json(); + const isValid = data.code; + console.log('매치 코드 응답:', data); + + if (isValid === 200 && data.data?.matchCode) { + setUserCode(data.data.matchCode); + } else { + console.warn('매치 코드 응답 실패'); + } + } catch (err) { + console.error('매치 코드 불러오기 실패:', err); + } + }; + checkLoginStatus(); }, [router]); @@ -71,7 +122,7 @@ export default function MatchUserCode() {

내 코드

- {userCode} + {userCode || '코드 불러오는 중...'}

diff --git a/src/features/sign-nickname/SignNickName.tsx b/src/features/sign-nickname/SignNickName.tsx index 79ca0ac..feeea62 100644 --- a/src/features/sign-nickname/SignNickName.tsx +++ b/src/features/sign-nickname/SignNickName.tsx @@ -4,6 +4,7 @@ import { Button } from '@/components/atoms/button/Button'; import TextInput from '@/components/atoms/textInput/TextInput'; import { useState } from 'react'; import { useRouter } from 'next/navigation'; +import Cookies from 'js-cookie'; import { TopBar } from '@/components/molecules/topBar/TopBar'; export default function SignNickName() { @@ -15,39 +16,52 @@ export default function SignNickName() { setInputValue(value); }; + const getAccessToken = () => { + return process.env.NODE_ENV === 'development' + ? localStorage.getItem('accessToken') + : Cookies.get('accessToken'); + }; + const handleConnect = async () => { if (inputValue.length === 0 || gender === '') return; - // GROOM: 신랑 / BRIDE: 신부 + const accessToken = getAccessToken(); + if (!accessToken) { + console.warn('Access token이 없습니다. 다시 로그인해주세요.'); + router.push('/'); + return; + } + const mappedGender = gender === 'male' ? 'GROOM' : 'BRIDE'; - try { - const res = await fetch( - `${process.env.NEXT_PUBLIC_BACKEND_URL}api/couple`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - credentials: 'include', - body: JSON.stringify({ - gender: mappedGender, - nickName: inputValue, - }), + // try { + const res = await fetch( + `${process.env.NEXT_PUBLIC_BACKEND_URL}api/couple`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${accessToken}`, }, - ); + credentials: 'include', + body: JSON.stringify({ + gender: mappedGender, + nickName: inputValue, + }), + }, + ); - if (!res.ok) { - throw new Error(`요청 실패: ${res.status}`); - } + const result = await res.json(); + console.log('result:', result); - const result = await res.json(); - console.log('요청 성공:', result); + // if (result.code !== 200) { + // throw new Error(`요청 실패: ${result.message}`); + // } - router.push(`/home/${inputValue}`); - } catch (error) { - console.error('요청 중 오류 발생:', error); - } + router.push(`/home/${inputValue}`); + // } catch (error) { + // console.error('요청 중 오류 발생:', error); + // } }; return ( @@ -92,7 +106,11 @@ export default function SignNickName() { return (