diff --git a/package-lock.json b/package-lock.json index 2945440..b01ff11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "react-scripts": "5.0.1", "react-simple-image-slider": "^2.4.1", "reacticons": "^0.0.1", + "recoil": "^0.7.7", "redux": "^5.0.1", "web-vitals": "^2.1.4" }, @@ -9187,6 +9188,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/hamt_plus": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", + "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==" + }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -15175,6 +15181,25 @@ "node": ">=0.10.0" } }, + "node_modules/recoil": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz", + "integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==", + "dependencies": { + "hamt_plus": "1.0.2" + }, + "peerDependencies": { + "react": ">=16.13.1" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/recursive-readdir": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", diff --git a/package.json b/package.json index a66c221..5e373b0 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "react-scripts": "5.0.1", "react-simple-image-slider": "^2.4.1", "reacticons": "^0.0.1", + "recoil": "^0.7.7", "redux": "^5.0.1", "web-vitals": "^2.1.4" }, @@ -54,5 +55,6 @@ }, "devDependencies": { "tailwindcss": "^3.4.1" - } + }, + "proxy": "https://tukorea-dp.s3.amazonaws.com" } diff --git a/src/App.js b/src/App.js index 096e95b..cabc025 100644 --- a/src/App.js +++ b/src/App.js @@ -24,7 +24,7 @@ import Surveyresult from "./pages/Survey/SurveyResult.js"; import BeforeGame from "./pages/gamePages/BeforeGame"; import Gymnastics from "./pages/Gym/Gymnastics"; import GymnasticsVideo from "./pages/Gym/GymnasticsVideo"; -import MyPage from "./pages/MyPage"; +import MyPage from "./pages/MyPages/MyPage"; import CenterMap from "./pages/CenterPages/CenterMap"; import PrevSurveyResult from "./pages/Survey/PrevSurveyResult"; import SurveyError from "./pages/Survey/SurveyError"; @@ -34,11 +34,19 @@ import Login from "./pages/Login"; import Signup from "./pages/Signup"; import useAutoLogin from "./hooks/useAutoLogin"; import Explain from "./pages/Keyword/Explain.js"; -import UserUpdate from "./pages/UserUpdate.js"; -import DiaryManagement from "./pages/DiaryManagement.js"; +import UserUpdate, { + USER_UPDATE_PAGE_PATH, +} from "./pages/MyPages/UserUpdate.js"; +import DiaryManagement, { + DIARY_MANAGEMENT_PAGE_PATH, +} from "./pages/MyPages/DiaryManagement.js"; import HelpForAi from "./pages/ImageDiary/HelpForAi.js"; import ShowAiResult from "./pages/ImageDiary/ShowAiResult.js"; import Keyword from "./pages/Keyword/Keyword.js"; +import { + API_KEY_INPUT_PAGE_PATH, + APIKeyInput, +} from "./pages/MyPages/APIKeyInput"; function App() { let { loading } = useAutoLogin(); @@ -83,12 +91,17 @@ function App() { } /> } /> } /> - } /> + } /> } /> + } + /> { + return await this.get( + `/diary/check?userId=${userId}&year=${year}&month=${month}` + ); + }; } export default new DiaryController(); diff --git a/src/component/ImageDiary/AIModal.js b/src/component/ImageDiary/AIModal.js index 3732863..efdecaf 100644 --- a/src/component/ImageDiary/AIModal.js +++ b/src/component/ImageDiary/AIModal.js @@ -1,6 +1,5 @@ import React from "react"; import BackGroundSkyButton from "../BackGroundSkyButton"; -import { IoClose } from "react-icons/io5"; import { useNavigate } from "react-router-dom"; const AIModal = ({ onClose, content, keyword }) => { @@ -11,7 +10,7 @@ const AIModal = ({ onClose, content, keyword }) => { return (
diff --git a/src/component/ImageDiary/Canvas.js b/src/component/ImageDiary/Canvas.js index 9898a61..b706ac9 100644 --- a/src/component/ImageDiary/Canvas.js +++ b/src/component/ImageDiary/Canvas.js @@ -1,117 +1,216 @@ -import { IoTrashOutline } from "react-icons/io5"; -import { AiOutlineRollback } from "react-icons/ai"; -import { HiOutlinePaintBrush } from "react-icons/hi2"; -import { TfiEraser } from "react-icons/tfi"; -import { useEffect, useState } from "react"; -import { useDispatch, useSelector } from "react-redux"; -import { BRUSH_SIZE, SELECT_COLOR } from "../../redux/modules/ImageDiary"; - -const Canvas = ({ isVisible, canvasRef }) => { - const [getCtx, setGetCtx] = useState(null); //드로잉 영역 - const [painting, setPainting] = useState(false); //그리기 모드 - const [erasing, setErasing] = useState(false); //지우기 모드 - const [history, setHistory] = useState([]); //실행 취소 - const brushSize = useSelector((state) => state.ImageDiary.brushSize); - const selectedColor = useSelector((state) => state.ImageDiary.selectedColor); - const dispatch = useDispatch(); +import React, { useEffect, useRef, useState } from "react"; +import { imageState } from "../../recoil/keywordState"; +import { useRecoilState, useRecoilValue } from "recoil"; +import { brushSizeState, selectedColorState } from "../../recoil/canvasState"; +import { + BRUSH_MODE, + BrushButton, + ChangeBrushSizeRangeComp, + EraserButton, + SelectedColor, + TrashButton, + UnDoButton, +} from "./DrawTools"; +import { INPUT_END, useDrawInputEvents } from "./useDrawInputEvents"; + +export const CanvasList = ({ Keywords, canvasRefs, canvasBgRefs, index }) => { + return ( +
+ {Keywords.map((cur, i) => ( + + ))} +
+ ); +}; + +const Canvas = ({ + isVisible, + canvasRef, + canvasBgRef, + canvasKeyword, + arrIdx, +}) => { + const [screenInputMode, setScreenInputMode] = useState(INPUT_END); // 입력 모드 + const [drawMode, setDrawMode] = useState(BRUSH_MODE); // 그리기 모드 + const [history, setHistory] = useState([]); + //드로잉 영역 초기 세팅 - useEffect(() => { - const canvas = canvasRef.current; - const ctx = canvas.getContext("2d"); + let { clearCanvas } = useInitializeCanvas({ + canvasRef, + canvasBgRef, + canvasKeyword, + }); - ctx.lineJoin = "round"; //선이 꺽이는 부분의 스타일 - ctx.fillStyle = "white"; //캔버스 배경색 - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.lineWidth = 1; //선의 두께 - ctx.strokeStyle = "#000000"; //선의 색 - - dispatch({ type: SELECT_COLOR, selectedColor: "#000000" }); - dispatch({ type: BRUSH_SIZE, brushSize: 1 }); - setGetCtx(ctx); - clearCanvas(); - }, []); + const width = useGetScreenWidth(); + + return ( +
+ + + {/* 전체삭제, 뒤로가기, 브러쉬, 지우개 */} +
+ + + + + +
+ {/* 브러쉬 크기 조정 */} + +
+ ); +}; + +const DrawCanvas = ({ + canvasRef, + screenInputMode, + setScreenInputMode, + setHistory, + drawMode, + width, + arrIdx, +}) => { + const { + handleTouchStart, + handleTouchMove, + handleTouchEnd, + handleMouseDown, + handleMouseMove, + handleMouseUp, + } = useDrawInputEvents({ + canvasRef, + drawMode, + screenInputMode, + setScreenInputMode, + setHistory, + }); + + return ( + + ); +}; + +const BackGroundCanvas = ({ bgCanvasRef, width, canvasKeyword, arrIdx }) => { + const images = useRecoilValue(imageState); - // 브러쉬 크기, 펜 색상 변경 시 호출됨 useEffect(() => { - if (getCtx) { - getCtx.lineWidth = brushSize; - getCtx.strokeStyle = selectedColor; + const bgCtx = bgCanvasRef.current.getContext("2d"); + + const findAiImages = images.find((cur) => { + return cur.keyword === canvasKeyword; + }); + if (findAiImages) { + console.log(findAiImages.bgOpacity); + bgCtx.globalAlpha = findAiImages.bgOpacity; } - }, [brushSize, selectedColor]); - - //그리기, 지우기 기능 - const drawFn = (x, y) => { - if (!painting) { - getCtx.beginPath(); - getCtx.moveTo(x, y); - } else { - if (erasing) { - getCtx.clearRect( - x - brushSize, - y - brushSize, - brushSize * 2, - brushSize * 2 - ); - } else { - getCtx.lineTo(x, y); - getCtx.stroke(); + }, [images]); + + return ( + + ); +}; - //canvas 화면 전체 지우기 - const clearCanvas = () => { - const canvas = canvasRef.current; - const ctx = canvas.getContext("2d"); - ctx.clearRect(0, 0, canvas.width, canvas.height); - }; +const useInitializeCanvas = ({ canvasRef, canvasBgRef, canvasKeyword }) => { + const [brushSize, _] = useRecoilState(brushSizeState); //브러쉬 크기 + const [selectedColor, setSelectedColor] = useRecoilState(selectedColorState); //선택된 색상 + const aiImages = useRecoilValue(imageState); - // 캔버스 상태를 히스토리에 업데이트하는 함수 - const updateCanvasState = () => { + useEffect(() => { + //드로잉 영역 초기 세팅 const canvas = canvasRef.current; + const bgCanvas = canvasBgRef.current; const ctx = canvas.getContext("2d"); - const currentState = ctx.getImageData(0, 0, canvas.width, canvas.height); - setHistory((prevHistory) => [...prevHistory, currentState]); - }; + const bgCtx = canvasBgRef.current.getContext("2d"); - //실행 취소 - const unDo = () => { - const canvas = canvasRef.current; - const ctx = canvas.getContext("2d"); - if (history.length > 1) { - history.pop(); // 현재 상태 제거 - const prevState = history[history.length - 1]; - ctx.putImageData(prevState, 0, 0); - } else { - history.pop(); - clearCanvas(); + ctx.lineJoin = "round"; //선이 꺽이는 부분의 스타일 + ctx.lineWidth = brushSize; //선의 두께 + ctx.strokeStyle = selectedColor; //선의 색 + + setSelectedColor("#000000"); + + bgCtx.fillStyle = "white"; + bgCtx.fillRect(0, 0, canvas.width, canvas.height); + + // ai 가 제시한 image url 찾기 + let findAiSuggest = aiImages.find((i) => i.keyword === canvasKeyword); + + // 있다면 이미지를 배경에 그려줌 + if (findAiSuggest) { + console.log(findAiSuggest); + const backgroundImage = new Image(); + backgroundImage.crossOrigin = "anonymous"; + backgroundImage.src = + findAiSuggest.imageUrl + `?v=${new Date().getTime()}`; + backgroundImage.onload = () => { + bgCtx.drawImage(backgroundImage, 0, 0, bgCanvas.width, bgCanvas.height); + canvasBgRef.current.globalAlpha = findAiSuggest.bgOpacity; + }; } - }; + }, []); - // 터치 이벤트 핸들러 함수 - const handleTouchStart = (e) => { - const touch = e.touches[0]; - const rect = canvasRef.current.getBoundingClientRect(); - const x = touch.clientX - rect.left + window.scrollX; - const y = touch.clientY - rect.top + window.scrollY; - setPainting(true); - drawFn(x, y); + //canvas 화면 전체 지우기 + const clearCanvas = () => { + const canvas = canvasRef.current; + const ctx = canvas.getContext("2d"); + ctx.clearRect(0, 0, canvas.width, canvas.height); }; - const handleTouchMove = (e) => { - const touch = e.touches[0]; - const rect = canvasRef.current.getBoundingClientRect(); - const x = touch.clientX - rect.left + window.scrollX; - const y = touch.clientY - rect.top + window.scrollY; - drawFn(x, y); - }; + return { clearCanvas }; +}; - const handleTouchEnd = () => { - setPainting(false); - updateCanvasState(); - }; +const useGetScreenWidth = () => { // 캔버스 크기를 반응형으로 조절하기 위해 화면의 크기를 받아와서 조정 - const [width, setWidth] = useState(); + const [width, setWidth] = useState(window.innerWidth); const resizeListener = () => { const size = window.innerWidth > 450 ? 450 : window.innerWidth; setWidth(Math.ceil(size * 0.9)); @@ -123,110 +222,7 @@ const Canvas = ({ isVisible, canvasRef }) => { return () => { window.removeEventListener("resize", resizeListener); }; - }, []); - // 마우스 클릭 이벤트 핸들러 함수 - const handleMouseDown = (e) => { - const rect = canvasRef.current.getBoundingClientRect(); - const x = e.clientX - rect.left + window.scrollX; - const y = e.clientY - rect.top + window.scrollY; - setPainting(true); - drawFn(x, y); - }; - - const handleMouseMove = (e) => { - if (painting) { - const rect = canvasRef.current.getBoundingClientRect(); - const x = e.clientX - rect.left + window.scrollX; - const y = e.clientY - rect.top + window.scrollY; - drawFn(x, y); - } - }; - - const handleMouseUp = () => { - setPainting(false); - updateCanvasState(); - }; + }, [window.innerWidth]); - //브러쉬 크기 변경 - const changeLineWidth = (event) => { - console.log(brushSize); - dispatch({ type: BRUSH_SIZE, brushSize: parseInt(event.target.value, 10) }); - }; - - useEffect(() => { - console.log("painting: ", painting, " erasing: ", erasing); - }, [painting, erasing]); - - return ( -
- - {/* 전체삭제, 뒤로가기, 브러쉬, 지우개 */} -
- - -
- { - setErasing(false); - }} - color={erasing ? "black" : "red"} - /> - setErasing(true)} - color={erasing ? "red" : "black"} - /> -
- {/* 브러쉬 크기 조정 */} -
-

