From 22e952060db2078494ab4d4672b115afe946d0e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=83=9C=ED=98=84?= <143389711+taehyun00@users.noreply.github.com> Date: Sun, 22 Jun 2025 17:03:49 +0900 Subject: [PATCH 01/11] feat :: report page make --- apps/client/src/app/mypage/page.tsx | 13 ++- apps/client/src/app/report/page.tsx | 68 ++++++++++++ apps/client/src/app/style/Mypage.ts | 2 + apps/client/src/app/style/report.ts | 158 ++++++++++++++++++++++++++++ 4 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 apps/client/src/app/report/page.tsx create mode 100644 apps/client/src/app/style/report.ts diff --git a/apps/client/src/app/mypage/page.tsx b/apps/client/src/app/mypage/page.tsx index e0a6fc7..1591233 100644 --- a/apps/client/src/app/mypage/page.tsx +++ b/apps/client/src/app/mypage/page.tsx @@ -2,6 +2,7 @@ import React from "react"; import arrow from "@/app/images/arrow3.svg"; +import { useRouter } from "next/navigation"; import { Container, Title, @@ -22,7 +23,17 @@ import { const name = "1134박기주"; const email = "24.013@bssm.hs.kr"; + + export default function Mypage() { + + const router = useRouter() ; + + +function Goreport(){ + router.push("/report"); +} + return ( 마이페이지 @@ -44,7 +55,7 @@ export default function Mypage() { + + + + + + + ); +} diff --git a/apps/client/src/app/style/Mypage.ts b/apps/client/src/app/style/Mypage.ts index 422110f..a096c3b 100644 --- a/apps/client/src/app/style/Mypage.ts +++ b/apps/client/src/app/style/Mypage.ts @@ -125,6 +125,7 @@ export const ButtonSection = styled.div` justify-content : start; align-items: start; gap : 25vh; + `; export const Buttonp = styled.p` @@ -146,4 +147,5 @@ export const ButtonContent = styled.div` flex-direction : column; align-items : start; gap : 3vh; + margin-left : 4px; `; \ No newline at end of file diff --git a/apps/client/src/app/style/report.ts b/apps/client/src/app/style/report.ts new file mode 100644 index 0000000..60e3739 --- /dev/null +++ b/apps/client/src/app/style/report.ts @@ -0,0 +1,158 @@ +"use client"; + +import styled from "styled-components"; +import Image from "next/image"; + +export const Container = styled.div` + height: 100vh; + padding: 3rem; + margin-left : 10vh; + +`; + +export const Title = styled.h1` + font-size: 5vh; + font-weight: 500; + margin-bottom:1rem; +`; + +export const Sub = styled.h1` + font-size: 3vh; + font-weight: 350; + margin-bottom: 5rem; +`; + + +export const ContentBox = styled.div` + display: flex; + flex-direction: row; + align-items: start; + justify-content: start; + gap: 10vh; + width: 100vh; + height : 30vh; + background-color : #FFFFFF; + border-radius: 2vh; +`; + +export const ProfileImage = styled(Image)` + width: 15vh; + height: 15vh; + margin-top : 14vh; + +`; + +export const InfoContainer = styled.div` + display: flex; + flex-direction: column; + gap: 1.25rem; + margin-top : 12vh; +`; + +export const Label = styled.p` + font-size: 2vh; + font-weight: 400; + margin-bottom:2vh; +`; + +export const Input = styled.input` + width: 40vh; + height: 40px; + border-radius: 8px; + border: none; + padding: 0 1rem; + font-size: 0.95rem; + background-color: white; + + + &::placeholder { + color: #a0a0a0; + } +`; + +export const StyledArrowImage = styled(Image)` + transition: filter 0.1s ease; +`; + +export const Button = styled.button` + + width: 43vh; + height: 12vh; + background-color: #FFFFFF; + + font-weight: 600; + border: 2px solid #8B8B8B; + border-radius: 6px; + cursor: pointer; + + &:hover { + border: 2px solid #0050d7; + ${StyledArrowImage} { + filter: invert(22%) sepia(100%) saturate(5740%) hue-rotate(213deg) brightness(95%) contrast(99%); + + } +`; + + + + +export const Form = styled.div` + display : flex; + flex-direction : column; + gap : 10vh; + +`; + + +export const Name = styled.p` + font-size: 2.5vh; +`; + + +export const NameForm = styled.div` + display : flex; + justify-content : start; + align-items: end; + gap : 2vh; + margin-left : 6vh; + margin-top : -15vh; +`; + +export const ButtonForm = styled.div` + display : flex; + justify-content : start; + align-items: end; + gap : 2vh; + margin-left : 6vh; + margin-top : 0vh; +`; + +export const ButtonSection = styled.div` + display : flex; + justify-content : start; + align-items: start; + gap : 25vh; + +`; + +export const Buttonp = styled.p` + font-size: 2vh; + font-weight: 400; + +`; + + +export const ButtonSub = styled.p` + font-size: 1.4vh; + font-weight: 400; +`; + + + +export const ButtonContent = styled.div` + display : flex; + flex-direction : column; + align-items : start; + gap : 3vh; + margin-left : 4px; +`; \ No newline at end of file From 05fbc943f9a8988437da1aaf955c1195da43d2ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=83=9C=ED=98=84?= <143389711+taehyun00@users.noreply.github.com> Date: Sun, 22 Jun 2025 17:28:30 +0900 Subject: [PATCH 02/11] feat :: report page layout --- apps/client/src/app/report/page.tsx | 96 +++++++++++++++++------------ apps/client/src/app/style/report.ts | 1 - 2 files changed, 55 insertions(+), 42 deletions(-) diff --git a/apps/client/src/app/report/page.tsx b/apps/client/src/app/report/page.tsx index 44ca2e8..8592122 100644 --- a/apps/client/src/app/report/page.tsx +++ b/apps/client/src/app/report/page.tsx @@ -1,29 +1,56 @@ "use client"; import React from "react"; -import arrow from "@/app/images/arrow3.svg"; -import { useRouter } from "next/navigation"; + import { Container, Title, ContentBox, - InfoContainer, - NameForm, - Name, - Form, - Button, - ButtonForm, - StyledArrowImage, - ButtonSection, - Buttonp, - ButtonSub, - ButtonContent, + Sub } from "../style/report" const name = "1134박기주"; const email = "24.013@bssm.hs.kr"; +const posts = [ + { + id: "001", + title: "차수민쌤이랑 데이트 박제현쌤이랑 데이트", + writer: "오주현", + date: "2025.06.22" + }, + { + id: "002", + title: "오늘 점심 뭐 먹지?", + writer: "김예은", + date: "2025.06.21" + }, + { + id: "003", + title: "React 훅 정리 노트 공유합니다!", + writer: "이정환", + date: "2025.06.20" + }, + { + id: "004", + title: "Next.js 서버 컴포넌트 사용기", + writer: "정은지", + date: "2025.06.19" + }, + { + id: "005", + title: "스타벅스 쿠폰 나눔해요~", + writer: "박지민", + date: "2025.06.18" + }, + { + id: "006", + title: "차수민쌤이랑 데이트 박제현쌤이랑 데이트", + writer: "오주현", + date: "2025.06.22" + } + ]; @@ -34,34 +61,21 @@ export default function Mypage() { 마이페이지 신고기록 조회 -
- - - {name} -

{email}

-
- - - - -
-
+
+ {posts.map((post) => ( +
+
+

{post.title}

+
+

{post.writer}

+

{post.date}

+

{post.id}

+
+
+
+ ))} + +
); diff --git a/apps/client/src/app/style/report.ts b/apps/client/src/app/style/report.ts index 60e3739..ab75e32 100644 --- a/apps/client/src/app/style/report.ts +++ b/apps/client/src/app/style/report.ts @@ -31,7 +31,6 @@ export const ContentBox = styled.div` gap: 10vh; width: 100vh; height : 30vh; - background-color : #FFFFFF; border-radius: 2vh; `; From 960c8c0f0ea607225d41d1b4ba1ae2f683a908ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=83=9C=ED=98=84?= <143389711+taehyun00@users.noreply.github.com> Date: Tue, 8 Jul 2025 10:58:28 +0900 Subject: [PATCH 03/11] feat :: report page / mtvote page --- apps/client/src/app/images/arrow4.svg | 3 + apps/client/src/app/images/arrow5.svg | 3 + apps/client/src/app/mypage/page.tsx | 7 +- apps/client/src/app/myvote/page.tsx | 113 ++++++++++++++ apps/client/src/app/report/page.tsx | 169 +++++++++++++------- apps/client/src/app/style/Mypage.ts | 8 +- apps/client/src/app/style/myvote.ts | 214 ++++++++++++++++++++++++++ apps/client/src/app/style/report.ts | 153 +++++++++++++++++- 8 files changed, 606 insertions(+), 64 deletions(-) create mode 100644 apps/client/src/app/images/arrow4.svg create mode 100644 apps/client/src/app/images/arrow5.svg create mode 100644 apps/client/src/app/myvote/page.tsx create mode 100644 apps/client/src/app/style/myvote.ts diff --git a/apps/client/src/app/images/arrow4.svg b/apps/client/src/app/images/arrow4.svg new file mode 100644 index 0000000..14e43d6 --- /dev/null +++ b/apps/client/src/app/images/arrow4.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/client/src/app/images/arrow5.svg b/apps/client/src/app/images/arrow5.svg new file mode 100644 index 0000000..2a2b9e0 --- /dev/null +++ b/apps/client/src/app/images/arrow5.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/client/src/app/mypage/page.tsx b/apps/client/src/app/mypage/page.tsx index 1591233..c413059 100644 --- a/apps/client/src/app/mypage/page.tsx +++ b/apps/client/src/app/mypage/page.tsx @@ -34,6 +34,11 @@ function Goreport(){ router.push("/report"); } +function Gomyvote(){ + router.push("/myvote"); +} + + return ( 마이페이지 @@ -46,7 +51,7 @@ function Goreport(){ + setShowModal(!showModal)}>투표 제작하기 - - - - ); diff --git a/apps/client/src/app/style/Votemake.ts b/apps/client/src/app/style/Votemake.ts index cabc29f..59bb6f3 100644 --- a/apps/client/src/app/style/Votemake.ts +++ b/apps/client/src/app/style/Votemake.ts @@ -147,3 +147,35 @@ export const WarnP = styled.p` color: #ED3020; `; + + +export const OptionButton = styled(Button)` +position: relative; + +color: #0158de; +background-color: #FFFFFF; +border-color: #0158de; + +&:hover { + background-color: #004cc0; + color: #FFFFFF; +} + +width : 12vh; +height : 4vh; +border: 2px solid #0158de; +border-radius: 9999px; + +display : flex; +align-items : center; +justify-content : center; + +`; + + + +export const OptionC = styled.div` + display : flex; + gap : 2vh; + margin-bottom : 3vh; +` \ No newline at end of file diff --git a/apps/client/src/app/style/Votemenu.ts b/apps/client/src/app/style/Votemenu.ts index 1010dab..446a7e1 100644 --- a/apps/client/src/app/style/Votemenu.ts +++ b/apps/client/src/app/style/Votemenu.ts @@ -3,7 +3,7 @@ import styled from "styled-components"; export const Container = styled.div` display: flex; flex-direction: column; - gap: 4vh; + gap: 1vh; align-items: center; `; @@ -47,9 +47,9 @@ export const OptionLabel = styled.p` export const SubmitButton = styled.button.attrs(() => ({ type: "button", }))<{ $active: boolean }>` - position: relative; - left: 47vh; - top: 0vh; + position: absolute; + left: 95vh; + top: 32vh; font-family: "P_Regular"; width: 16vh; height: 4vh; diff --git a/apps/client/src/app/style/report.ts b/apps/client/src/app/style/report.ts index d1f04c1..77b46ef 100644 --- a/apps/client/src/app/style/report.ts +++ b/apps/client/src/app/style/report.ts @@ -15,6 +15,7 @@ export const Title = styled.h1` font-size: 5vh; font-weight: 500; margin-bottom:1rem; + position : relative; `; export const Sub = styled.h1` @@ -204,6 +205,7 @@ export const ListItemInner = styled.div` export const ListTitle = styled.p` font-size: 2vh; font-weight: 400; + `; export const ListInfo = styled.div` @@ -229,7 +231,7 @@ export const DetailTitle = styled.p` font-size: 2.7vh; font-weight: 500; position : absolute; - top:-24vh; + top:-20vh; `; @@ -237,13 +239,13 @@ export const DetailTitle = styled.p` export const DetailInfo = styled.p` font-size: 1.5vh; position : absolute; - top:-18vh; + top:-15vh; `; export const DetailContent = styled.p` font-size: 2vh; position : absolute; - top:-10vh; + top:-7vh; width:100% `; @@ -269,6 +271,7 @@ export const BackButtonWrapper = styled.div` display: flex; align-items: center; gap: 3vh; + `; export const BackArrowImage = styled(Image)` @@ -286,6 +289,8 @@ export const DetailWrapper = styled.div` `; export const CheckResultButton = styled.button` + position: relative; + top:6vh; background-color: #0158DE; border: none; width: 14vh; diff --git a/apps/client/src/app/style/votepage.ts b/apps/client/src/app/style/votepage.ts index 374ef3d..d5fa60d 100644 --- a/apps/client/src/app/style/votepage.ts +++ b/apps/client/src/app/style/votepage.ts @@ -27,6 +27,7 @@ export const CategoryText = styled.p` export const TitleWrapper = styled.div` position: absolute; top: 3vh; + width : 100%; `; export const TitleText = styled.p` diff --git a/apps/client/src/app/success/page.tsx b/apps/client/src/app/success/page.tsx index 3816c41..29c6057 100644 --- a/apps/client/src/app/success/page.tsx +++ b/apps/client/src/app/success/page.tsx @@ -3,14 +3,56 @@ import React from "react"; import Image from "next/image"; import check from "@/app/images/check.svg"; import { useRouter } from "next/navigation"; - import { Wrapper, Container, Message, Button } from "../style/End"; -export default function Endvote() { + +type Props = { + searchParams: Record; +}; + +import { useMutation } from '@apollo/client'; +import {POST_VOTE} from "../mutations" + +// mutation 정의 (variables 선언 포함) + + +export default function Endvote({ searchParams }: Props) { + + const [post_vote, { loading, error }] = useMutation(POST_VOTE); + const router = useRouter(); + const category = searchParams.catego as string || ""; // 변수명 맞춤 + const title = searchParams.title as string || ""; + const optionsss = searchParams.options; + + + let parsedOptions: string[] = []; + + if (typeof optionsss === "string") { + try { + parsedOptions = JSON.parse(optionsss); + } catch (e) { + console.error("JSON parse error:", e); + } + } else if (Array.isArray(optionsss)) { + parsedOptions = optionsss; + } + + console.log(parsedOptions,typeof(parsedOptions)) - function gomain() { - router.push("/"); + async function gomain() { + try { + await post_vote({ + variables: { + title, + category, + options: parsedOptions, + } + }); + router.push("/"); + } catch (err) { + console.error(err); + } } return ( diff --git a/apps/client/src/app/votemake/page.tsx b/apps/client/src/app/votemake/page.tsx index 1165fc8..a4b696e 100644 --- a/apps/client/src/app/votemake/page.tsx +++ b/apps/client/src/app/votemake/page.tsx @@ -21,11 +21,14 @@ import { ButtonsRow, InnerButtonsWrapper, Button, - SubmitButton,WarnP + SubmitButton, + WarnP, + OptionButton, + OptionC } from "../style/Votemake"; export default function Votemake() { - const [options, setOptions] = useState(["", ""]); + const [optionss, setOptionss] = useState(["", ""]); const [aiUseCount, setAiUseCount] = useState(0); const [showModal, setShowModal] = useState(false); const [showModal_option, setShowModal_option] = useState(false); @@ -37,23 +40,23 @@ export default function Votemake() { const maxAiUse = 3; const handleAddOption = () => { - if (options.length < 5) { - setOptions([...options, ""]); + if (optionss.length < 5) { + setOptionss([...optionss, ""]); } }; const handleChangeOption = (index: number, value: string) => { - const updated = [...options]; + const updated = [...optionss]; updated[index] = value; - setOptions(updated); + setOptionss(updated); }; const handleRemoveOption = (index: number) => { - const updated = options.filter((_, i) => i !== index); - setOptions(updated); + const updated = optionss.filter((_, i) => i !== index); + setOptionss(updated); }; - const exampleOptions = [ + const exampleOptionss = [ "AI 추천 선지 1", "AI 추천 선지 2", "AI 추천 선지 3", @@ -67,7 +70,7 @@ export default function Votemake() { return; } - else if (options.length < 2|| options.some(option => option.trim() === "")) { + else if (optionss.length < 2|| optionss.some(option => option.trim() === "")) { setShowModal_option(true); return; @@ -89,24 +92,38 @@ export default function Votemake() { return; } - const updated = options.map((option, idx) => - option.trim() === "" ? exampleOptions[idx] || `AI 추천 선지 ${idx + 1}` : option + const updated = optionss.map((option, idx) => + option.trim() === "" ? exampleOptionss[idx] || `AI 추천 선지 ${idx + 1}` : option ); - setOptions(updated); + setOptionss(updated); setAiUseCount(aiUseCount + 1); }; + const [catego, setcate] = useState(""); + return ( <> {showModal && setShowModal(false)} />} {showModal_option && setShowModal_option(false)} />} - {showModal_worn && setShowModal_worn(false)} onMain={() => router.push("/recommend")} />} + {showModal_worn && setShowModal_worn(false)} onMain={() => { + const query = new URLSearchParams({ + catego: catego, + title: title, + optionsss: JSON.stringify(optionss), + }).toString(); + + router.push(`/recommend?${query}`); +}} />} - 투표 만들기 + + + setcate("재미")}>재미 + setcate("진지")}>진지 + settitle(e.target.value)} /> {IStitle ? (
@@ -118,7 +135,7 @@ export default function Votemake() { - {options.map((option, index) => ( + {optionss.map((option, index) => ( - {options.length < 5 && ( + {optionss.length < 5 && ( + )} diff --git a/apps/client/src/app/votemenu/page.tsx b/apps/client/src/app/votemenu/page.tsx index fa14335..4b67ff1 100644 --- a/apps/client/src/app/votemenu/page.tsx +++ b/apps/client/src/app/votemenu/page.tsx @@ -13,9 +13,6 @@ import { export default function Votemenu({ options }: { options: any[] }) { - console.log(options) - - const navigate = useRouter(); const [selected, setSelected] = useState(null); From 8bebff118b34c75782082f174c7b6655711c12be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=83=9C=ED=98=84?= <143389711+taehyun00@users.noreply.github.com> Date: Sun, 13 Jul 2025 23:16:16 +0900 Subject: [PATCH 08/11] refactor :: backend connect code refact --- apps/client/src/app/layout.tsx | 2 +- apps/client/src/app/queries.js | 41 ++++---- apps/client/src/app/recommend/page.tsx | 34 ++----- apps/client/src/app/success/page.tsx | 11 ++- apps/client/src/app/votemake/page.tsx | 104 ++++++++++++--------- apps/client/src/components/PleaseTitle.tsx | 27 ++++++ 6 files changed, 129 insertions(+), 90 deletions(-) create mode 100644 apps/client/src/components/PleaseTitle.tsx diff --git a/apps/client/src/app/layout.tsx b/apps/client/src/app/layout.tsx index a029ada..96e9c5f 100644 --- a/apps/client/src/app/layout.tsx +++ b/apps/client/src/app/layout.tsx @@ -10,7 +10,7 @@ import { const client = new ApolloClient({ - uri: 'http://10.150.2.230:8080/graphql', + uri: 'http://10.150.149.229:8080/graphql', cache: new InMemoryCache(), }); diff --git a/apps/client/src/app/queries.js b/apps/client/src/app/queries.js index e5dc24a..9e4bf4c 100644 --- a/apps/client/src/app/queries.js +++ b/apps/client/src/app/queries.js @@ -1,22 +1,31 @@ import { gql } from '@apollo/client'; export const GET_ALL_VOTES = gql` - query MyQuery { - vote { - getAllVotes { - category - finishedAt - id - status - title - totalResponses - options { - content - id - percentage - responseCount - } - } +query MyQuery { + vote { + getAllVotes { + category + finishedAt + id + options { + content + id + percentage + responseCount } + status + title + totalResponses + } } + } +`; +export const GET_AIOPTION = gql` +query MyQuery($count: Int!, $title: String!) { + optionGenerator { + generateOptions(count: $count, title: $title) { + options + } + } +} `; \ No newline at end of file diff --git a/apps/client/src/app/recommend/page.tsx b/apps/client/src/app/recommend/page.tsx index b527ee2..effa64d 100644 --- a/apps/client/src/app/recommend/page.tsx +++ b/apps/client/src/app/recommend/page.tsx @@ -25,27 +25,15 @@ import { StyledArrowImage } from "../style/Recommend"; -type Props = { - searchParams: Record; -}; - -export default function recommend() { - // 클라이언트 훅들 및 상태는 여기서 OK (use client 선언 필요) - // next/navigation 사용 가능 - +export default function Recommend() { const [showModal, setShowModal] = useState(false); const router = useRouter(); - const searchParams = useSearchParams(); - const catego = searchParams.get("catego"); // string | null - const title = searchParams.get("title"); - const optionsss = searchParams.get("optionsss"); - - console.log(optionsss) - - - + // searchParams.get 은 string | null 반환함 + const catego = searchParams.get("catego") || ""; + const title = searchParams.get("title") || ""; + const optionsss = searchParams.get("optionsss") || ""; const [options, setOptions] = useState(["", "", "", "", ""]); @@ -53,12 +41,6 @@ export default function recommend() { router.push("/"); } - function toStringParam(param: string | null | undefined): string { - if (!param) return ""; // null, undefined, '' 모두 빈 문자열로 변환 - if (Array.isArray(param)) return param.join(","); - return param; - } - return ( <> {showModal && ( @@ -66,9 +48,9 @@ export default function recommend() { onClose={() => setShowModal(false)} onMain={() => { const query = new URLSearchParams({ - category: toStringParam(catego), - title: toStringParam(title), - options: toStringParam(optionsss), + category: catego, + title: title, + options: optionsss, }).toString(); router.push(`/success?${query}`); diff --git a/apps/client/src/app/success/page.tsx b/apps/client/src/app/success/page.tsx index 29c6057..b038c65 100644 --- a/apps/client/src/app/success/page.tsx +++ b/apps/client/src/app/success/page.tsx @@ -4,6 +4,7 @@ import Image from "next/image"; import check from "@/app/images/check.svg"; import { useRouter } from "next/navigation"; import { Wrapper, Container, Message, Button } from "../style/End"; +import { useSearchParams } from "next/navigation"; type Props = { @@ -16,14 +17,16 @@ import {POST_VOTE} from "../mutations" // mutation 정의 (variables 선언 포함) -export default function Endvote({ searchParams }: Props) { +export default function Endvote() { + const searchParams = useSearchParams(); + const [post_vote, { loading, error }] = useMutation(POST_VOTE); const router = useRouter(); - const category = searchParams.catego as string || ""; // 변수명 맞춤 - const title = searchParams.title as string || ""; - const optionsss = searchParams.options; + const category = searchParams.get("catego") as string || ""; // 변수명 맞춤 + const title = searchParams.get("title") as string || ""; + const optionsss = searchParams.get("options"); let parsedOptions: string[] = []; diff --git a/apps/client/src/app/votemake/page.tsx b/apps/client/src/app/votemake/page.tsx index a4b696e..a9722ec 100644 --- a/apps/client/src/app/votemake/page.tsx +++ b/apps/client/src/app/votemake/page.tsx @@ -2,10 +2,14 @@ import { useEffect, useState } from "react"; import AiLimitModal from "@/components/Ailimitmodal"; +import PleaseTitle from "@/components/PleaseTitle"; import OptionModal from "@/components/OptionModal"; import WornModal from "@/components/WornModal"; import { useRouter } from "next/navigation"; +import { useQuery } from '@apollo/client'; +import { GET_AIOPTION } from '../queries'; + import { Container, TitleWrapper, @@ -33,9 +37,10 @@ export default function Votemake() { const [showModal, setShowModal] = useState(false); const [showModal_option, setShowModal_option] = useState(false); const [showModal_worn, setShowModal_worn] = useState(false); + const [showModal_title, setShowModal_title] = useState(false); const [title, settitle] = useState(""); const [IStitle, setIstitle] = useState(false); - const router = useRouter() ; + const router = useRouter(); const maxAiUse = 3; @@ -56,75 +61,88 @@ export default function Votemake() { setOptionss(updated); }; - const exampleOptionss = [ - "AI 추천 선지 1", - "AI 추천 선지 2", - "AI 추천 선지 3", - "AI 추천 선지 4", - "AI 추천 선지 5", - ]; - const submitVote = () => { - if (title.trim() === "") { - return; - } - else if (optionss.length < 2|| optionss.some(option => option.trim() === "")) { + } else if (optionss.length < 2 || optionss.some(option => option.trim() === "")) { setShowModal_option(true); return; - - } - - else{ + } else { setShowModal_worn(true); } - - } + }; useEffect(() => { setIstitle(!(title.trim() !== "")); }, [title]); - + + + const { loading, error, data, refetch } = useQuery(GET_AIOPTION, { + variables: { + count: optionss.length, + title: title, + }, + }); + const handleAIRecommend = () => { - if (aiUseCount >= maxAiUse) { - setShowModal(true); - return; - } + if (title.length === 0) { + setShowModal_title(true); + } else { + if (aiUseCount >= maxAiUse) { + setShowModal(true); + return; + } - const updated = optionss.map((option, idx) => - option.trim() === "" ? exampleOptionss[idx] || `AI 추천 선지 ${idx + 1}` : option - ); + - setOptionss(updated); - setAiUseCount(aiUseCount + 1); + if (loading) { + console.log("AI가 선지를 생성 중입니다..."); + } else if (error) { + console.error("Error:", error.message); + } else if (data) { + console.log("데이터 생성 완료"); + setOptionss(data.optionGenerator.generateOptions.options); + setAiUseCount(aiUseCount + 1); + } else { + refetch(); // 데이터를 다시 가져오도록 요청 + } + } }; const [catego, setcate] = useState(""); return ( <> + {showModal_title && setShowModal_title(false)} />} {showModal && setShowModal(false)} />} {showModal_option && setShowModal_option(false)} />} - {showModal_worn && setShowModal_worn(false)} onMain={() => { - const query = new URLSearchParams({ - catego: catego, - title: title, - optionsss: JSON.stringify(optionss), - }).toString(); - - router.push(`/recommend?${query}`); -}} />} + {showModal_worn && ( + setShowModal_worn(false)} + onMain={() => { + const query = new URLSearchParams({ + catego: catego, + title: title, + optionsss: JSON.stringify(optionss), + }).toString(); + + router.push(`/recommend?${query}`); + }} + /> + )} - - - setcate("재미")}>재미 - setcate("진지")}>진지 + + setcate("재미")}>재미 + setcate("진지")}>진지 - settitle(e.target.value)} /> + settitle(e.target.value)} + /> {IStitle ? (
필수입력사항입니다! diff --git a/apps/client/src/components/PleaseTitle.tsx b/apps/client/src/components/PleaseTitle.tsx new file mode 100644 index 0000000..275bed9 --- /dev/null +++ b/apps/client/src/components/PleaseTitle.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import { + ModalOverlay, + ModalContent, + CloseButton, + Title, + Description, + Buttons +} from "../app/style/Modal" + +interface ModalProps { + onClose: () => void; +} + +export default function PleaseTitle({ onClose }: ModalProps) { + return ( + + + 안내 + AI 선지 추천을 사용하시려면 투표제목을 입력해주세요. + + 확인 + + + + ); +} From 855854b4c9637ad531257f94680c83c67cb97ec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=83=9C=ED=98=84?= <143389711+taehyun00@users.noreply.github.com> Date: Mon, 14 Jul 2025 09:27:17 +0900 Subject: [PATCH 09/11] refactor :: backend connect code refact --- apps/client/src/app/component/votepage.tsx | 17 ++++---- apps/client/src/app/mutations.js | 17 ++++++++ apps/client/src/app/tailvote/page.tsx | 20 ++++++++-- apps/client/src/app/votemake/page.tsx | 1 - apps/client/src/app/votemenu/page.tsx | 46 +++++++++++++++++++--- 5 files changed, 83 insertions(+), 18 deletions(-) diff --git a/apps/client/src/app/component/votepage.tsx b/apps/client/src/app/component/votepage.tsx index d828121..22d1fd1 100644 --- a/apps/client/src/app/component/votepage.tsx +++ b/apps/client/src/app/component/votepage.tsx @@ -10,11 +10,11 @@ import { DateText, InfoWrapper, TitleText, - MenuWrapper + MenuWrapper, } from "../style/votepage"; -import { useQuery } from '@apollo/client'; -import { GET_ALL_VOTES } from '../queries'; +import { useQuery } from "@apollo/client"; +import { GET_ALL_VOTES } from "../queries"; export default function Votepage() { const { loading, error, data } = useQuery(GET_ALL_VOTES); @@ -23,12 +23,9 @@ export default function Votepage() { if (error) return

에러 발생: {error.message}

; if (!data?.vote?.getAllVotes) return

투표 데이터 없음

; - - return ( {data.vote.getAllVotes.map((vote: any) => ( -
@@ -43,8 +40,12 @@ export default function Votepage() { - - + +
))} diff --git a/apps/client/src/app/mutations.js b/apps/client/src/app/mutations.js index 786c46d..4b9893f 100644 --- a/apps/client/src/app/mutations.js +++ b/apps/client/src/app/mutations.js @@ -7,4 +7,21 @@ export const POST_VOTE = gql` } } } +`; + + +export const POST_VOTERESUlT = gql` +mutation MyMutation($voteId : ID!,$optionId : ID!) { + voteResponse { + createVoteResponse(input: {voteId: $voteId, optionId: $optionId}) { + id + optionContent + optionId + userId + voteTitle + voteId + createdAt + } + } +} `; \ No newline at end of file diff --git a/apps/client/src/app/tailvote/page.tsx b/apps/client/src/app/tailvote/page.tsx index 2e4f3b2..8456007 100644 --- a/apps/client/src/app/tailvote/page.tsx +++ b/apps/client/src/app/tailvote/page.tsx @@ -2,6 +2,7 @@ import React, { useState } from "react"; import Tailvote_modal from "../Tailvote_modal/page"; import { useRouter } from "next/navigation"; +import { useSearchParams } from "next/navigation"; import { Overlay, Wrapper, @@ -18,9 +19,22 @@ import { } from "../style/Tailvote"; export default function Tailvote() { - const category = "재미질문 > 꼬리질문1"; - const title = "그 존재가 가장 무서운 이유는 무엇인가요?"; - const date = "2025-05-23"; + const searchParams = useSearchParams(); + const choseoption = searchParams.get("choseoptions"); + const fihishedAt = searchParams.get("fihishedAt"); + let datas = null; + if(choseoption != null){ + datas = JSON.parse(choseoption); + + } + console.log(datas.voteResponse.createVoteResponse.optionContent) + const choseoptions = datas.voteResponse.createVoteResponse.optionContent + + const category = "재미질문 > 꼬리질문"; + const title = choseoptions + "을(를) 선택한 이유는 무엇인가요?"; + const date = fihishedAt; + + console.log(date) const [textcontext, changetext] = useState(""); const [modalOpen, setModalOpen] = useState(false); diff --git a/apps/client/src/app/votemake/page.tsx b/apps/client/src/app/votemake/page.tsx index a9722ec..4579499 100644 --- a/apps/client/src/app/votemake/page.tsx +++ b/apps/client/src/app/votemake/page.tsx @@ -14,7 +14,6 @@ import { Container, TitleWrapper, TitleInputWrapper, - TitleLabel, TitleInput, OptionsWrapper, OptionRow, diff --git a/apps/client/src/app/votemenu/page.tsx b/apps/client/src/app/votemenu/page.tsx index 4b67ff1..fbacbd4 100644 --- a/apps/client/src/app/votemenu/page.tsx +++ b/apps/client/src/app/votemenu/page.tsx @@ -2,6 +2,8 @@ import { useState } from "react"; import { useRouter } from "next/navigation"; +import { useSearchParams } from "next/navigation"; + import { Container, VoteOption, @@ -10,20 +12,51 @@ import { SubmitButton, } from "../style/Votemenu"; +import { useMutation } from "@apollo/client"; +import { POST_VOTERESUlT } from "../mutations"; -export default function Votemenu({ options }: { options: any[] }) { - +export default function Votemenu({ + options, + voteid, + votefihishedAt +}: { + options: any[]; + voteid: string; + votefihishedAt: string // voteid를 문자열로 전달받음 +}) { + const searchParams = useSearchParams(); const navigate = useRouter(); const [selected, setSelected] = useState(null); + const [postVoteResult] = useMutation(POST_VOTERESUlT); + const handleChange = (index: number) => { setSelected((prev) => (prev === index ? null : index)); }; - const voting = () => { + const voting = async () => { if (selected !== null) { - setSelected(null); - navigate.push("/tailvote"); + const selectedOption = options[selected]; // 선택된 옵션 가져오기 + try { + console.log(voteid) + console.log(selectedOption.id) + const response = await postVoteResult({ + variables: { + voteId : voteid, // 전달받은 voteid 사용 + optionId: selectedOption.id, // 선택된 옵션 ID 전달 + }, + }); + console.log("투표 성공:", response.data); + setSelected(null); + const query = new URLSearchParams({ + choseoptions : JSON.stringify(response.data), + fihishedAt : String(votefihishedAt) + }); + navigate.push(`/tailvote?${query}`); + + } catch (error) { + console.error("투표 실패:", error); + } } else { alert("투표하고 진행해주세요"); } @@ -48,7 +81,8 @@ export default function Votemenu({ options }: { options: any[] }) { checked={selected === index} onChange={() => handleChange(index)} /> - {item.content} + {item.content} + {item.id} ))} From 78f1a4263e34d70bed0ff9d2deed2d8bdb936f8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=83=9C=ED=98=84?= <143389711+taehyun00@users.noreply.github.com> Date: Mon, 14 Jul 2025 14:40:09 +0900 Subject: [PATCH 10/11] refactor :: backend connect code refact --- apps/client/src/app/apolloClient.ts | 10 ++ apps/client/src/app/component/votepage.tsx | 104 +++++++++++++++------ apps/client/src/app/queries.js | 10 ++ apps/client/src/app/style/votepage.ts | 17 ++++ apps/client/src/app/success/page.tsx | 2 +- apps/client/src/app/votemenu/page.tsx | 1 - 6 files changed, 114 insertions(+), 30 deletions(-) create mode 100644 apps/client/src/app/apolloClient.ts diff --git a/apps/client/src/app/apolloClient.ts b/apps/client/src/app/apolloClient.ts new file mode 100644 index 0000000..68d3a28 --- /dev/null +++ b/apps/client/src/app/apolloClient.ts @@ -0,0 +1,10 @@ +// src/apolloClient.ts + +import { ApolloClient, InMemoryCache } from "@apollo/client"; + +const client = new ApolloClient({ + uri: "http://10.150.149.229:8080/graphql", // GraphQL API 엔드포인트 + cache: new InMemoryCache(), +}); + +export default client; diff --git a/apps/client/src/app/component/votepage.tsx b/apps/client/src/app/component/votepage.tsx index 22d1fd1..83e53c4 100644 --- a/apps/client/src/app/component/votepage.tsx +++ b/apps/client/src/app/component/votepage.tsx @@ -1,4 +1,6 @@ "use client"; +import client from "../apolloClient"; +import { useState, useEffect } from "react"; import Votemenu from "../votemenu/page"; import { @@ -11,43 +13,89 @@ import { InfoWrapper, TitleText, MenuWrapper, + ALERDYTitleText, + ALERDYCard } from "../style/votepage"; import { useQuery } from "@apollo/client"; -import { GET_ALL_VOTES } from "../queries"; +import { GET_ALL_VOTES, HAS_VOTED } from "../queries"; export default function Votepage() { - const { loading, error, data } = useQuery(GET_ALL_VOTES); + const { loading: loadingVotes, error: errorVotes, data: dataVotes } = useQuery(GET_ALL_VOTES); + const [hasUserVotedResults, setHasUserVotedResults] = useState([]); - if (loading) return

로딩 중...

; - if (error) return

에러 발생: {error.message}

; - if (!data?.vote?.getAllVotes) return

투표 데이터 없음

; + // 비동기적으로 투표 여부를 확인하여 상태 업데이트 + useEffect(() => { + if (dataVotes?.vote?.getAllVotes?.length) { + const fetchVoteStatuses = async () => { + const results: boolean[] = []; + for (const vote of dataVotes.vote.getAllVotes) { + if (vote?.id) { + try { + const response = await client.query({ + query: HAS_VOTED, + variables: { + votedId: vote.id, + }, + }); + results.push(response.data.voteResponse.hasUserVoted); + } catch (error) { + + + } + } else { + console.warn(`Invalid vote object detected:`, vote); + // 유효하지 않은 객체에 기본 값 추가 + } + } + setHasUserVotedResults(results); // 모든 결과를 한 번에 상태 업데이트 + }; + + fetchVoteStatuses(); + } + }, [dataVotes]); + + if (loadingVotes) return

로딩 중...

; + if (errorVotes) return

에러 발생: {errorVotes.message}

; + if (!dataVotes?.vote?.getAllVotes) return

투표 데이터 없음

; + + + return ( - {data.vote.getAllVotes.map((vote: any) => ( -
- - - {vote.category} - - {vote.title} - - {vote.finishedAt} 투표마감 - - - - - - - - - -
+ {dataVotes.vote.getAllVotes.map((vote: any, i: number) => ( + hasUserVotedResults[i++] === false ? ( + // 투표하지 않은 경우 +
+ + + {vote.category} + + {vote.title} + + {vote.finishedAt} 투표마감 + + + + + + + + +
+ ) : ( + // 이미 투표한 경우 +
+ + 이미 투표를 완료했습니다. + +
+ ) ))}
); diff --git a/apps/client/src/app/queries.js b/apps/client/src/app/queries.js index 9e4bf4c..a5b40bd 100644 --- a/apps/client/src/app/queries.js +++ b/apps/client/src/app/queries.js @@ -28,4 +28,14 @@ query MyQuery($count: Int!, $title: String!) { } } } +`; + + + +export const HAS_VOTED = gql` +query MyQuery($votedId : ID!) { + voteResponse { + hasUserVoted(voteId: $votedId) + } +} `; \ No newline at end of file diff --git a/apps/client/src/app/style/votepage.ts b/apps/client/src/app/style/votepage.ts index d5fa60d..5d66cc4 100644 --- a/apps/client/src/app/style/votepage.ts +++ b/apps/client/src/app/style/votepage.ts @@ -7,6 +7,17 @@ export const Container = styled.div` `; export const Card = styled.div` + border-radius : 10px; + background-color: #FFFFFF; + width: 120vh; + height: 60vh; +`; + +export const ALERDYCard = styled.div` + display : flex; + align-items : center; + justify-content : center; + border-radius : 10px; background-color: #FFFFFF; width: 120vh; height: 60vh; @@ -36,6 +47,12 @@ export const TitleText = styled.p` font-size: 3vh; `; +export const ALERDYTitleText = styled.p` + color: #000000; + font-family: 'P_Regular'; + font-size: 3vh; +`; + export const DateWrapper = styled.div` position: absolute; top: 4vh; diff --git a/apps/client/src/app/success/page.tsx b/apps/client/src/app/success/page.tsx index b038c65..db816da 100644 --- a/apps/client/src/app/success/page.tsx +++ b/apps/client/src/app/success/page.tsx @@ -24,7 +24,7 @@ export default function Endvote() { const [post_vote, { loading, error }] = useMutation(POST_VOTE); const router = useRouter(); - const category = searchParams.get("catego") as string || ""; // 변수명 맞춤 + const category = searchParams.get("category") as string || ""; // 변수명 맞춤 const title = searchParams.get("title") as string || ""; const optionsss = searchParams.get("options"); diff --git a/apps/client/src/app/votemenu/page.tsx b/apps/client/src/app/votemenu/page.tsx index fbacbd4..5c66e1e 100644 --- a/apps/client/src/app/votemenu/page.tsx +++ b/apps/client/src/app/votemenu/page.tsx @@ -82,7 +82,6 @@ export default function Votemenu({ onChange={() => handleChange(index)} /> {item.content} - {item.id} ))} From 9bd57a63e312c5ecce3ca8bec3a7c252e87d9913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=83=9C=ED=98=84?= <143389711+taehyun00@users.noreply.github.com> Date: Mon, 14 Jul 2025 14:51:07 +0900 Subject: [PATCH 11/11] refactor :: env file set --- apps/client/src/app/apolloClient.ts | 2 +- apps/client/src/app/layout.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/client/src/app/apolloClient.ts b/apps/client/src/app/apolloClient.ts index 68d3a28..bbe3680 100644 --- a/apps/client/src/app/apolloClient.ts +++ b/apps/client/src/app/apolloClient.ts @@ -3,7 +3,7 @@ import { ApolloClient, InMemoryCache } from "@apollo/client"; const client = new ApolloClient({ - uri: "http://10.150.149.229:8080/graphql", // GraphQL API 엔드포인트 + uri: process.env.NEXT_PUBLIC_BACKEND_URL, // GraphQL API 엔드포인트 cache: new InMemoryCache(), }); diff --git a/apps/client/src/app/layout.tsx b/apps/client/src/app/layout.tsx index 96e9c5f..4774fd3 100644 --- a/apps/client/src/app/layout.tsx +++ b/apps/client/src/app/layout.tsx @@ -10,7 +10,7 @@ import { const client = new ApolloClient({ - uri: 'http://10.150.149.229:8080/graphql', + uri: process.env.NEXT_PUBLIC_BACKEND_URL, cache: new InMemoryCache(), });