From 15b022f2cc063e9e15e90cdfaec5939ac2ec5407 Mon Sep 17 00:00:00 2001 From: SEUNGHWA LEE Date: Sat, 3 May 2025 21:37:37 +0900 Subject: [PATCH 1/6] =?UTF-8?q?global.css=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/global.css | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/index.js | 1 + 2 files changed, 55 insertions(+) diff --git a/src/global.css b/src/global.css index e69de29..c3e4b88 100644 --- a/src/global.css +++ b/src/global.css @@ -0,0 +1,54 @@ +/* 기본 설정 */ +* { + box-sizing: border-box; + } + + body { + margin: 0; + padding: 0; + background-color: var(--bg-color); + font-family: "Noto Sans KR", sans-serif; + } + + /* CSS 변수 정의 */ + :root { + --bg-color: #f8ead2; + --accent-color: #dabec9; + --font-dark: #1c2c5b; + --font-light: #999; + --btn-radius: 12px; + } + + /* 버튼 스타일 */ + .button-basic { + padding: 12px 24px; + border: 1px solid #333; + background-color: white; + border-radius: var(--btn-radius); + font-size: 16px; + cursor: pointer; + } + + .button-accent { + background-color: var(--accent-color); + color: white; + border: none; + } + + /* 센터 정렬 */ + .flex-center { + display: flex; + justify-content: center; + align-items: center; + } + + /* 카드/박스 */ + .card { + background-color: white; + border: 1px solid #ccc; + border-radius: var(--btn-radius); + padding: 20px; + box-shadow: 2px 2px 6px rgba(0,0,0,0.1); + } + + \ No newline at end of file diff --git a/src/index.js b/src/index.js index d563c0f..5aa89e3 100755 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; +import './global.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; From 16eeca3536b84cf8faba95bcc1331c85fe64370d Mon Sep 17 00:00:00 2001 From: SEUNGHWA LEE Date: Sat, 3 May 2025 21:54:07 +0900 Subject: [PATCH 2/6] =?UTF-8?q?localhost=20=EC=97=90=EC=84=9C=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=ED=95=9C=20spring=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/axios/TokenInterceptor.js | 6 +++--- src/components/oauth/KakaoRedirectPage.js | 4 ++-- src/constants/api.js | 5 ++++- src/pages/KeywordSelectionPage.js | 6 +++--- src/pages/LoginPage.js | 4 ++-- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/axios/TokenInterceptor.js b/src/axios/TokenInterceptor.js index 3bfee88..65e38bb 100644 --- a/src/axios/TokenInterceptor.js +++ b/src/axios/TokenInterceptor.js @@ -1,5 +1,5 @@ import axios from 'axios'; -import {LOCAL_SPRING_API_URL} from "../constants/api"; +import { SPRING_API_URL } from "../constants/api"; const instance = axios.create(); @@ -49,7 +49,7 @@ instance.interceptors.response.use(async function (response) { } if (data.message === "유효하지 않은 토큰") { try { - const tokenReissueResult = await instance.post(`${LOCAL_SPRING_API_URL}/reissue`); + const tokenReissueResult = await instance.post(`${ SPRING_API_URL}/reissue`); if (tokenReissueResult.status === 200) { // 재발급 성공시 로컬스토리지에 토큰 저장 const accessToken = tokenReissueResult.headers['authorization'] || tokenReissueResult.headers['Authorization']; @@ -73,7 +73,7 @@ instance.interceptors.response.use(async function (response) { export const Logout = async () => { try { const token = localStorage.getItem('accessToken'); - await axios.post(`${LOCAL_SPRING_API_URL}/logout`, null, { + await axios.post(`${ SPRING_API_URL}/logout`, null, { headers: { Authorization: `Bearer ${token}` } diff --git a/src/components/oauth/KakaoRedirectPage.js b/src/components/oauth/KakaoRedirectPage.js index 90aa021..831a64d 100644 --- a/src/components/oauth/KakaoRedirectPage.js +++ b/src/components/oauth/KakaoRedirectPage.js @@ -1,7 +1,7 @@ import React, { useEffect, useRef } from "react"; import { useLocation, useNavigate } from "react-router-dom"; import instance from "../../axios/TokenInterceptor"; -import { LOCAL_SPRING_API_URL } from "../../constants/api"; +import { SPRING_API_URL } from "../constants/api"; const KakaoRedirectPage = () => { const location = useLocation(); @@ -12,7 +12,7 @@ const KakaoRedirectPage = () => { const handleOAuthKakao = async (code) => { try { const response = await instance.get( - `${LOCAL_SPRING_API_URL}/oauth/login/kakao?code=${code}` + `${ SPRING_API_URL}/oauth/login/kakao?code=${code}` ); if (response.data.isSuccess) { diff --git a/src/constants/api.js b/src/constants/api.js index 1820992..7e3dff9 100644 --- a/src/constants/api.js +++ b/src/constants/api.js @@ -1,2 +1,5 @@ export const LOCAL_SPRING_API_URL = "http://localhost:8080/api/spring"; -export const LOCAL_FASTAPI_API_URL = "http://localhost:8000/api/fastapi"; \ No newline at end of file +export const LOCAL_FASTAPI_API_URL = "http://localhost:8000/api/fastapi"; + +export const SPRING_API_URL = "https://www.humanicare.store/api/spring"; +export const FASTAPI_API_URL = "https://www.humanicare.store/api/fastapi"; \ No newline at end of file diff --git a/src/pages/KeywordSelectionPage.js b/src/pages/KeywordSelectionPage.js index 4ce71cc..39cd16e 100644 --- a/src/pages/KeywordSelectionPage.js +++ b/src/pages/KeywordSelectionPage.js @@ -5,7 +5,7 @@ import "react-time-picker/dist/TimePicker.css"; import "react-clock/dist/Clock.css"; import Logo from "../components/Logo"; import axios from "axios"; -import { LOCAL_SPRING_API_URL } from "../constants/api"; +import { SPRING_API_URL } from "../constants/api"; import { getAccessToken } from "../components/Header"; const keywords = { @@ -119,7 +119,7 @@ const KeywordSelectionPage = () => { try { const response = await axios.get( - `${LOCAL_SPRING_API_URL}/all-basic-schedules`, + `${SPRING_API_URL}/all-basic-schedules`, { headers: { Authorization: `Bearer ${token}` }, } @@ -202,7 +202,7 @@ const KeywordSelectionPage = () => { try { const response = await axios.post( - `${LOCAL_SPRING_API_URL}/basic-schedules`, + `${ SPRING_API_URL}/basic-schedules`, payload, { headers: { diff --git a/src/pages/LoginPage.js b/src/pages/LoginPage.js index 53d7793..b67d018 100644 --- a/src/pages/LoginPage.js +++ b/src/pages/LoginPage.js @@ -2,12 +2,12 @@ import React from "react"; import { useNavigate } from "react-router-dom"; import Button from "../components/Button"; import Logo from "../components/Logo"; -import { LOCAL_SPRING_API_URL } from "../constants/api"; +import { SPRING_API_URL } from "../constants/api"; const LoginPage = () => { const navigate = useNavigate(); const handleKakaoLogin = () => { - window.location.href = `${LOCAL_SPRING_API_URL}/oauth/kakao`; + window.location.href = `${ SPRING_API_URL}/oauth/kakao`; }; return ( From 0c25c414dfee2e440cad5a6ac594a53601a361db Mon Sep 17 00:00:00 2001 From: SEUNGHWA LEE Date: Sat, 3 May 2025 22:03:34 +0900 Subject: [PATCH 3/6] =?UTF-8?q?kakao=20redirect=20page=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/oauth/KakaoRedirectPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/oauth/KakaoRedirectPage.js b/src/components/oauth/KakaoRedirectPage.js index 831a64d..cc8fdcd 100644 --- a/src/components/oauth/KakaoRedirectPage.js +++ b/src/components/oauth/KakaoRedirectPage.js @@ -1,7 +1,7 @@ import React, { useEffect, useRef } from "react"; import { useLocation, useNavigate } from "react-router-dom"; import instance from "../../axios/TokenInterceptor"; -import { SPRING_API_URL } from "../constants/api"; +import { SPRING_API_URL } from "../../constants/api"; const KakaoRedirectPage = () => { const location = useLocation(); From a4386688872f03b57204bfce9df29b7b082ef817 Mon Sep 17 00:00:00 2001 From: SEUNGHWA LEE Date: Sun, 4 May 2025 12:19:43 +0900 Subject: [PATCH 4/6] =?UTF-8?q?LoginPage=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/KeywordSelectionPage.js | 12 +++++++- src/pages/LoginPage.js | 48 +++++++++++++++++-------------- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/src/pages/KeywordSelectionPage.js b/src/pages/KeywordSelectionPage.js index 39cd16e..c248de6 100644 --- a/src/pages/KeywordSelectionPage.js +++ b/src/pages/KeywordSelectionPage.js @@ -129,7 +129,17 @@ const KeywordSelectionPage = () => { console.log("JSON 파싱 직전"); console.log(schedules); - const updatedSelected = JSON.parse(JSON.stringify(selected)); + const updatedSelected = Object.fromEntries( + Object.entries(keywords).map(([category, list]) => [ + category, + Object.fromEntries( + list.map((keyword) => [ + keyword, + { selected: false, time: "08:00", days: [] } + ]) + ) + ]) + ); schedules.forEach(item => { const [categoryPrefix, keyword] = item.scheduleTitle.split("_"); diff --git a/src/pages/LoginPage.js b/src/pages/LoginPage.js index b67d018..ae28ac6 100644 --- a/src/pages/LoginPage.js +++ b/src/pages/LoginPage.js @@ -1,45 +1,49 @@ import React from "react"; -import { useNavigate } from "react-router-dom"; -import Button from "../components/Button"; -import Logo from "../components/Logo"; import { SPRING_API_URL } from "../constants/api"; +import Logo from "../components/Logo"; const LoginPage = () => { - const navigate = useNavigate(); const handleKakaoLogin = () => { - window.location.href = `${ SPRING_API_URL}/oauth/kakao`; + window.location.href = `${SPRING_API_URL}/oauth/kakao`; }; return ( -
+
-

회원가입하기

- -
); }; const styles = { container: { - display : "flex", - flexDirection : "column", - alignItems : "center", + display: "flex", + flexDirection: "column", + alignItems: "center", justifyContent: "center", height: "100vh", backgroundColor: "#FAE8D4", + padding: "0 20px", + textAlign: "center", }, - title: { - fontSize : "20px", - marginBottom: "20px", + message: { + fontSize: "18px", + color: "#333", + margin: "30px 0 40px", + lineHeight: "1.5", + fontWeight: "500", }, - signupButton: { - backgroundColor: "transparent", - border: "1px solid black", - padding: "10px 20px", - borderRadius: "8px", - fontSize: "16px", - marginBottom: "20px", + kakaoButton: { + width: "250px", cursor: "pointer", }, }; From f1af0ad7d7688198bc33f395d940222132405b9a Mon Sep 17 00:00:00 2001 From: SEUNGHWA LEE Date: Sun, 4 May 2025 12:53:19 +0900 Subject: [PATCH 5/6] =?UTF-8?q?voice=20=EB=85=B9=EC=9D=8C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/FinalPage.js | 2 - src/pages/VoiceTrainingPage.js | 134 +++++++++++++++++++++++++++++---- 2 files changed, 120 insertions(+), 16 deletions(-) diff --git a/src/pages/FinalPage.js b/src/pages/FinalPage.js index a8975c6..5d75a1e 100644 --- a/src/pages/FinalPage.js +++ b/src/pages/FinalPage.js @@ -1,11 +1,9 @@ // FinalPage.jsx import React, { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; import Logo from "../components/Logo"; import { Logout } from "../axios/TokenInterceptor"; // 실제 axios 파일 경로에 맞게 수정 const FinalPage = () => { - const navigate = useNavigate(); const [showConfirm, setShowConfirm] = useState(false); const handleLogoutClick = () => { diff --git a/src/pages/VoiceTrainingPage.js b/src/pages/VoiceTrainingPage.js index 3eefa4f..d3a4153 100644 --- a/src/pages/VoiceTrainingPage.js +++ b/src/pages/VoiceTrainingPage.js @@ -1,10 +1,71 @@ -import React from "react"; +import React, { useState, useRef } from "react"; import { useNavigate } from "react-router-dom"; -import MicButton from "../components/MicButton"; +import MicRecorder from "mic-recorder-to-mp3"; import Logo from "../components/Logo"; +import { FASTAPI_API_URL } from "../constants/api"; + +const recorder = new MicRecorder({ bitRate: 128 }); const VoiceTrainingPage = () => { const navigate = useNavigate(); + const [isRecording, setIsRecording] = useState(false); + const [blobURL, setBlobURL] = useState(""); + const [audioFile, setAudioFile] = useState(null); + const audioRef = useRef(null); + + const handleMicClick = async () => { + if (!isRecording) { + try { + await navigator.mediaDevices.getUserMedia({ audio: true }); + await recorder.start(); + setIsRecording(true); + } catch (err) { + alert("마이크 권한이 필요합니다."); + } + } else { + try { + const [buffer, blob] = await recorder.stop().getMp3(); + const file = new File(buffer, "voice.mp3", { + type: blob.type, + lastModified: Date.now(), + }); + setAudioFile(file); + setBlobURL(URL.createObjectURL(blob)); + setIsRecording(false); + } catch (e) { + console.error("녹음 종료 실패:", e); + setIsRecording(false); + } + } + }; + + const handleUpload = async () => { + if (!audioFile) { + alert("녹음된 음성이 없습니다."); + return; + } + + const formData = new FormData(); + formData.append("file", audioFile); + + try { + const res = await fetch(`${FASTAPI_API_URL}/upload`, { + method: "POST", + body: formData, + }); + const data = await res.json(); + console.log("업로드 성공:", data); + alert("업로드 완료!"); + } catch (error) { + console.error("업로드 실패:", error); + alert("업로드 실패"); + } + }; + + const handleReset = () => { + setBlobURL(""); + setAudioFile(null); + }; return (
@@ -13,7 +74,6 @@ const VoiceTrainingPage = () => {

(아래의 마이크 버튼을 누르고 텍스트를 읽어주세요.)

- Memo

오늘 하루는 어땠나요? 기분이 괜찮으신가요?

밖에 나가서 산책도 하셨어요?

@@ -23,7 +83,20 @@ const VoiceTrainingPage = () => {
- + + + {blobURL && ( +
+
+ )} ); }; @@ -14,6 +60,9 @@ const styles = { border: "none", cursor: "pointer", marginBottom: "20px", + display: "flex", + flexDirection: "column", + alignItems: "center" }, micIcon: { width: "60px",