- 브러쉬 크기 {brushSize} -

- -
-
- ); + return width; }; - -export default Canvas; diff --git a/src/component/ImageDiary/Color.js b/src/component/ImageDiary/Color.js index 97078bb..d0f4cff 100644 --- a/src/component/ImageDiary/Color.js +++ b/src/component/ImageDiary/Color.js @@ -1,11 +1,10 @@ import React from "react"; -import { connect, useDispatch } from "react-redux"; -import { SELECT_COLOR } from "../../redux/modules/ImageDiary"; +import { useSetRecoilState } from "recoil"; +import { selectedColorState } from "../../recoil/canvasState"; const Color = ({ color }) => { - const dispatch = useDispatch(); - // const selectedcolor = useSelector((state) => state.ImageDiary.selectedColor); const borderColor = color === "#FFFFFF" ? "1px solid black" : "none"; + const setSelectedColor = useSetRecoilState(selectedColorState); return (
{ backgroundColor: color, border: borderColor, }} - onClick={() => dispatch({ type: SELECT_COLOR, selectedColor: color })} + onClick={() => setSelectedColor(color)} >
); }; diff --git a/src/component/ImageDiary/DrawTools.js b/src/component/ImageDiary/DrawTools.js new file mode 100644 index 0000000..38bacf2 --- /dev/null +++ b/src/component/ImageDiary/DrawTools.js @@ -0,0 +1,103 @@ +import { IoTrashOutline } from "react-icons/io5"; +import { AiOutlineRollback } from "react-icons/ai"; +import { HiOutlinePaintBrush } from "react-icons/hi2"; +import { TfiEraser } from "react-icons/tfi"; +import React, { useEffect } from "react"; +import { useRecoilState } from "recoil"; +import { brushSizeState, selectedColorState } from "../../recoil/canvasState"; + +export const BRUSH_MODE = "brush"; +export const ERASER_MODE = "eraser"; + +export const TrashButton = ({ clearCanvas }) => { + return ( + + ); +}; + +export const UnDoButton = ({ canvasRef, history, clearCanvas }) => { + //실행 취소 + const unDo = () => { + const canvas = canvasRef.current; + const ctx = canvas.getContext("2d"); + + history.pop(); // 현재 상태 제거 + + if (history.length > 1) { + const prevState = history[history.length - 1]; + ctx.putImageData(prevState, 0, 0); + return; + } + + clearCanvas(); + }; + + return ; +}; + +export const SelectedColor = () => { + const [selectedColor, _] = useRecoilState(selectedColorState); //선택된 색상 + return ( +
+ ); +}; + +export const BrushButton = ({ drawMode, setDrawMode }) => { + return ( + setDrawMode(BRUSH_MODE)} + color={drawMode === BRUSH_MODE ? "red" : "black"} + /> + ); +}; + +export const EraserButton = ({ drawMode, setDrawMode }) => { + return ( + setDrawMode(ERASER_MODE)} + color={drawMode === ERASER_MODE ? "red" : "black"} + /> + ); +}; + +export const ChangeBrushSizeRangeComp = () => { + const [brushSize, setBrushSize] = useRecoilState(brushSizeState); //브러쉬 크기 + useEffect(() => { + setBrushSize(brushSize); + }, []); + + const changeLineWidth = (event) => { + setBrushSize(parseInt(event.target.value)); + }; + + return ( +
+

+ 브러쉬 크기 {brushSize} +

+ +
+ ); +}; diff --git a/src/component/ImageDiary/Palette.js b/src/component/ImageDiary/Palette.js index 754eb56..7550654 100644 --- a/src/component/ImageDiary/Palette.js +++ b/src/component/ImageDiary/Palette.js @@ -16,11 +16,16 @@ const Palette = ({}) => { "#E500FF", ]; return ( -
- {colors.map((item, idx) => ( - - ))} -
+ <> +

+ 옆으로 넘겨서 더 많은 색상을 볼 수 있어요! +

+
+ {colors.map((item, idx) => ( + + ))} +
+ ); }; diff --git a/src/component/ImageDiary/useDrawInputEvents.js b/src/component/ImageDiary/useDrawInputEvents.js new file mode 100644 index 0000000..100743e --- /dev/null +++ b/src/component/ImageDiary/useDrawInputEvents.js @@ -0,0 +1,117 @@ +import { BRUSH_MODE, ERASER_MODE } from "./DrawTools"; +import { useRecoilState } from "recoil"; +import { brushSizeState, selectedColorState } from "../../recoil/canvasState"; +import { useEffect, useState } from "react"; + +export const INPUT_START = "start"; +export const INPUT_MOVE = "move"; +export const INPUT_END = "end"; + +export const useDrawInputEvents = ({ + canvasRef, + screenInputMode, + drawMode, + setScreenInputMode, + setHistory, +}) => { + const [brushSize, _] = useRecoilState(brushSizeState); //브러쉬 크기 + const [selectedColor, __] = useRecoilState(selectedColorState); //선택된 색상 + + //브러쉬 크기, 펜 색상 변경 시 호출됨 + useEffect(() => { + let canvas = canvasRef.current; + let ctx = canvas.getContext("2d"); + ctx.lineWidth = brushSize; + ctx.strokeStyle = selectedColor; + }, [brushSize, selectedColor]); + + const drawFn = (x, y) => { + let canvas = canvasRef.current; + let ctx = canvas.getContext("2d"); + // 그리기전이라면 그리기 시작 + if (screenInputMode === INPUT_END) { + ctx.beginPath(); + ctx.moveTo(x, y); + return; + } + + // 그리기 모드에 따라 그리기 또는 지우기 + if (drawMode === ERASER_MODE) { + ctx.clearRect(x - brushSize, y - brushSize, brushSize * 2, brushSize * 2); + } + + if (drawMode === BRUSH_MODE) { + ctx.lineTo(x, y); + ctx.stroke(); + } + }; + + // 캔버스 상태를 히스토리에 업데이트하는 함수 + const updateCanvasState = () => { + let canvas = canvasRef.current; + let ctx = canvas.getContext("2d"); + const currentState = ctx.getImageData(0, 0, canvas.width, canvas.height); + setHistory((prevHistory) => [...prevHistory, currentState]); + }; + + // 터치 이벤트 핸들러 함수 + const handleTouchStart = (e) => { + let canvas = canvasRef.current; + let rect = canvas.getBoundingClientRect(); + const touch = e.touches[0]; + const x = touch.clientX - rect.left + window.scrollX; + const y = touch.clientY - rect.top + window.scrollY; + setScreenInputMode(INPUT_START); + drawFn(x, y); + }; + + const handleTouchMove = (e) => { + let canvas = canvasRef.current; + let rect = canvas.getBoundingClientRect(); + const touch = e.touches[0]; + const x = touch.clientX - rect.left + window.scrollX; + const y = touch.clientY - rect.top + window.scrollY; + setScreenInputMode(INPUT_MOVE); + drawFn(x, y); + }; + + const handleTouchEnd = () => { + setScreenInputMode(INPUT_END); + updateCanvasState(); + }; + + // 마우스 클릭 이벤트 핸들러 함수 + const handleMouseDown = (e) => { + let canvas = canvasRef.current; + let rect = canvas.getBoundingClientRect(); + const x = e.clientX - rect.left + window.scrollX; + const y = e.clientY - rect.top + window.scrollY; + setScreenInputMode(INPUT_START); + drawFn(x, y); + }; + + const handleMouseMove = (e) => { + if (screenInputMode === INPUT_END) return; + + let canvas = canvasRef.current; + let rect = canvas.getBoundingClientRect(); + const x = e.clientX - rect.left + window.scrollX; + const y = e.clientY - rect.top + window.scrollY; + setScreenInputMode(INPUT_MOVE); + drawFn(x, y); + }; + + const handleMouseUp = () => { + setScreenInputMode(INPUT_END); + updateCanvasState(); + }; + + return { + handleTouchStart, + handleTouchMove, + handleTouchEnd, + handleMouseDown, + handleMouseMove, + handleMouseUp, + }; +}; diff --git a/src/component/MyPageList.js b/src/component/MyPageItem.js similarity index 75% rename from src/component/MyPageList.js rename to src/component/MyPageItem.js index fcd2e20..dbe65ab 100644 --- a/src/component/MyPageList.js +++ b/src/component/MyPageItem.js @@ -1,4 +1,4 @@ -const MyPageList = ({ src, text, onClick }) => { +const MyPageItem = ({ src, text, onClick }) => { return (
{
); }; -export default MyPageList; +export default MyPageItem; diff --git a/src/index.js b/src/index.js index f542046..95f9d43 100644 --- a/src/index.js +++ b/src/index.js @@ -5,9 +5,9 @@ import "./index.css"; import store from "./redux/config/configStore"; import { Provider } from "react-redux"; import { BrowserRouter } from "react-router-dom"; +import { RecoilRoot } from "recoil"; const root = ReactDOM.createRoot(document.getElementById("root")); - const render = () => { const state = store.getState(); const fontSize = state.fontSize; @@ -15,11 +15,13 @@ const render = () => { document.documentElement.style.fontSize = fontSize; root.render( - - - - - + + + + + + + ); }; diff --git a/src/pages/Calendar/Calendar.js b/src/pages/Calendar/Calendar.js index d6475e1..72092c5 100644 --- a/src/pages/Calendar/Calendar.js +++ b/src/pages/Calendar/Calendar.js @@ -13,11 +13,13 @@ import { import { CHANGE_DIARY } from "../../redux/modules/DiaryInfo.js"; import DiaryController from "../../api/diary.controller.js"; import { SET_PAGENAME } from "../../redux/modules/PageName.js"; +import diaryController from "../../api/diary.controller.js"; const Calendar = () => { const location = useLocation(); const dispatch = useDispatch(); const [isOpen, setIsOpen] = useState(false); + const [mark, setMark] = useState([]); useEffect(() => { // 페이지 이름 설정 @@ -42,6 +44,7 @@ const Calendar = () => { day: currentDate.getDate(), }); } + checkDiaryList(); }, [location.state, dispatch]); const navigate = useNavigate(); @@ -68,6 +71,25 @@ const Calendar = () => { ); }; + //일기 유무 리스트 가져오기 + const checkDiaryList = async () => { + try { + const response = await diaryController.checkDiaryList({ + userId, + year: reduxYear, + month: reduxMonth, + }); + const { result } = response.data; + const currentMonthData = result[`${reduxYear}-${reduxMonth}`]; + const filteredDates = Object.keys(currentMonthData).filter( + (date) => currentMonthData[date].isExist + ); + setMark(filteredDates); + } catch (error) { + console.error("일기 유무 리스트 가져오기 중 오류", error); + setMark([]); + } + }; //선택한 날의 일기 가져오기 const getDiary = async () => { setIsGetDiaryComplete(false); @@ -89,7 +111,6 @@ const Calendar = () => { userId: userId, date: dateFormat(), }); - console.log(res.data); //일기가 존재하지 않음 if (res.data.result.length == 0) { @@ -105,8 +126,9 @@ const Calendar = () => { type: CHANGE_DIARY, diaryId: diaryInfo.diaryId, content: diaryInfo.content, - imgUrl: diaryInfo.imgUrl, date: diaryInfo.createDate, + keywords: diaryInfo.keywords, + imgUrl: diaryInfo.imgUrl, }); setIsDiaryExist(true); } catch (error) { @@ -116,6 +138,10 @@ const Calendar = () => { setIsGetDiaryComplete(true); }; + useEffect(() => { + // 첫 렌더링 시와 연도, 달이 변경될 때마다 일기 유무 리스트를 가져옵니다. + checkDiaryList(); + }, [reduxYear, reduxMonth]); useEffect(() => { // 일기 데이터 가져오기 getDiary(); @@ -124,11 +150,13 @@ const Calendar = () => { // 이전 달로 이동 const prevMonth = () => { dispatch({ type: CHANGE_MONTH, number: -1 }); + setMark([]); }; // 다음 달로 이동 const nextMonth = () => { dispatch({ type: CHANGE_MONTH, number: 1 }); + setMark([]); }; // 현재 달의 첫째 날의 요일을 반환합니다. (0: 일요일, 1: 월요일, ...) @@ -176,34 +204,36 @@ const Calendar = () => { days.forEach((selectDay, index) => { const isSelected = selectDay !== "" && reduxDay === selectDay; + // 현재 선택한 날짜의 날짜 문자열을 생성합니다. + const dateStr = `${selectDay.toString()}`; + + // mark 배열에 포함된 숫자들과 일치하는지 확인합니다. + const isDiaryExistForDay = mark.includes(dateStr); + + let cellClassNames = ""; if (index % 7 !== 0) { - cells.push( - handleDateClick(selectDay)} - > - {selectDay} - - ); + cellClassNames = `${selectDay === "" ? "empty" : ""} ${isSelected ? "selected" : ""}`; } else { rows.push(cells); cells = []; - cells.push( - handleDateClick(selectDay)} - > - {selectDay} - - ); + cellClassNames = `${selectDay === "" ? "empty" : ""} ${isSelected ? "selected" : ""}`; } + cells.push( + handleDateClick(selectDay)} + > + {selectDay} + {isDiaryExistForDay && ( +
+
+
+ )} + + ); + if (index === days.length - 1) { rows.push(cells); } diff --git a/src/pages/Calendar/Diary.js b/src/pages/Calendar/Diary.js index f8aa22f..4264331 100644 --- a/src/pages/Calendar/Diary.js +++ b/src/pages/Calendar/Diary.js @@ -5,18 +5,20 @@ import DiaryController from "../../api/diary.controller.js"; import { useDispatch, useSelector } from "react-redux"; import Loading from "../../component/Loading.js"; import { CHANGE_DIARY } from "../../redux/modules/DiaryInfo.js"; +import SimpleImageSlider from "react-simple-image-slider"; -const Diary = ({ data }) => { +const Diary = () => { const navigate = useNavigate(); const dispatch = useDispatch(); const textRef = useRef(); - const [isImage, setIsImage] = useState(false); const [isSaving, setIsSaving] = useState(false); - const { diaryId, content, date, imgUrl } = useSelector( + const { diaryId, content, date, keywords, imgUrl } = useSelector( (state) => state.DiaryInfo ); + const [diaryImages, setDiaryImages] = useState([]); + const userId = useSelector((state) => state.UserInfo.userId); const [newContent, setNewContent] = useState(content); @@ -26,7 +28,6 @@ const Diary = ({ data }) => { textarea.style.height = "auto"; // Reset height to auto textarea.style.height = textarea.scrollHeight + "px"; }, []); - useEffect(() => { handleResizeHeight(); }, [handleResizeHeight]); @@ -57,8 +58,9 @@ const Diary = ({ data }) => { type: CHANGE_DIARY, diaryId: diaryInfo.diaryId, content: diaryInfo.content, - imgUrl: diaryInfo.imgUrl, date: diaryInfo.createDate, + keywords: diaryInfo.keywords, + imgUrl: diaryInfo.imgUrl, }); setIsSaving(false); } catch (error) { @@ -80,26 +82,51 @@ const Diary = ({ data }) => { } }; + useEffect(() => { + if (imgUrl) { + setDiaryImages([imgUrl]); + return; + } + + if (keywords.length === 0 || keywords[0].imgUrl == null) { + setDiaryImages([]); + return; + } + + setDiaryImages( + keywords.map((keyword) => { + return keyword.imgUrl; + }) + ); + }, []); + return (
{isSaving ? : null} -
- {imgUrl !== null ? ( - - ) : ( +
+ {diaryImages.length === 0 && (
{ - navigate("/draw", { state: data }); + navigate("/draw"); }} > 그림 그리기
)} + {diaryImages.length > 0 && ( + + )}
{ const [showDiary, setShowDiary] = useState(false); @@ -14,8 +16,8 @@ const DiaryEdit = ({ isOpen }) => { (state) => state.DiaryInfo ); - const [data, setData] = useState([]); - + const setKeywordState = useSetRecoilState(keywordState); + const resetKeywordState = useSetRecoilState(keywordState); const fetchData = async () => { try { const response = await DiaryController.getQuiz({ @@ -45,6 +47,7 @@ const DiaryEdit = ({ isOpen }) => { const navigate = useNavigate(); useEffect(() => { + resetKeywordState(); setShowDiary(false); }, [diaryId]); @@ -56,15 +59,11 @@ const DiaryEdit = ({ isOpen }) => { try { const response = await keywordController.getKeyword(diaryId); const { isSuccess, result } = response.data; + setKeywordState(result); if (result.length == 0) { return; } - setData( - result.map((item, index) => { - return { keywordId: item.keywordId, keyword: item.keyword }; - }) - ); console.log(result); } catch (error) { console.error("Error fetching quiz data:", error); @@ -111,7 +110,7 @@ const DiaryEdit = ({ isOpen }) => { 일기닫기
- {showDiary && } + {showDiary && }
); }; diff --git a/src/pages/Calendar/Draw.js b/src/pages/Calendar/Draw.js index 2e8130b..ad8447a 100644 --- a/src/pages/Calendar/Draw.js +++ b/src/pages/Calendar/Draw.js @@ -1,262 +1,301 @@ import React, { useEffect, useRef, useState } from "react"; -import { useDispatch } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import { SET_PAGENAME } from "../../redux/modules/PageName"; -import { useLocation, useNavigate } from "react-router-dom"; import { IoIosArrowBack, IoIosArrowDown, - IoIosArrowUp, IoIosArrowForward, + IoIosArrowUp, } from "react-icons/io"; import { AiOutlineExclamationCircle } from "react-icons/ai"; -import Canvas from "../../component/ImageDiary/Canvas"; +import { CanvasList } from "../../component/ImageDiary/Canvas"; import Palette from "../../component/ImageDiary/Palette"; import Button from "../../component/Button"; import keywordController from "../../api/keyword.controller"; import imgController from "../../api/img.controller"; import InfiniteScroll from "../../component/ImageDiary/InfiniteScroll"; -import SkyButton from "../../component/BackGroundSkyButton"; import AIModal from "../../component/ImageDiary/AIModal"; +import { useRecoilState } from "recoil"; +import { keywordState } from "../../recoil/keywordState"; +import diaryController from "../../api/diary.controller"; +import { useNavigate } from "react-router-dom"; +import DiaryController from "../../api/diary.controller"; + const Draw = () => { const dispatch = useDispatch(); - useEffect(() => { - dispatch({ type: SET_PAGENAME, pageName: "그림일기" }); - }, []); - const location = useLocation(); - const navigate = useNavigate(); const [index, setIndex] = useState(0); + const [keywordInfo, _] = useRecoilState(keywordState); //키워드 - const [keyword, setKeyword] = useState([]); - //키워드 아이디 - const [keywordId, setKeywordId] = useState([]); + const [keyword, setKeyword] = useState( + keywordInfo.map((item) => item.keyword) + ); //캔버스들 저장 - const canvasRefs = useRef({}); - //키워드별 사진 저장(base64 형태) -> photoedit으로 넘겨줌 - const [savedImages, setSavedImages] = useState([]); - //키워드가 존재하는 지의 여부 - const [isKeywordExist, setIsKeywordExist] = useState(); - //다른 사람 그림 보기 - const [showOtherDraw, setShowOtherDraw] = useState(false); - const [isModalOpen, setIsModalOpen] = useState(false); - - //다른 사람 그림 보기 클릭 - const handleClickShowDraw = () => { - setShowOtherDraw(!showOtherDraw); - console.log(showOtherDraw); - }; - - const handleModal = () => { - setIsModalOpen(!isModalOpen); - }; + const canvasRefs = useRef(keywordInfo.map((_) => React.createRef())); + const canvasBgRefs = useRef(keywordInfo.map((_) => React.createRef())); useEffect(() => { + dispatch({ type: SET_PAGENAME, pageName: "그림일기" }); + //키워드가 없는 경우 - if (location.state.length == 0) { + if (keywordInfo.length === 0) { setKeyword(["자유롭게 그려주세요"]); - setIsKeywordExist(false); - } - - //키워드가 있는 경우 - if (location.state.length !== 0) { - setIsKeywordExist(true); - setKeyword(location.state.map((item) => item.keyword)); - setKeywordId(location.state.map((item) => item.keywordId)); + canvasRefs.current.push(React.createRef()); + canvasBgRefs.current.push(React.createRef()); } }, []); + return ( +
+ + {/* AI 도움 */} + + {/* 다른 사람의 키워드 사진 띄워줄 부분 */} + + {/* Canvas */} + + {/* 색상팔레트 */} + + {/* 저장 버튼 */} + +
+ ); +}; + +const KeywordNavigation = ({ keywords, index, setIndex }) => { + const isFirstIndex = index === 0; + const isLastIndex = index === keywords.length - 1; + //다음 키워드 제시 const getNextKeyword = () => { - if (index == keyword.length - 1) return; + if (index === keywords.length - 1) return; setIndex((index) => index + 1); }; //이전 키워드 제시 const getPrevKeyword = () => { - if (index == 0) return; + if (keywords === 0) return; setIndex((index) => index - 1); }; - // 캔버스 렌더링 - const renderCanvas = () => { - canvasRefs.current = keyword.map(() => React.createRef()); - return keyword.map((cur, i) => ( - + - )); - }; - - const renderPhoto = () => { - return keyword.map((cur, i) => ( - - )); - }; - - // 키워드 별 사진 저장(base64 형태) - const base64Images = async () => { - const images = await Promise.all( - canvasRefs.current.map(async (canvasRef, i) => { - const image = await canvasRef.current.toDataURL(); - return image; - }) - ); - setSavedImages(images); - }; - - useEffect(() => { - // 3. savedImages의 값을 photodiary 페이지에 넘겨주면서 페이지를 불러옴 - if (savedImages.length > 0) { - navigate("/photoedit", { state: savedImages }); - } - }, [savedImages]); - - //키워드 별 사진을 서버로 전송 - const postImg = async () => { - try { - const requests = canvasRefs.current.map(async (canvasRef, i) => { - const formData = new FormData(); - await new Promise((resolve, reject) => { - canvasRef.current.toBlob((blob) => { - if (blob) { - formData.append("image", blob, i + "image.png"); - resolve(); - } else { - reject(new Error("Failed to convert canvas to blob.")); - } - }); - }); - return imgController.uploadImg(formData); - }); - - const responses = await Promise.all(requests); - const photo = responses.map((res) => res.data.result.imageUrl); - return photo; // 이미지 URL 배열 반환 - } catch (err) { - console.log(err); - } - }; - - //키워드 별 이미지 저장 - const saveKeywordImg = async (photos) => { - try { - const requests = keywordId.map(async (keyId, i) => { - console.log(photos[i]); - return keywordController.saveKeywordImg(keyId, { - imgUrl: photos[i], - }); - }); - const responses = await Promise.all(requests); - console.log(responses); - } catch (err) { - console.log(err); - } - }; +

{keywords[index]}

+ +
+ ); +}; - const saveImage = async () => { - try { - const photos = await postImg(); // postImg 함수의 반환값을 받아옴 - if (!isKeywordExist) return; - console.log(photos); - await saveKeywordImg(photos); // saveKeywordImg 함수에 이미지 URL 배열 전달 - } catch (err) { - console.log(err); - } - }; +const AISuggestionTextAndIconAndModal = ({ keywords, index }) => { + const [isModalOpen, setIsModalOpen] = useState(false); - const handleClickAIButton = () => { - navigate("/draw/help", { - state: { - keyword: keyword[index], - }, - }); + const handleModal = () => { + setIsModalOpen(!isModalOpen); }; return ( -
- {/* 키워드 */} -
- {keyword.length > 0 ? ( - index === 0 ? ( -
- ) : ( - - ) - ) : null} -

{keyword[index]}

- {keyword.length > 0 && index !== keyword.length - 1 && ( - - )} - {keyword.length > 0 && index === keyword.length - 1 && ( -
- )} -
- {/* AI 도움 */} + <> + {isModalOpen && ( + + )}

혹시 그림 그리기 어려우신가요?

handleModal()} />
+ + ); +}; + +const ShowOtherDrawSlider = ({ keywords, index, isKeywordExist }) => { + //다른 사람 그림 보기 + const [showOtherDraw, setShowOtherDraw] = useState(false); + + //다른 사람 그림 보기 클릭 + const handleClickShowDraw = () => { + setShowOtherDraw(!showOtherDraw); + }; + + if (!isKeywordExist) { + return null; + } + + return ( + <>
handleClickShowDraw()} > 다른 사람 그림 보기 - {showOtherDraw ? ( - - ) : ( - - )} + {showOtherDraw && } + {!showOtherDraw && }
- {/* 사진 띄워줄 부분 */} - {isKeywordExist && showOtherDraw && renderPhoto()} - {/* Canvas */} -
- {renderCanvas()} -
- {/* 색상팔레트 */} -

- 옆으로 넘겨서 더 많은 색상을 볼 수 있어요! -

- -
- {keyword.length - 1 === index || keyword.length === 0 ? ( -
- {isModalOpen && ( - + ); +}; + +const SaveImageButton = ({ index, canvasRefs, canvasBgRefs, keywordInfo }) => { + const isKeywordExist = keywordInfo.length !== 0; + const { saveAllCanvasDrawToKeywordImage } = useSaveCanvasImage({ + canvasRefs, + canvasBgRefs, + keywordInfo, + isKeywordExist, + }); + + return ( +
+ {canvasRefs.current.length - 1 === index && ( +
); }; +const useSaveCanvasImage = ({ + canvasRefs, + canvasBgRefs, + keywordInfo, + isKeywordExist, +}) => { + let navigate = useNavigate(); + const diaryId = useSelector((state) => state.DiaryInfo.diaryId); + //키워드 별 사진을 서버로 전송 + const saveAllCanvasDrawToKeywordImage = async () => { + const uploadCanvasDrawRequests = []; + + for (let i = 0; i < canvasRefs.current.length; i++) { + uploadCanvasDrawRequests.push( + uploadCanvasImageToUrl({ + canvas: canvasRefs.current[i].current, + bgCanvas: canvasBgRefs.current[i].current, + }) + ); + } + + let uploadedImageUrls = await Promise.all(uploadCanvasDrawRequests); + + let saveKeywordRequests = []; + + for (let i = 0; i < canvasRefs.current.length; i++) { + saveKeywordRequests.push( + saveKeywordImageUrl({ + keywordInfo: keywordInfo[i], + uploadedImageUrl: uploadedImageUrls[i], + }) + ); + } + + await Promise.all(saveKeywordRequests); + navigate("/calendar"); + }; + + const uploadCanvasImageToUrl = async ({ canvas, bgCanvas }) => { + const formData = new FormData(); + + let resultCanvas = mergeCanvas({ canvas, bgCanvas }); + const blob = await canvasToBlob({ canvas: resultCanvas }); + + formData.append("image", blob, "image.png"); + + const response = await imgController.uploadImg(formData); + + return response.data.result.imageUrl; + }; + + const saveKeywordImageUrl = async ({ keywordInfo, uploadedImageUrl }) => { + //키워드가 있는 경우 키워드별 이미지 저장 + if (isKeywordExist) { + return await keywordController.saveKeywordImg(keywordInfo.keywordId, { + imgUrl: uploadedImageUrl, + }); + } + // 키워드가 없는 경우 다이어리 이미지 저장 + else { + return await DiaryController.saveDiaryImg(diaryId, { + imgUrl: uploadedImageUrl, + }); + } + }; + + return { saveAllCanvasDrawToKeywordImage }; +}; + +const mergeCanvas = ({ canvas, bgCanvas }) => { + // resultCanvas 컴포넌트 생성 + const resultCanvas = document.createElement("canvas"); + resultCanvas.width = canvas.width; + resultCanvas.height = canvas.height; + + const resultCtx = resultCanvas.getContext("2d"); + + resultCtx.drawImage(bgCanvas, 0, 0, canvas.width, canvas.height); + resultCtx.drawImage(canvas, 0, 0, canvas.width, canvas.height); + resultCtx.globalAlpha = 1.0; + + return resultCanvas; +}; + +const canvasToBlob = async ({ canvas }) => { + return new Promise((resolve, reject) => { + canvas.toBlob((blob) => { + console.log(blob); + if (blob) { + resolve(blob); + } else { + reject(); + } + }); + }); +}; + export default Draw; diff --git a/src/pages/DiaryManagement.js b/src/pages/DiaryManagement.js deleted file mode 100644 index 95ae4f0..0000000 --- a/src/pages/DiaryManagement.js +++ /dev/null @@ -1,180 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { useDispatch, useSelector } from "react-redux"; -import { SET_PAGENAME } from "../redux/modules/PageName"; -import { useNavigate, useLocation } from "react-router-dom"; -import Button from "../component/Button"; -import diaryController from "../api/diary.controller"; -import queryString from "query-string"; - -function DiaryListCop({ diaryDates }) { - const navigate = useNavigate(); - const formattedDates = diaryDates.map((dateStr) => { - const date = new Date(dateStr); - const formattedDate = `${date.getFullYear()}년 ${date.getMonth() + 1}월 ${date.getDate()}일`; - return formattedDate; - }); - const handleClick = (index) => { - navigate("/calendar", { state: diaryDates[index] }); - }; - return ( -
- {diaryDates.length > 0 ? ( -
- {formattedDates.map((date, index) => ( -
handleClick(index)} - key={index} - > -
- {date} -
-
- 일기 확인하기 {">"} -
-
- ))} -
- ) : ( -
- 일기가 존재하지 않습니다. -
- )} -
- ); -} - -function SearchDiary({ id, setDiaries }) { - const [startDate, setStartDate] = useState(""); - const [endDate, setEndDate] = useState(""); - const [option, setOption] = useState(""); - - const navigate = useNavigate(); - const location = useLocation(); - - const query = queryString.parse(location.search); - - const currentDate = new Date(); - const currentYear = currentDate.getFullYear(); - const currentMonth = String(currentDate.getMonth() + 1).padStart(2, "0"); - const currentDay = String(currentDate.getDate()).padStart(2, "0"); - - const previousDate = new Date(currentDate); - previousDate.setDate(currentDate.getDate() - 1); - const previousYear = previousDate.getFullYear(); - const previousMonth = String(previousDate.getMonth() + 1).padStart(2, "0"); - const previousDay = String(previousDate.getDate()).padStart(2, "0"); - - const defaultDate = `${currentYear}-${currentMonth}-${currentDay}`; - const defaultPreviousDate = `${previousYear}-${previousMonth}-${previousDay}`; - - useEffect(() => { - setStartDate(query.startDate || defaultPreviousDate); - setEndDate(query.endDate || defaultDate); - setOption(query.sortBy || "DES_CREATE_DATE"); - if (query.startDate && query.endDate) { - searchDiaryList(query.startDate, query.endDate, query.sortBy); - } - }, [query.startDate, query.endDate, query.sortBy]); - - useEffect(() => { - searchDiaryList(startDate, endDate, option); - }, [option]); - - const handleStartDateChange = (event) => { - const newStartDate = event.target.value; - setStartDate(newStartDate); - if (newStartDate > endDate) { - setEndDate(""); - } - }; - - const handleEndDateChange = (event) => { - setEndDate(event.target.value); - }; - - const handleOptionChange = (event) => { - setOption(event.target.value); - }; - - const searchDiaryList = async ( - start = startDate, - end = endDate, - sortBy = option - ) => { - try { - const response = await diaryController.searchDiaryList({ - userId: id, - startDate: start, - finishDate: end, - sortBy: sortBy, - }); - const diaries = response.data.result.diaries; - const createDateList = diaries.map((diary) => diary.createDate); - setDiaries(createDateList); - - navigate( - `${location.pathname}?startDate=${start}&endDate=${end}&sortBy=${sortBy}`, - { replace: true } - ); - } catch (error) { - console.error("기간별 일기 조회 중 오류", error); - } - }; - - return ( -
-
- -
부터
-
-
- -
까지
-
-
- -
-
- ); -} - -const DiaryManagement = () => { - const dispatch = useDispatch(); - const userInfo = useSelector((state) => state.UserInfo); - const [diaries, setDiaries] = useState([]); - - useEffect(() => { - dispatch({ type: SET_PAGENAME, pageName: "일기 관리" }); - }, [dispatch]); - - return ( -
- - -
- ); -}; - -export default DiaryManagement; diff --git a/src/pages/ImageDiary/HelpForAi.js b/src/pages/ImageDiary/HelpForAi.js index fe40c7a..a1f9663 100644 --- a/src/pages/ImageDiary/HelpForAi.js +++ b/src/pages/ImageDiary/HelpForAi.js @@ -4,6 +4,9 @@ import Button from "../../component/Button"; import imgController from "../../api/img.controller"; import SimpleImageSlider from "react-simple-image-slider"; import Modal from "../../component/Modal"; +import Loading from "../../component/Loading"; +import { useRecoilState, useRecoilValue } from "recoil"; +import { apiKeyStore } from "../../recoil/apiKeyStore"; const HelpForAi = () => { const location = useLocation(); @@ -11,23 +14,31 @@ const HelpForAi = () => { const navigate = useNavigate(); // AI가 생성한 이미지 const [aiImages, setAiImages] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const apiKeyState = useRecoilValue(apiKeyStore); + const getImageForAI = async () => { - // prompt가 비어 있거나 keyword를 포함하지 않으면 모달을 표시 - if (!prompt || !prompt.includes(keyword)) { + // keyword가 "자유롭게 그려주세요"이거나, prompt가 keyword를 포함하는 경우 + if ( + (keyword === "자유롭게 그려주세요" && prompt) || + (prompt && prompt.includes(keyword)) + ) { + try { + setIsLoading(true); + const res = await imgController.generateImage({ + password: apiKeyState.apiKey, + prompt: prompt, + n: 3, + }); + console.log(res.data); + setAiImages(res.data.result.urls); + } catch (error) { + console.error(error); + } + setIsLoading(false); + } else { + // 그 외의 경우 모달을 표시 handleModal(); - return; - } - - try { - const res = await imgController.generateImage({ - password: "password", - prompt: prompt, - n: 3, - }); - console.log(res.data); - setAiImages(res.data.result.urls); - } catch (error) { - console.error(error); } }; @@ -66,7 +77,7 @@ const HelpForAi = () => { if (aiImages.length === 0) return; navigate("/draw/help/result", { state: { - keyword: prompt, + keyword: keyword, image: aiImages[currentIndex], fullWidth: fullWidth, }, @@ -102,7 +113,7 @@ const HelpForAi = () => { onChange={handlePromptChange} />
); }; diff --git a/src/pages/ImageDiary/ShowAiResult.js b/src/pages/ImageDiary/ShowAiResult.js index 55d5b5d..544da06 100644 --- a/src/pages/ImageDiary/ShowAiResult.js +++ b/src/pages/ImageDiary/ShowAiResult.js @@ -1,26 +1,54 @@ import React, { useEffect, useState } from "react"; import { useLocation, useNavigate } from "react-router-dom"; +import { useRecoilState } from "recoil"; import BackGroundSkyButton from "../../component/BackGroundSkyButton"; -import Button from "../../component/Button"; -import { style } from "d3"; +import { imageState } from "../../recoil/keywordState"; const ShowAiResult = () => { const location = useLocation(); + const navigate = useNavigate(); + const [image, setImage] = useRecoilState(imageState); + const keyword = location.state.keyword; const imageUrl = location.state.image; const fullWidth = location.state.fullWidth; - const navigate = useNavigate(); // 배경 투명도 + const [bgOpacity, setBgOpacity] = useState(1); const bgOpacityList = ["30", "50", "70", "100"]; - const [bgOpacity, setBgOpacity] = useState("1"); const [selectedOpacityIndex, setSelectedOpacityIndex] = useState(3); + // 투명도 변경 const changeOpacity = (item, index) => { setBgOpacity(parseInt(item) * 0.01); setSelectedOpacityIndex(index); }; + // 이미지 저장 + const saveEditImage = () => { + const newImage = { + keyword: keyword, + imageUrl: imageUrl, + bgOpacity: bgOpacity, + }; - const saveEditImage = () => {}; + setImage((prevImage) => { + // 같은 키워드에 2번 도움받는 경우, 기존 키워드의 이미지 덮어쓰기 + const existingIndex = prevImage.findIndex( + (image) => image.keyword === keyword + ); + + if (existingIndex !== -1) { + // 같은 keyword를 가진 항목이 이미 존재하는 경우, 해당 항목을 업데이트 + const updatedImages = [...prevImage]; + updatedImages[existingIndex] = newImage; + return updatedImages; + } else { + // 같은 keyword를 가진 항목이 존재하지 않는 경우, 새 항목 추가 + return [...prevImage, newImage]; + } + }); + + navigate("/draw"); + }; return (
diff --git a/src/pages/MyPages/APIKeyInput.js b/src/pages/MyPages/APIKeyInput.js new file mode 100644 index 0000000..2e54caa --- /dev/null +++ b/src/pages/MyPages/APIKeyInput.js @@ -0,0 +1,49 @@ +import { useRecoilState } from "recoil"; +import { apiKeyStore } from "../../recoil/apiKeyStore"; + +export const API_KEY_INPUT_PAGE_PATH = "/mypage/apikey"; + +export const APIKeyInput = () => { + return ( +
+
+

API KEY 입력하기

+

+ API KEY를 입력하면, OpenAI 의 이미지 생성 API를 사용할 수 있습니다. +

+ +
+
+ ); +}; + +const APIKeyInputForm = () => { + const [apiKeyState, setApiKeyState] = useRecoilState(apiKeyStore); + + const handleInput = (e) => { + setApiKeyState((preState) => { + return { + ...preState, + apiKey: e.target.value, + }; + }); + }; + + return ( +
+
+ + +
+
+ ); +}; diff --git a/src/pages/MyPage.js b/src/pages/MyPages/MyPage.js similarity index 69% rename from src/pages/MyPage.js rename to src/pages/MyPages/MyPage.js index a94f7a1..245a6e8 100644 --- a/src/pages/MyPage.js +++ b/src/pages/MyPages/MyPage.js @@ -1,35 +1,53 @@ -import User from "../assets/user.png"; -import MyPageList from "../component/MyPageList"; -import Diary from "../assets/diary.png"; -import { useState } from "react"; +import User from "../../assets/user.png"; +import Left from "../../assets/left.png"; +import MyPageItem from "../../component/MyPageItem"; +import Diary from "../../assets/diary.png"; +import { useEffect, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { useEffect } from "react"; -import { SET_PAGENAME } from "../redux/modules/PageName"; +import { SET_PAGENAME } from "../../redux/modules/PageName"; import { useNavigate } from "react-router-dom"; -import { setFontSize } from "../redux/modules/fontSize"; +import { setFontSize } from "../../redux/modules/fontSize"; +import { USER_UPDATE_PAGE_PATH } from "./UserUpdate"; +import { DIARY_MANAGEMENT_PAGE_PATH } from "./DiaryManagement"; +import { API_KEY_INPUT_PAGE_PATH } from "./APIKeyInput"; -const UserProfile = () => { +const MyPage = () => { + const dispatch = useDispatch(); let navigate = useNavigate(); + useEffect(() => { + dispatch({ type: SET_PAGENAME, pageName: "마이페이지" }); + }, []); - let userInfo = useSelector((state) => state.UserInfo); - - const LogOutButton = () => { - const logout = () => { - localStorage.clear(); - navigate("/login"); - }; + return ( +
+ {/* 사진, 이름, 닉네임 */} + - return ( -
- + {/* 마이페이지 리스트 */} +
+ navigate(USER_UPDATE_PAGE_PATH)} + /> + navigate(API_KEY_INPUT_PAGE_PATH)} + /> + navigate(DIARY_MANAGEMENT_PAGE_PATH)} + /> +
- ); - }; +
+ ); +}; + +const UserProfile = () => { + let userInfo = useSelector((state) => state.UserInfo); return (
{ ); }; -const Toggle = () => { +const LogOutButton = () => { + let navigate = useNavigate(); + + const logout = () => { + localStorage.clear(); + navigate("/login"); + }; + + return ( +
+ +
+ ); +}; + +const FontSizeToggleButton = () => { const dispatch = useDispatch(); const currentFontSize = useSelector((state) => state.fontSize); const [isDrop, setIsDrop] = useState(false); @@ -103,32 +138,4 @@ const Toggle = () => { ); }; -const MyPage = () => { - const dispatch = useDispatch(); - let navigate = useNavigate(); - useEffect(() => { - dispatch({ type: SET_PAGENAME, pageName: "마이페이지" }); - }, []); - return ( -
- {/* 사진, 이름, 닉네임 */} - - - {/* 마이페이지 리스트 */} -
- navigate("/userupdate")} - /> - navigate("/diarymanagement")} - /> - -
-
- ); -}; export default MyPage; diff --git a/src/pages/MyPages/UserUpdate.js b/src/pages/MyPages/UserUpdate.js index cd6c138..0d1d391 100644 --- a/src/pages/MyPages/UserUpdate.js +++ b/src/pages/MyPages/UserUpdate.js @@ -5,12 +5,7 @@ import Button from "../../component/Button"; import Modal from "../../component/Modal"; import { useNavigate } from "react-router-dom"; import { useDispatch, useSelector } from "react-redux"; -<<<<<<< HEAD:src/pages/UserUpdate.js -import { SET_PAGENAME } from "../redux/modules/PageName"; -======= import { SET_PAGENAME } from "../../redux/modules/PageName"; -import axios from "axios"; ->>>>>>> develop:src/pages/MyPages/UserUpdate.js export const USER_UPDATE_PAGE_PATH = "/userupdate"; diff --git a/src/pages/UserUpdate.js b/src/pages/UserUpdate.js deleted file mode 100644 index 7c22b79..0000000 --- a/src/pages/UserUpdate.js +++ /dev/null @@ -1,208 +0,0 @@ -import React, { useEffect, useState } from "react"; -import UserController from "../api/users.controller"; -import { useForm } from "react-hook-form"; -import Button from "../component/Button"; -import Modal from "../component/Modal"; -import { useNavigate } from "react-router-dom"; -import { useDispatch, useSelector } from "react-redux"; -import { SET_PAGENAME } from "../redux/modules/PageName"; - -const UserUpdate = () => { - const dispatch = useDispatch(); - const userInfo = useSelector((state) => state.UserInfo); - const navigate = useNavigate(); - - const { - register, - handleSubmit, - watch, - formState: { errors }, - reset, - } = useForm(); - - useEffect(() => { - dispatch({ type: SET_PAGENAME, pageName: "정보 수정" }); - }, [dispatch]); - - useEffect(() => { - reset(userInfo); - console.log(userInfo); - }, [userInfo, reset]); - - const onSubmit = async (data) => { - const accessToken = localStorage.getItem("AccessToken"); - if (!accessToken) { - console.error("엑세스 토큰이 없습니다."); - return; - } - - const userData = { - id: userInfo.userId, - username: data.username, - nickname: data.nickname, - email: data.email, - password: data.password, - birth: data.birth, - }; - - try { - await UserController.updateUser({ userData, accessToken }); - console.log("성공"); - navigate("/mypage"); - } catch (error) { - console.log("정보 수정 중 오류", error); - } - }; - - const [nickname, setNickname] = useState(""); - const nicknameRegister = register("nickname", { - required: "빈 칸 없이 작성해주세요.", - }); - - const handleChange = (event) => { - setNickname(event.target.value); - nicknameRegister.onChange(event); - }; - - const checkNickname = async () => { - if (nickname === "") return; - try { - const res = await UserController.checkNickname({ nickname }); - setIsNicknameExist(false); - } catch (error) { - console.log(error); - setIsNicknameExist(true); - } - setIsModalOpen(true); - }; - - const [isModalOpen, setIsModalOpen] = useState(false); - const closeModal = () => { - setIsModalOpen(false); - }; - const [isNicknameExist, setIsNicknameExist] = useState(false); - - if (!userInfo) return null; - - return ( -
-
-
- - -
- {errors.username && errors.username.message} -
-
-
- -
- -
-
- {errors.nickname && errors.nickname.message} -
-
-
- - -
- {errors.birth && errors.birth.message} -
-
-
- - -
- {errors.email && errors.email.message} -
-
-
- - -
- {errors.password && errors.password.message} -
-
-
- - - value === watch("password") || "비밀번호가 일치하지 않습니다.", - })} - /> -
- {errors.passwordChk && errors.passwordChk.message} -
-
- -
- {isModalOpen && isNicknameExist && ( - - )} - {isModalOpen && !isNicknameExist && ( - - )} -
- ); -}; - -export default UserUpdate; diff --git a/src/recoil/apiKeyStore.js b/src/recoil/apiKeyStore.js new file mode 100644 index 0000000..c9e8b66 --- /dev/null +++ b/src/recoil/apiKeyStore.js @@ -0,0 +1,8 @@ +import { atom } from "recoil"; + +export const apiKeyStore = atom({ + key: "apiKeyState", + default: { + apiKey: "", + }, +}); diff --git a/src/recoil/canvasState.js b/src/recoil/canvasState.js new file mode 100644 index 0000000..324a46c --- /dev/null +++ b/src/recoil/canvasState.js @@ -0,0 +1,10 @@ +import { atom } from "recoil"; + +export const selectedColorState = atom({ + key: "selectedColorState", + default: "#000000", +}); +export const brushSizeState = atom({ + key: "brushSizeState", + default: 3, +}); diff --git a/src/recoil/keywordState.js b/src/recoil/keywordState.js new file mode 100644 index 0000000..e4728bb --- /dev/null +++ b/src/recoil/keywordState.js @@ -0,0 +1,17 @@ +import { atom } from "recoil"; + +export const keywordState = atom({ + key: "keywordState", + default: [], +}); + +const defaultImageState = { + keyword: "Keyword", + imageUrl: "ImageUrl", + bgOpacity: "numBgOpacity", +}; + +export const imageState = atom({ + key: "imageState", + default: [], +}); diff --git a/src/redux/config/configStore.js b/src/redux/config/configStore.js index 9521ef5..5af27ed 100644 --- a/src/redux/config/configStore.js +++ b/src/redux/config/configStore.js @@ -1,6 +1,5 @@ import { createStore } from "redux"; import { combineReducers } from "redux"; -import ImageDiary from "../modules/ImageDiary.js"; import DiaryDate from "../modules/DiaryDate.js"; import DiaryInfo from "../modules/DiaryInfo.js"; import UserInfo from "../modules/UserInfo.js"; @@ -8,7 +7,6 @@ import PageName from "../modules/PageName.js"; import fontSizeReducer from "../modules/fontSize.js"; const rootReducer = combineReducers({ - ImageDiary, DiaryDate, DiaryInfo, UserInfo, diff --git a/src/redux/modules/DiaryInfo.js b/src/redux/modules/DiaryInfo.js index f2a4d36..06fd189 100644 --- a/src/redux/modules/DiaryInfo.js +++ b/src/redux/modules/DiaryInfo.js @@ -7,7 +7,8 @@ const initialState = { diaryId: 0, content: "", date: 0, - imgUrl: null, + keywords: [], + imgUrl: "", }; export default function DiaryInfo(state = initialState, action) { @@ -18,6 +19,7 @@ export default function DiaryInfo(state = initialState, action) { diaryId: action.diaryId, content: action.content, date: action.date, + keywords: action.keywords, imgUrl: action.imgUrl, }; diff --git a/src/redux/modules/ImageDiary.js b/src/redux/modules/ImageDiary.js deleted file mode 100644 index 228f657..0000000 --- a/src/redux/modules/ImageDiary.js +++ /dev/null @@ -1,24 +0,0 @@ -export const SELECT_COLOR = "SELECT_COLOR"; -export const BRUSH_SIZE = "BRUSH_SIZE"; - -export const initialState = { - selectedColor: "#000000", - brushSize: 1, -}; - -export default function ImageDiary(state = initialState, action) { - switch (action.type) { - case SELECT_COLOR: - return { - ...state, - selectedColor: action.selectedColor, - }; - case BRUSH_SIZE: - return { - ...state, - brushSize: action.brushSize, - }; - default: - return state; - } -}