From 7abc06e34e562121450efa2aae4c0de340b2c8e1 Mon Sep 17 00:00:00 2001 From: gayoung Date: Sat, 21 Jun 2025 23:58:29 +0900 Subject: [PATCH 1/9] =?UTF-8?q?=E2=9C=A8:=20=EC=97=B0=EB=8F=99=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/goal/index.ts | 4 ++-- src/app/goal/page.tsx | 8 ++++++++ src/queries/goal/userGoalQuery.ts | 10 ++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 src/queries/goal/userGoalQuery.ts diff --git a/src/app/api/goal/index.ts b/src/app/api/goal/index.ts index acbf1d2..2a4af60 100644 --- a/src/app/api/goal/index.ts +++ b/src/app/api/goal/index.ts @@ -1,5 +1,5 @@ import { authAPI } from "@/app/api/config" -export const getGoalInfo = (id: number) => { - return authAPI.get(`/goal/2025-01-25`) +export const getGoalMainInfo = (date: string) => { + return authAPI.get(`/api/goal/${date}`) } diff --git a/src/app/goal/page.tsx b/src/app/goal/page.tsx index eb25e27..cfb111b 100644 --- a/src/app/goal/page.tsx +++ b/src/app/goal/page.tsx @@ -14,8 +14,16 @@ import AddButton from "@/components/common/addbutton/addbutton" import TopSheet from "@/components/common/topsheet/topsheet" import DefaultHeader from "@/components/layout/header/DefaultHeader" import Navigation from "@/components/layout/Navigation" +import { useGoalMainQuery } from "@/queries/goal/userGoalQuery" +import { useEffect } from "react" const Page = () => { + const { data } = useGoalMainQuery() + + useEffect(() => { + data + }, []) + return (
diff --git a/src/queries/goal/userGoalQuery.ts b/src/queries/goal/userGoalQuery.ts new file mode 100644 index 0000000..517836f --- /dev/null +++ b/src/queries/goal/userGoalQuery.ts @@ -0,0 +1,10 @@ +import { useQuery } from "@tanstack/react-query" +import { getGoalMainInfo } from "@/app/api/goal" + +export const useGoalMainQuery = () => { + return useQuery({ + queryKey: ["goalmain"], + queryFn: () => getGoalMainInfo("2025-01-25"), + onSuccess: (res: any) => {}, + }) +} From bc846a46807409c04d50a0772a2cda13272429e0 Mon Sep 17 00:00:00 2001 From: gayoung Date: Sun, 22 Jun 2025 01:02:28 +0900 Subject: [PATCH 2/9] =?UTF-8?q?=E2=9C=A8:=20smallcalendar=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + pnpm-lock.yaml | 21 +++- .../smallcalendar/smallcalendar.module.scss | 38 ++++-- .../common/smallcalendar/smallcalendar.tsx | 110 ++++++++++++------ .../common/topsheet/topsheet.module.scss | 7 +- src/components/common/topsheet/topsheet.tsx | 10 +- src/lib/utils/generatedates.ts | 18 +++ 7 files changed, 146 insertions(+), 59 deletions(-) create mode 100644 src/lib/utils/generatedates.ts diff --git a/package.json b/package.json index 35e58e3..85d83e7 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "react-dom": "^19.0.0", "react-mobile-picker": "^1.1.2", "react-qr-code": "^2.0.15", + "swiper": "^11.2.8", "zustand": "^5.0.3" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 966572b..9d6960e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,6 +47,9 @@ importers: react-qr-code: specifier: ^2.0.15 version: 2.0.15(react@19.1.0) + swiper: + specifier: ^11.2.8 + version: 11.2.8 zustand: specifier: ^5.0.3 version: 5.0.4(@types/react@19.1.4)(react@19.1.0) @@ -68,7 +71,7 @@ importers: version: 8.6.14(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.14(prettier@3.5.3)) '@storybook/experimental-addon-test': specifier: ^8.6.9 - version: 8.6.14(@vitest/browser@3.1.4)(@vitest/runner@3.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.14(prettier@3.5.3))(vitest@3.1.4) + version: 8.6.14(@vitest/browser@3.1.4(playwright@1.52.0)(vite@6.3.5(@types/node@20.17.48)(sass@1.89.0))(vitest@3.1.4))(@vitest/runner@3.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.14(prettier@3.5.3))(vitest@3.1.4(@types/node@20.17.48)(@vitest/browser@3.1.4)(sass@1.89.0)) '@storybook/experimental-nextjs-vite': specifier: 8.6.9 version: 8.6.9(@babel/core@7.27.1)(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.5.3)))(next@15.4.0-canary.10(@babel/core@7.27.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.89.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rollup@4.41.0)(storybook@8.6.14(prettier@3.5.3))(typescript@5.8.3)(vite@6.3.5(@types/node@20.17.48)(sass@1.89.0)) @@ -104,7 +107,7 @@ importers: version: 3.1.4(playwright@1.52.0)(vite@6.3.5(@types/node@20.17.48)(sass@1.89.0))(vitest@3.1.4) '@vitest/coverage-v8': specifier: ^3.0.9 - version: 3.1.4(@vitest/browser@3.1.4)(vitest@3.1.4) + version: 3.1.4(@vitest/browser@3.1.4(playwright@1.52.0)(vite@6.3.5(@types/node@20.17.48)(sass@1.89.0))(vitest@3.1.4))(vitest@3.1.4(@types/node@20.17.48)(@vitest/browser@3.1.4)(sass@1.89.0)) concurrently: specifier: ^9.1.2 version: 9.1.2 @@ -4329,6 +4332,10 @@ packages: engines: {node: '>=14.0.0'} hasBin: true + swiper@11.2.8: + resolution: {integrity: sha512-S5FVf6zWynPWooi7pJ7lZhSUe2snTzqLuUzbd5h5PHUOhzgvW0bLKBd2wv0ixn6/5o9vwc/IkQT74CRcLJQzeg==} + engines: {node: '>= 4.7.0'} + test-exclude@7.0.1: resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} engines: {node: '>=18'} @@ -6136,7 +6143,7 @@ snapshots: dependencies: type-fest: 2.19.0 - '@storybook/experimental-addon-test@8.6.14(@vitest/browser@3.1.4)(@vitest/runner@3.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.14(prettier@3.5.3))(vitest@3.1.4)': + '@storybook/experimental-addon-test@8.6.14(@vitest/browser@3.1.4(playwright@1.52.0)(vite@6.3.5(@types/node@20.17.48)(sass@1.89.0))(vitest@3.1.4))(@vitest/runner@3.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.14(prettier@3.5.3))(vitest@3.1.4(@types/node@20.17.48)(@vitest/browser@3.1.4)(sass@1.89.0))': dependencies: '@storybook/global': 5.0.0 '@storybook/icons': 1.4.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -6642,7 +6649,7 @@ snapshots: - utf-8-validate - vite - '@vitest/coverage-v8@3.1.4(@vitest/browser@3.1.4)(vitest@3.1.4)': + '@vitest/coverage-v8@3.1.4(@vitest/browser@3.1.4(playwright@1.52.0)(vite@6.3.5(@types/node@20.17.48)(sass@1.89.0))(vitest@3.1.4))(vitest@3.1.4(@types/node@20.17.48)(@vitest/browser@3.1.4)(sass@1.89.0))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 @@ -7513,7 +7520,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.32.1(eslint@9.27.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.27.0): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.32.1(eslint@9.27.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.27.0))(eslint@9.27.0): dependencies: debug: 3.2.7 optionalDependencies: @@ -7535,7 +7542,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.27.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.32.1(eslint@9.27.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.27.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.32.1(eslint@9.27.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.27.0))(eslint@9.27.0) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -9254,6 +9261,8 @@ snapshots: csso: 5.0.5 picocolors: 1.1.1 + swiper@11.2.8: {} + test-exclude@7.0.1: dependencies: '@istanbuljs/schema': 0.1.3 diff --git a/src/components/common/smallcalendar/smallcalendar.module.scss b/src/components/common/smallcalendar/smallcalendar.module.scss index a45f2a3..67c12c5 100644 --- a/src/components/common/smallcalendar/smallcalendar.module.scss +++ b/src/components/common/smallcalendar/smallcalendar.module.scss @@ -2,21 +2,43 @@ background-color: #fff; border-radius: 0 0 32px 32px; text-align: center; + padding: 24px 0; + + &-header { + h2 { + padding-top: 26px; + text-align: center; + font-size: 24px; + font-weight: 700; + margin-bottom: 27px; + } + } &-item { display: flex; - align-items: center; - justify-content: space-between; - padding: 24px 30px; + flex-direction: column; font-weight: 700; + padding-bottom: 10px; - li { + p { + position: relative; + margin-top: 5px; + padding: 10px; font-size: 20px; - text-align: center; - p { - margin-top: 5px; - padding: 10px; + &:nth-of-type(2)::after { + content: ""; + position: absolute; + top: -2px; + left: 0; + z-index: -1; + border-radius: 50%; + width: 46px; + height: 46px; + } + + &.today::after { + background: #F1F3FF; } } } diff --git a/src/components/common/smallcalendar/smallcalendar.tsx b/src/components/common/smallcalendar/smallcalendar.tsx index 6070c50..d48f4cc 100644 --- a/src/components/common/smallcalendar/smallcalendar.tsx +++ b/src/components/common/smallcalendar/smallcalendar.tsx @@ -1,41 +1,83 @@ -import classNames from "classnames/bind"; -import styles from "./smallcalendar.module.scss"; -const cx = classNames.bind(styles); +"use client" + +import { useEffect, useRef, useState } from "react" +import classNames from "classnames/bind" +import styles from "./smallcalendar.module.scss" +import { Swiper, SwiperSlide } from "swiper/react" +import "swiper/css" +import generateDates from "@/lib/utils/generatedates" +import dayjs from "dayjs" + +import "dayjs/locale/ko" + +dayjs.locale("ko") + +const cx = classNames.bind(styles) + const SmallCalendar = () => { + const swiperRef = useRef(null) + const centerDate = dayjs() + const [dates, setDates] = useState(() => generateDates(centerDate, 30)) + const [currentCenterDate, setCurrentCenterDate] = useState(null) + + const todayIndex = dates.findIndex((d) => d.isToday) + + const handleSlideChange = (swiper: any) => { + const center = dates[swiper.activeIndex] + if (center) { + setCurrentCenterDate(center.fullDate) + } + + // 날짜 확장 로직 + const buffer = 5 + if (swiper.activeIndex < buffer) { + const first = dates[0].fullDate + const more = generateDates(first.subtract(30, "day"), 30) + setDates((prev) => [...more.slice(0, 30), ...prev]) + swiper.slideTo(swiper.activeIndex + 30, 0) + } + + if (swiper.activeIndex > dates.length - buffer) { + const last = dates[dates.length - 1].fullDate + const more = generateDates(last.add(1, "day"), 30) + setDates((prev) => [...prev, ...more.slice(1)]) + } + } + + useEffect(() => { + if (todayIndex !== -1) { + setCurrentCenterDate(dates[todayIndex].fullDate) + } + }, []) + return (
-
    -
  • -

    -

    24

    -
  • -
  • -

    -

    24

    -
  • -
  • -

    -

    24

    -
  • -
  • -

    -

    24

    -
  • -
  • -

    -

    24

    -
  • -
  • -

    -

    24

    -
  • -
  • -

    -

    24

    -
  • -
+ {currentCenterDate && ( +
+

{currentCenterDate.format("YYYY년 M월")}

+
+ )} + { + swiperRef.current = swiper + setTimeout(() => swiper.slideTo(todayIndex, 0), 0) + }} + onSlideChange={handleSlideChange} + slidesPerView={7} + centeredSlides + spaceBetween={10}> + {dates.map((day) => ( + +
+

{day.label}

+

{day.date}

+
+
+ ))} +
) } -export default SmallCalendar; \ No newline at end of file + +export default SmallCalendar diff --git a/src/components/common/topsheet/topsheet.module.scss b/src/components/common/topsheet/topsheet.module.scss index 335d0da..ee37d01 100644 --- a/src/components/common/topsheet/topsheet.module.scss +++ b/src/components/common/topsheet/topsheet.module.scss @@ -4,12 +4,7 @@ box-shadow: 0 18px 35px 0 rgba(0, 0, 0, 0.1); text-align: center; padding-bottom: 10px; - h2 { - padding-top: 26px; - text-align: center; - font-size: 24px; - font-weight: 700; - } + &-bar { display: inline-block; width: 78px; diff --git a/src/components/common/topsheet/topsheet.tsx b/src/components/common/topsheet/topsheet.tsx index 6ffbe93..8122ff2 100644 --- a/src/components/common/topsheet/topsheet.tsx +++ b/src/components/common/topsheet/topsheet.tsx @@ -1,15 +1,15 @@ -import classNames from "classnames/bind"; -import styles from "./topsheet.module.scss"; -const cx = classNames.bind(styles); +import classNames from "classnames/bind" +import styles from "./topsheet.module.scss" + +const cx = classNames.bind(styles) const TopSheet = ({ children }) => { return (
-

2020. 00

{children}
) } -export default TopSheet; \ No newline at end of file +export default TopSheet diff --git a/src/lib/utils/generatedates.ts b/src/lib/utils/generatedates.ts new file mode 100644 index 0000000..608c6a2 --- /dev/null +++ b/src/lib/utils/generatedates.ts @@ -0,0 +1,18 @@ +import dayjs from "dayjs" + +const generateDates = (centerDate: dayjs.Dayjs, range = 30) => { + const dates = [] + for (let i = -range; i <= range; i++) { + const d = centerDate.add(i, "day") + dates.push({ + key: d.format("YYYY-MM-DD"), + label: d.format("dd"), // 요일 + date: d.date(), + fullDate: d, + isToday: d.isSame(dayjs(), "day"), + }) + } + return dates +} + +export default generateDates From 01903175c21093ff9304dab1249363a3a73e29d7 Mon Sep 17 00:00:00 2001 From: gayoung Date: Sun, 22 Jun 2025 01:15:20 +0900 Subject: [PATCH 3/9] =?UTF-8?q?=E2=9C=A8:=20=EB=82=A0=EC=A7=9C=ED=98=95?= =?UTF-8?q?=EC=8B=9D=20=EC=A0=84=EC=86=A1=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EA=B0=80=EA=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/goal/page.tsx | 14 +++++++------- .../common/smallcalendar/smallcalendar.tsx | 6 ++++-- src/queries/goal/userGoalQuery.ts | 4 ++-- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/app/goal/page.tsx b/src/app/goal/page.tsx index cfb111b..776205e 100644 --- a/src/app/goal/page.tsx +++ b/src/app/goal/page.tsx @@ -15,22 +15,22 @@ import TopSheet from "@/components/common/topsheet/topsheet" import DefaultHeader from "@/components/layout/header/DefaultHeader" import Navigation from "@/components/layout/Navigation" import { useGoalMainQuery } from "@/queries/goal/userGoalQuery" -import { useEffect } from "react" +import { useEffect, useState } from "react" +import dayjs from "dayjs" const Page = () => { - const { data } = useGoalMainQuery() - - useEffect(() => { - data - }, []) + const today = dayjs().format("YYYY-MM-DD") + const [clickedDate, setClickedDate] = useState(today) + console.log(clickedDate) + const { data } = useGoalMainQuery(clickedDate) return (
- +
diff --git a/src/components/common/smallcalendar/smallcalendar.tsx b/src/components/common/smallcalendar/smallcalendar.tsx index d48f4cc..005e1da 100644 --- a/src/components/common/smallcalendar/smallcalendar.tsx +++ b/src/components/common/smallcalendar/smallcalendar.tsx @@ -14,7 +14,7 @@ dayjs.locale("ko") const cx = classNames.bind(styles) -const SmallCalendar = () => { +const SmallCalendar = ({ setClickedDate }) => { const swiperRef = useRef(null) const centerDate = dayjs() const [dates, setDates] = useState(() => generateDates(centerDate, 30)) @@ -68,7 +68,9 @@ const SmallCalendar = () => { spaceBetween={10}> {dates.map((day) => ( -
+
setClickedDate(`${currentCenterDate?.format("YYYY-MM")}-${day.date}`)}>

{day.label}

{day.date}

diff --git a/src/queries/goal/userGoalQuery.ts b/src/queries/goal/userGoalQuery.ts index 517836f..65befc1 100644 --- a/src/queries/goal/userGoalQuery.ts +++ b/src/queries/goal/userGoalQuery.ts @@ -1,10 +1,10 @@ import { useQuery } from "@tanstack/react-query" import { getGoalMainInfo } from "@/app/api/goal" -export const useGoalMainQuery = () => { +export const useGoalMainQuery = (clickedDate) => { return useQuery({ queryKey: ["goalmain"], - queryFn: () => getGoalMainInfo("2025-01-25"), + queryFn: () => getGoalMainInfo(clickedDate), onSuccess: (res: any) => {}, }) } From 53afa63c8285de8f06fb0048dfc66ae67efd4690 Mon Sep 17 00:00:00 2001 From: gayoung Date: Thu, 26 Jun 2025 16:51:16 +0900 Subject: [PATCH 4/9] =?UTF-8?q?=E2=9C=A8:=20step1=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=97=B0=EB=8F=99=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/goal/index.ts | 6 +- .../_components/buttonwrapper.module.scss | 10 +- src/app/goal/_components/buttonwrapper.tsx | 33 ++-- .../goal/_components/goalresult.module.scss | 14 +- src/app/goal/_components/goalresult.tsx | 13 +- src/app/goal/_components/step1.module.scss | 14 +- src/app/goal/_components/step1.tsx | 161 ++++++++++-------- src/app/goal/create/page.tsx | 32 +++- src/app/goal/page.tsx | 3 +- src/assets/icons/goals/goal1.svg | 10 ++ src/assets/icons/goals/goal2.svg | 10 ++ src/assets/icons/goals/goal3.svg | 10 ++ src/assets/icons/goals/goal4.svg | 10 ++ src/assets/icons/goals/goal5.svg | 10 ++ src/assets/icons/goals/goal6.svg | 10 ++ src/assets/icons/goals/goal7.svg | 10 ++ src/assets/icons/goals/goal8.svg | 10 ++ src/assets/icons/goals/goal9.svg | 10 ++ src/queries/goal/userGoalQuery.ts | 14 +- 19 files changed, 281 insertions(+), 109 deletions(-) create mode 100644 src/assets/icons/goals/goal1.svg create mode 100644 src/assets/icons/goals/goal2.svg create mode 100644 src/assets/icons/goals/goal3.svg create mode 100644 src/assets/icons/goals/goal4.svg create mode 100644 src/assets/icons/goals/goal5.svg create mode 100644 src/assets/icons/goals/goal6.svg create mode 100644 src/assets/icons/goals/goal7.svg create mode 100644 src/assets/icons/goals/goal8.svg create mode 100644 src/assets/icons/goals/goal9.svg diff --git a/src/app/api/goal/index.ts b/src/app/api/goal/index.ts index 2a4af60..0556a98 100644 --- a/src/app/api/goal/index.ts +++ b/src/app/api/goal/index.ts @@ -1,5 +1,9 @@ import { authAPI } from "@/app/api/config" export const getGoalMainInfo = (date: string) => { - return authAPI.get(`/api/goal/${date}`) + return authAPI.get(`/api/goal?date=${date}`) +} + +export const crateGoal = (request) => { + return authAPI.post(`/api/goal`, request) } diff --git a/src/app/goal/_components/buttonwrapper.module.scss b/src/app/goal/_components/buttonwrapper.module.scss index af40d66..f94e5e6 100644 --- a/src/app/goal/_components/buttonwrapper.module.scss +++ b/src/app/goal/_components/buttonwrapper.module.scss @@ -1,9 +1,9 @@ .goal__btn { - padding: 0 30px; - background: $white; + background: #fff; position: fixed; - bottom: 0; - left: 0; width: 100%; - z-index: 100; + left: 0; + bottom: 0; + padding: 16px 32px; + border-top: 1px solid #D8DADB; } diff --git a/src/app/goal/_components/buttonwrapper.tsx b/src/app/goal/_components/buttonwrapper.tsx index d1ceeee..e72e511 100644 --- a/src/app/goal/_components/buttonwrapper.tsx +++ b/src/app/goal/_components/buttonwrapper.tsx @@ -1,20 +1,33 @@ "use client" -import styles from "./buttonwrapper.module.scss"; -import Button from "@/components/common/button/button"; +import styles from "./buttonwrapper.module.scss" +import Button from "@/components/common/button/button" -const ButtonWrapper = () => { +type Props = { + step: 1 | 2 | 3 + onNext: () => void + onPrev: () => void +} + +const ButtonWrapper = ({ step, onNext, onPrev }: Props) => { + const handleClick = () => { + if (step < 3) { + onNext() + } else { + // 완료 동작 + alert("목표 설정이 완료되었습니다!") + } + } return (
) } -export default ButtonWrapper; \ No newline at end of file +export default ButtonWrapper diff --git a/src/app/goal/_components/goalresult.module.scss b/src/app/goal/_components/goalresult.module.scss index 133adf3..d4f12cb 100644 --- a/src/app/goal/_components/goalresult.module.scss +++ b/src/app/goal/_components/goalresult.module.scss @@ -1,22 +1,24 @@ .goal__result { - width: 160px; - height: 163px; - border: 5px solid #F2F2F5; - border-radius: 50%; + padding-right: 16px; + p { - margin-top: 20px; + padding-top: 20px; text-align: center; font-weight: 700; + span { color: #8D8D8D; font-weight: 400; } } } + .goal__icon { width: 100%; - height: 100%; + aspect-ratio: 1 / 1; display: flex; align-items: center; justify-content: center; + border: 5px solid #F2F2F5; + border-radius: 50%; } \ No newline at end of file diff --git a/src/app/goal/_components/goalresult.tsx b/src/app/goal/_components/goalresult.tsx index 4439a7f..a65f733 100644 --- a/src/app/goal/_components/goalresult.tsx +++ b/src/app/goal/_components/goalresult.tsx @@ -1,13 +1,16 @@ import styles from "./goalresult.module.scss" -import DefaultIcon from "@/assets/icons/goals/icon-default.svg"; -const GoalResult = () => { + +const GoalResult = ({ data }) => { + const Icon = data?.icon return (
- +
-

01 하루 물 6컵

+

+ {String(data?.number || 1).padStart(2, "0")} {data?.name || "목표 추가"} +

) } -export default GoalResult \ No newline at end of file +export default GoalResult diff --git a/src/app/goal/_components/step1.module.scss b/src/app/goal/_components/step1.module.scss index f208b00..4248545 100644 --- a/src/app/goal/_components/step1.module.scss +++ b/src/app/goal/_components/step1.module.scss @@ -46,17 +46,23 @@ &-control { width: 100%; - overflow-x: auto; - display: flex; margin-top: 24px; - gap: 16px; - padding: 14px; + + > div { + text-align: center; + } } &-txt { + margin-top: 16px; display: flex; align-items: center; + justify-content: center; gap: 5px; font-size: 14px; + + p { + white-space: nowrap; + } } } diff --git a/src/app/goal/_components/step1.tsx b/src/app/goal/_components/step1.tsx index f77c842..1e51baa 100644 --- a/src/app/goal/_components/step1.tsx +++ b/src/app/goal/_components/step1.tsx @@ -1,28 +1,86 @@ -"use client"; -import classNames from "classnames/bind"; -import styles from "./step1.module.scss"; -const cx = classNames.bind(styles); +"use client" +import classNames from "classnames/bind" +import styles from "./step1.module.scss" -import StepBox from "@/app/goal/_components/stepBox"; -import Goal1 from "@/assets/icons/sticker/compliment-sticker-1.svg" -import AddButton from "@/components/common/addbutton/addbutton"; -import GoalResult from "@/app/goal/_components/goalresult"; +const cx = classNames.bind(styles) +import { Swiper, SwiperSlide } from "swiper/react" +import "swiper/css" +import "swiper/css/navigation" +import "swiper/css/pagination" +import StepBox from "@/app/goal/_components/stepBox" +import Goal1 from "@/assets/icons/goals/goal1.svg" +import Goal2 from "@/assets/icons/goals/goal2.svg" +import Goal3 from "@/assets/icons/goals/goal3.svg" +import Goal4 from "@/assets/icons/goals/goal4.svg" +import Goal5 from "@/assets/icons/goals/goal5.svg" +import Goal6 from "@/assets/icons/goals/goal6.svg" +import Goal7 from "@/assets/icons/goals/goal7.svg" +import Goal8 from "@/assets/icons/goals/goal8.svg" +import DefaultIcon from "@/assets/icons/goals/icon-default.svg" +import AddButton from "@/components/common/addbutton/addbutton" +import GoalResult from "@/app/goal/_components/goalresult" +import { useEffect, useRef, useState } from "react" + +const Step1 = ({ data, setData }) => { + const swiperRef = useRef() + const goalList = [ + { id: 1, icon: Goal1, label: "하루 물 6컵" }, + { id: 2, icon: Goal2, label: "야채 먹기" }, + { id: 3, icon: Goal3, label: "삼시세끼 채소" }, + { id: 4, icon: Goal4, label: "가족과 운동" }, + { id: 5, icon: Goal5, label: "저녁 30분 운동" }, + { id: 6, icon: Goal6, label: "패스트 푸드 끊기" }, + { id: 7, icon: Goal7, label: "근력 운동" }, + { id: 8, icon: Goal8, label: "하루 물 6컵" }, + ] + const [selectedGoals, setSelectedGoals] = useState<{ icon: any; name: string }[]>([]) + + const handleClickGoal = (id: number, icon: any, label: string) => { + if (selectedGoals.length >= 10) return alert("최대 10개의 목표까지 추가할 수 있어요") + + // 중복 방지 + const alreadyExists = selectedGoals.some((g) => g.name === label) + if (alreadyExists) return alert("이미 추가된 목표예요") + + setSelectedGoals((prev) => { + const updated = [...prev, { icon, name: label }] + setTimeout(() => { + swiperRef.current?.slideTo(updated.length - 1) + }, 100) // 약간 delay 줘야 렌더링 이후 슬라이드됨 + return updated + }) + } + + useEffect(() => { + setData(selectedGoals) + }, [selectedGoals, setData]) -const Step1 = () => { return ( <>
- +

최대 10개까지의 목표를 추가할 수 있어요

- + (swiperRef.current = swiper)}> + {selectedGoals.map((goal, idx) => ( + + + + ))} + + + +
@@ -30,61 +88,30 @@ const Step1 = () => { 그로우핏 추천 목표

직접 설정하기

-
    -
  • - -
    - { - }}/> -

    하루 물 6컵

    -
    -
  • -
  • - -
    - { - }}/> -

    하루 물 6컵

    -
    -
  • -
  • - -
    - { - }}/> -

    하루 물 6컵

    -
    -
  • -
  • - -
    - { - }}/> -

    하루 물 6컵

    -
    -
  • -
  • - -
    - { - }}/> -

    하루 물 6컵

    -
    -
  • -
  • - -
    - { - }}/> -

    하루 물 6컵

    -
    -
  • -
+ + {goalList.map((goal) => { + const Icon = goal.icon + const isSelected = selectedGoals.some((g) => g.name === goal.label) + return ( + + +
+ handleClickGoal(goal.id, goal.icon, goal.label)} + /> +

{goal.label}

+
+
+ ) + })} +
- ); -}; + ) +} -export default Step1; \ No newline at end of file +export default Step1 diff --git a/src/app/goal/create/page.tsx b/src/app/goal/create/page.tsx index 31c26fa..29509be 100644 --- a/src/app/goal/create/page.tsx +++ b/src/app/goal/create/page.tsx @@ -1,19 +1,41 @@ -import styles from "./page.module.scss" +"use client" import Step1 from "@/app/goal/_components/step1" import Step2 from "@/app/goal/_components/step2" import ButtonWrapper from "@/app/goal/_components/buttonwrapper" import Complete from "@/app/goal/_components/complete" import BackHeader from "@/components/layout/header/BackHeader" +import { useState } from "react" +import { useGoalCreate } from "@/queries/goal/userGoalQuery" const Page = () => { + const [step, setStep] = useState<1 | 2 | 3>(1) // 단계: 1 → 2 → 3 + const [data, setData] = useState({ + startDate: "", + endDate: "", + certificationCount: 0, + name: "", + iconId: 0, + icon: "", + }) + + const handleNext = () => { + setStep((prev) => (prev < 3 ? ((prev + 1) as 1 | 2 | 3) : prev)) + } + const handlePrev = () => { + setStep((prev) => (prev > 1 ? ((prev - 1) as 1 | 2 | 3) : prev)) + } + + const { mutate } = useGoalCreate(data) return (
- {/**/} - {/**/} - - + + {step === 1 && } + {step === 2 && } + {step === 3 && } + +
) } diff --git a/src/app/goal/page.tsx b/src/app/goal/page.tsx index 776205e..ad88f4b 100644 --- a/src/app/goal/page.tsx +++ b/src/app/goal/page.tsx @@ -15,14 +15,13 @@ import TopSheet from "@/components/common/topsheet/topsheet" import DefaultHeader from "@/components/layout/header/DefaultHeader" import Navigation from "@/components/layout/Navigation" import { useGoalMainQuery } from "@/queries/goal/userGoalQuery" -import { useEffect, useState } from "react" +import { useState } from "react" import dayjs from "dayjs" const Page = () => { const today = dayjs().format("YYYY-MM-DD") const [clickedDate, setClickedDate] = useState(today) - console.log(clickedDate) const { data } = useGoalMainQuery(clickedDate) return (
diff --git a/src/assets/icons/goals/goal1.svg b/src/assets/icons/goals/goal1.svg new file mode 100644 index 0000000..cd4cd98 --- /dev/null +++ b/src/assets/icons/goals/goal1.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/icons/goals/goal2.svg b/src/assets/icons/goals/goal2.svg new file mode 100644 index 0000000..a793fbc --- /dev/null +++ b/src/assets/icons/goals/goal2.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/icons/goals/goal3.svg b/src/assets/icons/goals/goal3.svg new file mode 100644 index 0000000..e420824 --- /dev/null +++ b/src/assets/icons/goals/goal3.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/icons/goals/goal4.svg b/src/assets/icons/goals/goal4.svg new file mode 100644 index 0000000..ede9e2b --- /dev/null +++ b/src/assets/icons/goals/goal4.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/icons/goals/goal5.svg b/src/assets/icons/goals/goal5.svg new file mode 100644 index 0000000..090395d --- /dev/null +++ b/src/assets/icons/goals/goal5.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/icons/goals/goal6.svg b/src/assets/icons/goals/goal6.svg new file mode 100644 index 0000000..f2ac207 --- /dev/null +++ b/src/assets/icons/goals/goal6.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/icons/goals/goal7.svg b/src/assets/icons/goals/goal7.svg new file mode 100644 index 0000000..e0ddc22 --- /dev/null +++ b/src/assets/icons/goals/goal7.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/icons/goals/goal8.svg b/src/assets/icons/goals/goal8.svg new file mode 100644 index 0000000..42cfac2 --- /dev/null +++ b/src/assets/icons/goals/goal8.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/icons/goals/goal9.svg b/src/assets/icons/goals/goal9.svg new file mode 100644 index 0000000..f652ca8 --- /dev/null +++ b/src/assets/icons/goals/goal9.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/queries/goal/userGoalQuery.ts b/src/queries/goal/userGoalQuery.ts index 65befc1..987c039 100644 --- a/src/queries/goal/userGoalQuery.ts +++ b/src/queries/goal/userGoalQuery.ts @@ -1,10 +1,16 @@ -import { useQuery } from "@tanstack/react-query" -import { getGoalMainInfo } from "@/app/api/goal" +import { useMutation, useQuery } from "@tanstack/react-query" +import { crateGoal, getGoalMainInfo } from "@/app/api/goal" export const useGoalMainQuery = (clickedDate) => { return useQuery({ - queryKey: ["goalmain"], + queryKey: ["goalmain", clickedDate], queryFn: () => getGoalMainInfo(clickedDate), - onSuccess: (res: any) => {}, + }) +} + +export const useGoalCreate = (data) => { + return useMutation({ + mutationKey: ["goaldata"], + mutationFn: () => crateGoal(data), }) } From a5a91d5141813940042637496087372973ac104d Mon Sep 17 00:00:00 2001 From: gayoung Date: Sun, 13 Jul 2025 23:33:58 +0900 Subject: [PATCH 5/9] =?UTF-8?q?=E2=9C=A8:=20goal=20api,=20letter=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 +- src/app/api/goal/index.ts | 14 +++- src/app/goal/_components/buttonwrapper.tsx | 18 +++-- src/app/goal/_components/complete.tsx | 10 +-- src/app/goal/_components/step1.module.scss | 12 +++ src/app/goal/_components/step1.tsx | 4 +- src/app/goal/_components/step2.module.scss | 7 ++ src/app/goal/_components/step2.tsx | 63 ++++++++++----- src/app/goal/create/page.tsx | 29 +++++-- .../@modal/goal-modal/goalmodal.module.scss | 20 +++++ .../goal/detail/@modal/goal-modal/page.tsx | 81 +++++++++++-------- .../goal/detail/@modal/letter-modal/page.tsx | 49 ++++++++--- src/app/goal/detail/page.tsx | 33 ++++++-- src/app/goal/page.tsx | 20 ++--- src/assets/icons/common/icon-add_black.svg | 5 ++ src/assets/icons/common/icon-added_black.svg | 4 + src/assets/icons/goals/goal1.svg | 11 ++- src/assets/icons/goals/goal2.svg | 11 ++- src/assets/icons/goals/goal3.svg | 11 ++- src/assets/icons/goals/goal4.svg | 11 ++- src/assets/icons/goals/goal5.svg | 11 ++- src/assets/icons/goals/goal6.svg | 11 ++- src/assets/icons/goals/goal7.svg | 11 ++- src/assets/icons/goals/goal8.svg | 11 ++- src/assets/icons/goals/goal9.svg | 11 ++- src/components/common/addbutton/addbutton.tsx | 4 +- .../buttomsheet/buttomsheet.module.scss | 1 + .../common/gaugedonut/gaugedonut.tsx | 15 ++-- .../smallcalendar/smallcalendar.module.scss | 7 +- .../common/smallcalendar/smallcalendar.tsx | 19 ++++- src/components/common/stepbox/stepbox.tsx | 41 ++++------ src/queries/goal/userGoalQuery.ts | 25 +++++- src/styles/_variables.scss | 2 +- 33 files changed, 381 insertions(+), 204 deletions(-) create mode 100644 src/assets/icons/common/icon-add_black.svg create mode 100644 src/assets/icons/common/icon-added_black.svg diff --git a/package.json b/package.json index 8206999..0f0a306 100644 --- a/package.json +++ b/package.json @@ -74,5 +74,6 @@ "extends": [ "plugin:storybook/recommended" ] - } + }, + "packageManager": "pnpm@9.7.0+sha512.dc09430156b427f5ecfc79888899e1c39d2d690f004be70e05230b72cb173d96839587545d09429b55ac3c429c801b4dc3c0e002f653830a420fa2dd4e3cf9cf" } diff --git a/src/app/api/goal/index.ts b/src/app/api/goal/index.ts index 0556a98..e19e180 100644 --- a/src/app/api/goal/index.ts +++ b/src/app/api/goal/index.ts @@ -4,6 +4,18 @@ export const getGoalMainInfo = (date: string) => { return authAPI.get(`/api/goal?date=${date}`) } -export const crateGoal = (request) => { +export const createGoal = (request) => { return authAPI.post(`/api/goal`, request) } + +export const getGoalDetailInfo = (date: string) => { + return authAPI.get(`/api/goal?date=${date}`) +} + +export const postCertifyGoal = (goalId: number, request) => { + return authAPI.post(`/api/goal/${goalId}/certify`, request) +} + +export const postLetter = (goalId, request) => { + return authAPI.post(`/api/letter/${goalId}`, request) +} diff --git a/src/app/goal/_components/buttonwrapper.tsx b/src/app/goal/_components/buttonwrapper.tsx index e72e511..ee7dcb5 100644 --- a/src/app/goal/_components/buttonwrapper.tsx +++ b/src/app/goal/_components/buttonwrapper.tsx @@ -1,20 +1,24 @@ "use client" import styles from "./buttonwrapper.module.scss" import Button from "@/components/common/button/button" +import { useRouter } from "next/navigation" type Props = { step: 1 | 2 | 3 onNext: () => void - onPrev: () => void + onMutate: () => void } -const ButtonWrapper = ({ step, onNext, onPrev }: Props) => { +const ButtonWrapper = ({ step, onNext, onMutate }: Props) => { + const router = useRouter() const handleClick = () => { - if (step < 3) { + if (step < 2) { onNext() - } else { - // 완료 동작 - alert("목표 설정이 완료되었습니다!") + } else if (step === 2) { + onMutate() + onNext() + } else if (step === 3) { + router.push("/goal/detail") } } return ( @@ -23,7 +27,7 @@ const ButtonWrapper = ({ step, onNext, onPrev }: Props) => { size="large" variant="filled" shape="rounded" - label={step < 3 ? "다음" : "완료"} + label={step < 3 ? "다음" : "목표보드로 이동하기"} onClick={handleClick} />
diff --git a/src/app/goal/_components/complete.tsx b/src/app/goal/_components/complete.tsx index 53c6995..de78f73 100644 --- a/src/app/goal/_components/complete.tsx +++ b/src/app/goal/_components/complete.tsx @@ -3,7 +3,7 @@ import Link from "next/link" import styles from "./complete.module.scss" import GoalResult from "@/app/goal/_components/goalresult" -const Complete = () => { +const Complete = ({ data }) => { return (
@@ -12,11 +12,9 @@ const Complete = () => { 목표가
설정되었어요! -

2020년 5월 1일

- 목표 보드로 이동하기 -
-
- +

+ {data.startDate} ~ {data.endDate} +

) diff --git a/src/app/goal/_components/step1.module.scss b/src/app/goal/_components/step1.module.scss index 4248545..e4a2ca2 100644 --- a/src/app/goal/_components/step1.module.scss +++ b/src/app/goal/_components/step1.module.scss @@ -10,6 +10,18 @@ padding-left: 31px; } } + + &__icon { + display: flex; + align-items: center; + justify-content: center; + text-align: center; + border: 3px solid #F2F2F5; + border-radius: 50%; + width: 64px; + height: 64px; + margin: 0 auto; + } } .goal__result { diff --git a/src/app/goal/_components/step1.tsx b/src/app/goal/_components/step1.tsx index 1e51baa..8b4847d 100644 --- a/src/app/goal/_components/step1.tsx +++ b/src/app/goal/_components/step1.tsx @@ -94,7 +94,9 @@ const Step1 = ({ data, setData }) => { const isSelected = selectedGoals.some((g) => g.name === goal.label) return ( - +
+ +
p { color: #8D8D8D; @@ -13,6 +14,7 @@ .goal__period { margin-top: 50px; + h2 { font-size: 18px; text-align: center; @@ -22,21 +24,26 @@ .goal__times { margin-top: 89px; + > h3 { font-size: 16px; font-weight: 600; } + > div { margin-top: 16px; + span { font-size: 15px; } + input { border: 1px solid #D8DADB; border-radius: 6px; width: 107px; height: 42px; margin: 0 8px; + text-align: right; } } } \ No newline at end of file diff --git a/src/app/goal/_components/step2.tsx b/src/app/goal/_components/step2.tsx index b1a922e..28bd864 100644 --- a/src/app/goal/_components/step2.tsx +++ b/src/app/goal/_components/step2.tsx @@ -1,18 +1,33 @@ -"use client"; -import { useState } from "react"; -import classNames from "classnames/bind"; -import styles from "./step2.module.scss"; -import DefaultIcon from "@/assets/icons/goals/icon-default.svg" -const cx = classNames.bind(styles); -import BackHeader from "@/components/layout/header/BackHeader"; -import StepBox from "@/app/goal/_components/stepBox"; -import Goal1 from "@/assets/icons/sticker/compliment-sticker-1.svg" -import AddButton from "@/components/common/addbutton/addbutton"; -import CustomCalendar from "@/components/common/calendar/customcalendar"; -import { DateRange } from "react-day-picker"; +"use client" +import { useEffect, useState } from "react" +import classNames from "classnames/bind" +import styles from "./step2.module.scss" -const Step2 = () => { - const [selectedWeek, setSelectedWeek] = useState(); +const cx = classNames.bind(styles) +import StepBox from "@/app/goal/_components/stepBox" +import CustomCalendar from "@/components/common/calendar/customcalendar" +import { DateRange } from "react-day-picker" +import dayjs from "dayjs" + +const Step2 = ({ setData }) => { + const [selectedWeek, setSelectedWeek] = useState() + useEffect(() => { + if (selectedWeek?.from && selectedWeek?.to) { + setData((prev) => ({ + ...prev, + startDate: dayjs(selectedWeek.from).format("YYYY-MM-DD"), + endDate: dayjs(selectedWeek.to).format("YYYY-MM-DD"), + })) + } + }, [selectedWeek, setData]) + + const handleCertCountChange = (e) => { + const value = Math.min(Number(e.target.value), 5) + setData((prev) => ({ + ...prev, + certificationCount: value, + })) + } return ( <> @@ -26,24 +41,28 @@ const Step2 = () => {
- +

인증횟수(최대 5회 가능)

인증 - +
- ); -}; + ) +} -export default Step2; \ No newline at end of file +export default Step2 diff --git a/src/app/goal/create/page.tsx b/src/app/goal/create/page.tsx index 29509be..12a1a6c 100644 --- a/src/app/goal/create/page.tsx +++ b/src/app/goal/create/page.tsx @@ -19,23 +19,38 @@ const Page = () => { icon: "", }) + const transformData = (rawData) => { + const goals = Object.entries(rawData) + .filter(([key]) => !["startDate", "endDate", "certificationCount"].includes(key)) + .map(([_, value], index) => ({ + name: value.name, + iconId: index + 1, + })) + + return { + startDate: rawData.startDate, + endDate: rawData.endDate, + certificationCount: rawData.certificationCount, + goals, + } + } + + const requestData = transformData(data) + const handleNext = () => { setStep((prev) => (prev < 3 ? ((prev + 1) as 1 | 2 | 3) : prev)) } - const handlePrev = () => { - setStep((prev) => (prev > 1 ? ((prev - 1) as 1 | 2 | 3) : prev)) - } + const { mutate } = useGoalCreate(requestData) - const { mutate } = useGoalCreate(data) return (
{step === 1 && } - {step === 2 && } - {step === 3 && } + {step === 2 && } + {step === 3 && } - +
) } diff --git a/src/app/goal/detail/@modal/goal-modal/goalmodal.module.scss b/src/app/goal/detail/@modal/goal-modal/goalmodal.module.scss index acf0cd7..c9d3f27 100644 --- a/src/app/goal/detail/@modal/goal-modal/goalmodal.module.scss +++ b/src/app/goal/detail/@modal/goal-modal/goalmodal.module.scss @@ -1,24 +1,42 @@ +.calendar { + > div { + padding: 0 10px; + + > div:first-of-type { + display: none; + } + } +} + .upload { padding-top: 20px; border-top: 1px solid #D9D9D9; } + .uploadtitle { display: flex; align-items: center; justify-content: space-between; padding: 0 30px; + h3 { font-size: 20px; font-weight: 700; } + p { color: $gray-80; font-size: 18px; } } +.smallcalendar_calendar-header { + //display: none; +} + .uploadimage { padding: 20px 30px; + span { display: flex; align-items: center; @@ -29,6 +47,7 @@ color: #727793; border-radius: 6px; } + label { display: inline-block; width: 240px; @@ -36,6 +55,7 @@ background-size: cover; border-radius: 6px; } + input { display: none; } diff --git a/src/app/goal/detail/@modal/goal-modal/page.tsx b/src/app/goal/detail/@modal/goal-modal/page.tsx index fd00333..9d0a17f 100644 --- a/src/app/goal/detail/@modal/goal-modal/page.tsx +++ b/src/app/goal/detail/@modal/goal-modal/page.tsx @@ -1,63 +1,78 @@ "use client" -import BottomSheet from "@/components/common/buttomsheet/bottomsheet"; -import { useRouter } from "next/navigation"; -import SmallCalendar from "@/components/common/smallcalendar/smallcalendar"; +import BottomSheet from "@/components/common/buttomsheet/bottomsheet" +import { useRouter, useSearchParams } from "next/navigation" +import SmallCalendar from "@/components/common/smallcalendar/smallcalendar" import styles from "./goalmodal.module.scss" -import React, {useState} from "react"; -import Button from "@/components/common/button/button"; +import React, { useState } from "react" +import Button from "@/components/common/button/button" +import dayjs from "dayjs" +import { useGoalCertifyQuery } from "@/queries/goal/userGoalQuery" const GoalModal = () => { - const router = useRouter(); - const [imagePreview, setImagePreview] = useState(null); + const router = useRouter() + const today = dayjs().format("YYYY-MM-DD") + const searchParams = useSearchParams() + const goalId = searchParams.get("goalId") + const [clickedDate, setClickedDate] = useState(today) + const [imagePreview, setImagePreview] = useState(null) + const { mutate } = useGoalCertifyQuery(goalId) const handleImageChange = (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (!file) return; + const file = e.target.files?.[0] + if (!file) return - const reader = new FileReader(); + const reader = new FileReader() reader.onloadend = () => { - setImagePreview(reader.result as string); - }; - reader.readAsDataURL(file); - }; + setImagePreview(reader.result as string) + } + reader.readAsDataURL(file) + } const closeModal = () => { - router.back(); - }; + router.back() + } + + const onSubmit = () => { + if (!imagePreview) { + alert("이미지를 업로드해주세요.") + return + } + const formData = new FormData() + formData.append("image", imagePreview) + mutate(formData) + } return ( - +
+ +

20분동안 걷기

0/3회 인증

- +
-
) } -export default GoalModal; \ No newline at end of file +export default GoalModal diff --git a/src/app/goal/detail/@modal/letter-modal/page.tsx b/src/app/goal/detail/@modal/letter-modal/page.tsx index bcf4d75..195cdb9 100644 --- a/src/app/goal/detail/@modal/letter-modal/page.tsx +++ b/src/app/goal/detail/@modal/letter-modal/page.tsx @@ -1,24 +1,44 @@ "use client" -import React from "react"; -import { useRouter } from "next/navigation"; +import React, { useState } from "react" +import { useRouter, useSearchParams } from "next/navigation" import styles from "./lettermodal.module.scss" -import BottomSheet from "@/components/common/buttomsheet/bottomsheet"; +import BottomSheet from "@/components/common/buttomsheet/bottomsheet" import ProfileIcon from "@/assets/character/profile-default.svg" -import Button from "@/components/common/button/button"; +import Button from "@/components/common/button/button" +import { useGoalLetter } from "@/queries/goal/userGoalQuery" + const LetterModal = () => { - const router = useRouter(); + const router = useRouter() + const searchParams = useSearchParams() + const goalId = searchParams.get("goalId") + const [letterContent, setLetterContent] = useState("") + const { mutate } = useGoalLetter(goalId) const closeModal = () => { - router.back(); - }; + router.back() + } + const onSubmit = () => { + if (!letterContent.trim()) { + alert("편지 내용을 입력해주세요.") + return + } + + mutate({ content: letterContent }) // 여기에 맞게 payload 구조 조정 + } return (
- +

To. 미니준

-

블라블라블라

+

+