From 41ef3d6eb2a251e9a9e36e8bcdc53bae420b094f Mon Sep 17 00:00:00 2001 From: Park-min-hyoung Date: Sat, 1 Mar 2025 15:16:48 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20=ED=99=88=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=84=9C=EB=B2=84=20DTO=20=EA=B8=B0=EB=B0=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/auth/routes/GoogleCallback.tsx | 7 ++++-- src/features/auth/routes/KakaoCallback.tsx | 7 ++++-- src/features/home/api/home.ts | 9 +++++--- src/features/home/api/paths.ts | 5 ++++- src/features/home/components/goal-list.tsx | 18 ++++++++-------- src/features/home/components/home.tsx | 24 ++++++++++++--------- src/features/home/components/index.const.ts | 11 +++++----- src/features/home/types/index.ts | 10 ++++----- src/lib/react-query/queryOptions/home.ts | 6 +++--- src/stores/user.ts | 8 +++++-- 10 files changed, 62 insertions(+), 43 deletions(-) diff --git a/src/features/auth/routes/GoogleCallback.tsx b/src/features/auth/routes/GoogleCallback.tsx index c0e932f..3a26b2e 100644 --- a/src/features/auth/routes/GoogleCallback.tsx +++ b/src/features/auth/routes/GoogleCallback.tsx @@ -7,9 +7,11 @@ import { postGoogleLogin } from "@/features/auth/api/auth"; import { useNavigate, useSearchParams } from "react-router"; import { useAuthStore } from "@/stores/auth-store"; +import { useUserStore } from "@/stores/user"; import { paths } from "@/config/paths"; const GoogleCallback = (): JSX.Element | null => { + const { setUserId } = useUserStore(); const [searchParams] = useSearchParams(); const navigate = useNavigate(); const setTokens = useAuthStore((state) => state.setTokens); @@ -25,9 +27,10 @@ const GoogleCallback = (): JSX.Element | null => { throw new Error("구글 인증에 실패했습니다."); } - const { accessToken, refreshToken } = response.data; + const { accessToken, refreshToken, id } = response.data; setTokens(accessToken, refreshToken); + setUserId(id); navigate(paths.home.path); } catch (error) { @@ -37,7 +40,7 @@ const GoogleCallback = (): JSX.Element | null => { setIsLoading(false); } }, - [navigate, setTokens] + [navigate, setTokens, setUserId] ); useEffect(() => { diff --git a/src/features/auth/routes/KakaoCallback.tsx b/src/features/auth/routes/KakaoCallback.tsx index a45d336..8d25376 100644 --- a/src/features/auth/routes/KakaoCallback.tsx +++ b/src/features/auth/routes/KakaoCallback.tsx @@ -7,9 +7,11 @@ import { postKakaoLogin } from "@/features/auth/api/auth"; import { useNavigate, useSearchParams } from "react-router"; import { useAuthStore } from "@/stores/auth-store"; +import { useUserStore } from "@/stores/user"; import { paths } from "@/config/paths"; const KakaoCallback = (): JSX.Element | null => { + const { setUserId } = useUserStore(); const [searchParams] = useSearchParams(); const navigate = useNavigate(); const setTokens = useAuthStore((state) => state.setTokens); @@ -27,9 +29,10 @@ const KakaoCallback = (): JSX.Element | null => { throw new Error("카카오 인증에 실패했습니다."); } - const { accessToken, refreshToken } = response.data; + const { accessToken, refreshToken, id } = response.data; setTokens(accessToken, refreshToken); + setUserId(id); navigate(paths.home.path); } catch (error) { @@ -39,7 +42,7 @@ const KakaoCallback = (): JSX.Element | null => { setIsLoading(false); } }, - [navigate, setTokens] + [navigate, setTokens, setUserId] ); useEffect(() => { diff --git a/src/features/home/api/home.ts b/src/features/home/api/home.ts index d91b818..9823a7c 100644 --- a/src/features/home/api/home.ts +++ b/src/features/home/api/home.ts @@ -1,8 +1,11 @@ import { R } from "@/types/common"; import { GET } from "@/lib/axios"; +import { RoOnlyPathParamsType } from "@/lib/axios/utils"; import { Goals } from "../types"; -import { BASE_PATH } from "./paths"; +import { HOME_PATH } from "./paths"; -export function getGoals(): R { - return GET({ url: BASE_PATH }); +export function getGoals({ + pathParams: { userId } +}: RoOnlyPathParamsType<{ userId: number }>): R { + return GET({ url: HOME_PATH(userId) }); } diff --git a/src/features/home/api/paths.ts b/src/features/home/api/paths.ts index 0b3de6d..058926d 100644 --- a/src/features/home/api/paths.ts +++ b/src/features/home/api/paths.ts @@ -1,3 +1,6 @@ -import { genreateBasePath } from "@/lib/axios/utils"; +import { generatePathByBase, genreateBasePath } from "@/lib/axios/utils"; export const BASE_PATH = genreateBasePath("goals", "v1"); + +export const HOME_PATH = (userId: number) => + generatePathByBase(BASE_PATH, String(userId)); diff --git a/src/features/home/components/goal-list.tsx b/src/features/home/components/goal-list.tsx index c05a549..dbdecaf 100644 --- a/src/features/home/components/goal-list.tsx +++ b/src/features/home/components/goal-list.tsx @@ -24,35 +24,35 @@ function GoalList({ goalCount, transformGoals }: GoalListProps) { ({ id, title, - name, + goalName, lastRowText, - isAchieved, - isTemporarySaved, + achievedToday, + status, buttonText, redirectionUrl }) => (

{title}

- {name} + {goalName}

{lastRowText}

diff --git a/src/features/home/components/home.tsx b/src/features/home/components/home.tsx index b553594..3ee447e 100644 --- a/src/features/home/components/home.tsx +++ b/src/features/home/components/home.tsx @@ -8,13 +8,15 @@ import { useQuery } from "@tanstack/react-query"; import { map } from "es-toolkit/compat"; import { Link } from "react-router"; +import { useUserStore } from "@/stores/user"; import { paths } from "@/config/paths"; import { generate_qo_getGoals } from "@/lib/react-query/queryOptions/home"; import GoalList from "./goal-list"; import { generatDdateText, generateDayText } from "./index.const"; const HomePage = () => { - const { data: goals = [] } = useQuery(generate_qo_getGoals()); + const { userId } = useUserStore(); + const { data: goals = [] } = useQuery(generate_qo_getGoals(userId)); const goalCount = useMemo(() => { if (goals.length === 0) return 0; @@ -41,18 +43,20 @@ const HomePage = () => { const transformGoals = useMemo( () => map(goals, (item) => { - const lastRowText = item.isTemporarySaved - ? "목표등록을 마무리하고 바로 시작해보세요!" - : `${generatDdateText(item.startDate, item.endDate)} ${generateDayText(item.days)}`; - const buttonText = `${item.isTemporarySaved ? "완성하기" : "인증하기"} >`; - const redirectionUrl = item.isTemporarySaved - ? paths.goal.create.getHref() - : paths.map.certification.getHref(); + const lastRowText = + item.status === "DRAF" + ? "목표등록을 마무리하고 바로 시작해보세요!" + : `${generatDdateText(item.startDate, item.endDate)} ${generateDayText(item.dayOfWeek)}`; + const buttonText = `${item.status === "DRAF" ? "완성하기" : "인증하기"} >`; + const redirectionUrl = + item.status === "DRAF" + ? paths.goal.create.getHref() + : paths.map.certification.getHref(); return { ...item, - title: `${item.isTemporarySaved ? "임시저장" : "진행중"} 목표`, - name: item.name, + title: `${item.status === "DRAF" ? "임시저장" : "진행중"} 목표`, + name: item.goalName, lastRowText, buttonText, redirectionUrl diff --git a/src/features/home/components/index.const.ts b/src/features/home/components/index.const.ts index 16165b5..9a866b6 100644 --- a/src/features/home/components/index.const.ts +++ b/src/features/home/components/index.const.ts @@ -1,8 +1,6 @@ import { join, map, replace } from "es-toolkit/compat"; -import { WeekDay } from "../types"; - -export const DAYS_STRING_MAP = new Map([ +export const DAYS_STRING_MAP = new Map([ ["MON", "월"], ["TUE", "화"], ["WED", "수"], @@ -19,9 +17,10 @@ export const generatDdateText = (startDate: string, endDate: string) => { return `${replacedStartDate} ~ ${replacedEndDate}`; }; -export const generateDayText = (days: WeekDay[]) => { - const transformDays = map(days, (day) => DAYS_STRING_MAP.get(day)); - const day = days.length === 7 ? "매일" : join(transformDays, ", "); +export const generateDayText = (days: string) => { + const dayArr = days.split(","); + const transformDays = map(dayArr, (day) => DAYS_STRING_MAP.get(day)); + const day = dayArr.length === 7 ? "매일" : join(transformDays, ", "); return day; }; diff --git a/src/features/home/types/index.ts b/src/features/home/types/index.ts index c66c55e..d109624 100644 --- a/src/features/home/types/index.ts +++ b/src/features/home/types/index.ts @@ -1,13 +1,13 @@ -export type WeekDay = "MON" | "TUE" | "WED" | "THU" | "FRI" | "SAT" | "SUN"; +export type STATUS = "DRAF" | "ACTIVE" | "COMPLETE"; export interface Goals { id: number; - name: string; + goalName: string; startDate: string; endDate: string; - days: WeekDay[]; - isTemporarySaved: boolean; - isAchieved: boolean; + dayOfWeek: string; + status: STATUS; + achievedToday: boolean; } export type TransformedGoals = Goals & { diff --git a/src/lib/react-query/queryOptions/home.ts b/src/lib/react-query/queryOptions/home.ts index f4d1351..d91096a 100644 --- a/src/lib/react-query/queryOptions/home.ts +++ b/src/lib/react-query/queryOptions/home.ts @@ -6,13 +6,13 @@ import { AxiosError } from "axios"; interface UseQueryGoalsOptions extends UseQueryOptions {} -type GenerateQoGetGoals = () => UseQueryGoalsOptions; +type GenerateQoGetGoals = (userId: number) => UseQueryGoalsOptions; -export const generate_qo_getGoals: GenerateQoGetGoals = () => { +export const generate_qo_getGoals: GenerateQoGetGoals = (userId) => { return { queryKey: [BASE_PATH], queryFn: () => - getGoals().then((data) => { + getGoals({ pathParams: { userId } }).then((data) => { return data; }) }; diff --git a/src/stores/user.ts b/src/stores/user.ts index eaf2f56..c3bbb1d 100644 --- a/src/stores/user.ts +++ b/src/stores/user.ts @@ -3,11 +3,15 @@ import { create } from "zustand"; interface UserState { userName: string; point: number; + userId: number; + setUserId: (userId: number) => void; setPoint: (point: number) => void; } export const useUserStore = create((set) => ({ - userName: "park", - point: 100000, + userName: "", + point: 0, + userId: 0, + setUserId: (userId) => set({ userId }), setPoint: (point) => set({ point }) })); From fb9c94562ef13a572f18412b8d724824b15da125 Mon Sep 17 00:00:00 2001 From: Park-min-hyoung Date: Sat, 1 Mar 2025 16:51:47 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat:=20=EB=AA=A9=ED=91=9C=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=84=9C=EB=B2=84=20?= =?UTF-8?q?DTO=20=EA=B8=B0=EB=B0=98=20=EC=88=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/home/components/goal-list.tsx | 19 ++++---- src/features/home/components/home.tsx | 19 +++++--- src/features/home/types/index.ts | 2 +- src/features/map-certification/api/index.ts | 25 +++++++---- src/features/map-certification/api/paths.ts | 10 +++-- .../components/map-certification.const.ts | 4 +- .../components/map-certification.tsx | 21 ++++++--- src/features/map-certification/types/index.ts | 11 ++--- src/lib/react-query/queryOptions/goals.ts | 43 +++++++++++++------ 9 files changed, 97 insertions(+), 57 deletions(-) diff --git a/src/features/home/components/goal-list.tsx b/src/features/home/components/goal-list.tsx index dbdecaf..5b2c050 100644 --- a/src/features/home/components/goal-list.tsx +++ b/src/features/home/components/goal-list.tsx @@ -1,5 +1,4 @@ import { map } from "es-toolkit/compat"; -import { Link } from "react-router"; import type { TransformedGoals } from "../types"; @@ -29,7 +28,7 @@ function GoalList({ goalCount, transformGoals }: GoalListProps) { achievedToday, status, buttonText, - redirectionUrl + redirectionCallback }) => (

{lastRowText}

- - - + +
) diff --git a/src/features/home/components/home.tsx b/src/features/home/components/home.tsx index 3ee447e..8faf709 100644 --- a/src/features/home/components/home.tsx +++ b/src/features/home/components/home.tsx @@ -6,7 +6,7 @@ import ActvieAddIcon from "@/asset/common/plus-actvie.svg?react"; import InactiveAddIcon from "@/asset/common/plus-inactive.svg?react"; import { useQuery } from "@tanstack/react-query"; import { map } from "es-toolkit/compat"; -import { Link } from "react-router"; +import { Link, useNavigate } from "react-router"; import { useUserStore } from "@/stores/user"; import { paths } from "@/config/paths"; @@ -15,6 +15,7 @@ import GoalList from "./goal-list"; import { generatDdateText, generateDayText } from "./index.const"; const HomePage = () => { + const navigate = useNavigate(); const { userId } = useUserStore(); const { data: goals = [] } = useQuery(generate_qo_getGoals(userId)); @@ -48,10 +49,14 @@ const HomePage = () => { ? "목표등록을 마무리하고 바로 시작해보세요!" : `${generatDdateText(item.startDate, item.endDate)} ${generateDayText(item.dayOfWeek)}`; const buttonText = `${item.status === "DRAF" ? "완성하기" : "인증하기"} >`; - const redirectionUrl = - item.status === "DRAF" - ? paths.goal.create.getHref() - : paths.map.certification.getHref(); + const redirectionCallback = () => { + const url = + item.status === "DRAF" + ? paths.goal.create.getHref() + : paths.map.certification.getHref(); + + navigate(url, { state: { goalId: item.id, userId } }); + }; return { ...item, @@ -59,10 +64,10 @@ const HomePage = () => { name: item.goalName, lastRowText, buttonText, - redirectionUrl + redirectionCallback }; }), - [goals] + [goals, navigate, userId] ); return ( diff --git a/src/features/home/types/index.ts b/src/features/home/types/index.ts index d109624..362b7c7 100644 --- a/src/features/home/types/index.ts +++ b/src/features/home/types/index.ts @@ -14,5 +14,5 @@ export type TransformedGoals = Goals & { title: string; lastRowText: string; buttonText: string; - redirectionUrl: string; + redirectionCallback: () => void; }; diff --git a/src/features/map-certification/api/index.ts b/src/features/map-certification/api/index.ts index 71ea063..1b27078 100644 --- a/src/features/map-certification/api/index.ts +++ b/src/features/map-certification/api/index.ts @@ -1,17 +1,24 @@ import type { R } from "@/types/common"; import { GET, POST } from "@/lib/axios"; -import type { RoOnlyPathParamsType } from "@/lib/axios/utils"; -import type { Goals } from "../types"; -import { GOALS, GOALS_ACHIEVE } from "./paths"; +import type { + RoDataAndPathParamsType, + RoOnlyPathParamsType +} from "@/lib/axios/utils"; +import type { GoalDetail } from "../types"; +import { GOALS_ACHIEVE, GOALS_DETAIL } from "./paths"; export function getGoals({ - pathParams: { id } -}: RoOnlyPathParamsType<{ id: number }>): R { - return GET({ url: GOALS(id) }); + pathParams: { goalId } +}: RoOnlyPathParamsType<{ goalId: number }>): R { + return GET({ url: GOALS_DETAIL(goalId) }); } export function postGoalsAchieve({ - pathParams: { id } -}: RoOnlyPathParamsType<{ id: number }>): R<{ point: number }> { - return POST({ url: GOALS_ACHIEVE(id) }); + pathParams: { goalId }, + data +}: RoDataAndPathParamsType< + { userId: number; latitude: number; longitude: number }, + { goalId: number } +>): R<{ point: number }> { + return POST({ url: GOALS_ACHIEVE(goalId), data }); } diff --git a/src/features/map-certification/api/paths.ts b/src/features/map-certification/api/paths.ts index 6f6c46f..e7116af 100644 --- a/src/features/map-certification/api/paths.ts +++ b/src/features/map-certification/api/paths.ts @@ -2,7 +2,11 @@ import { generatePathByBase, genreateBasePath } from "@/lib/axios/utils"; export const BASE_PATH = genreateBasePath("goals", "v1"); -export const GOALS = (id: number) => generatePathByBase(BASE_PATH, String(id)); -export const GOALS_ACHIEVE = (id: number) => { - return generatePathByBase(GOALS(id), "achieve"); +export const GOALS_CHECK = generatePathByBase(BASE_PATH, "check"); + +export const GOALS_DETAIL = (goalId: number) => + generatePathByBase(GOALS_CHECK, String(goalId)); + +export const GOALS_ACHIEVE = (goalId: number) => { + return generatePathByBase(BASE_PATH, String(goalId), "achieve"); }; diff --git a/src/features/map-certification/components/map-certification.const.ts b/src/features/map-certification/components/map-certification.const.ts index 720ff71..5b5b7b0 100644 --- a/src/features/map-certification/components/map-certification.const.ts +++ b/src/features/map-certification/components/map-certification.const.ts @@ -1,11 +1,11 @@ export const generateInitialGoalsData = () => ({ id: 0, - name: "", + goalName: "", startDate: "", endDate: "", latitude: 33.450701, longitude: 126.570667, - days: [] + dayOfWeek: "" }); export const DAYS_STRING_MAP = new Map([ diff --git a/src/features/map-certification/components/map-certification.tsx b/src/features/map-certification/components/map-certification.tsx index f237dae..cd7d2d8 100644 --- a/src/features/map-certification/components/map-certification.tsx +++ b/src/features/map-certification/components/map-certification.tsx @@ -8,7 +8,7 @@ import { getDistance } from "@/utils/map"; import { useMutation, useQuery } from "@tanstack/react-query"; import { join, map, replace } from "es-toolkit/compat"; import { Circle, Map, MapMarker } from "react-kakao-maps-sdk"; -import { useNavigate } from "react-router"; +import { useLocation, useNavigate } from "react-router"; import { generate_qo_getGoals, @@ -27,21 +27,29 @@ const DISTANCE_DIFFRENCE = 20; function MapCertification() { // Hooks const navigate = useNavigate(); + const location = useLocation(); + const { userId, goalId } = location.state || {}; + const { center, position, setCenterToMyPosition, updateCenterWhenMapMoved } = useUserLocation(); const { data: { - name, + goalName: name, startDate, endDate, - days, + dayOfWeek: days, latitude: serverLatitude, longitude: serverLongitude } = generateInitialGoalsData() } = useQuery(generate_qo_getGoals(1)); const { mutate, isPending } = useMutation({ - ...generate_qo_postGoalsAchieve(1), + ...generate_qo_postGoalsAchieve({ + goalId, + userId, + latitude: position.lat, + longitude: position.lng + }), onSuccess: (data) => { /** @todo 홈 페이지에 사용도되는 데이터 쿼리 무효화 필요, 응답값으로 point 받아 성공 페이지로 전달 */ @@ -68,8 +76,9 @@ function MapCertification() { }, [serverLatitude, serverLongitude, position]); const dayString = useMemo(() => { - const transformDays = map(days, (day) => DAYS_STRING_MAP.get(day)); - const day = days.length === 7 ? "매일" : join(transformDays, ", "); + const daysArr = days.split(","); + const transformDays = map(daysArr, (day) => DAYS_STRING_MAP.get(day)); + const day = daysArr.length === 7 ? "매일" : join(transformDays, ", "); return day; }, [days]); diff --git a/src/features/map-certification/types/index.ts b/src/features/map-certification/types/index.ts index c6936bc..666e3f1 100644 --- a/src/features/map-certification/types/index.ts +++ b/src/features/map-certification/types/index.ts @@ -1,11 +1,12 @@ -type WeekDay = "MON" | "TUE" | "WED" | "THU" | "FRI" | "SAT" | "SUN"; +export type STATUS = "DRAF" | "ACTIVE" | "COMPLETE"; -export interface Goals { - id: number; - name: string; +export interface GoalDetail { + goalName: string; startDate: string; endDate: string; latitude: number; longitude: number; - days: WeekDay[]; + dayOfWeek: string; + status: STATUS; + achievedToday: boolean; } diff --git a/src/lib/react-query/queryOptions/goals.ts b/src/lib/react-query/queryOptions/goals.ts index 9ed6beb..2cb296a 100644 --- a/src/lib/react-query/queryOptions/goals.ts +++ b/src/lib/react-query/queryOptions/goals.ts @@ -2,33 +2,48 @@ import { getGoalsCheck, getGoalsComplete } from "@/features/goal/api/goal"; import { GOALS_CHECK, GOALS_COMPLETE } from "@/features/goal/api/path"; import type { CompleteGoal, ProgressGoal } from "@/features/goal/types"; import { getGoals, postGoalsAchieve } from "@/features/map-certification/api"; -import { GOALS } from "@/features/map-certification/api/paths"; -import { Goals } from "@/features/map-certification/types"; +import { GOALS_DETAIL } from "@/features/map-certification/api/paths"; +import { GoalDetail } from "@/features/map-certification/types"; import { simpleGenerateSecond } from "@/utils/date"; import { UseMutationOptions, UseQueryOptions } from "@tanstack/react-query"; import { AxiosError } from "axios"; interface UseQueryGoalsOptions - extends UseQueryOptions {} -type GenerateQoGetGoals = (id: number) => UseQueryGoalsOptions; -export const generate_qo_getGoals: GenerateQoGetGoals = (id) => { + extends UseQueryOptions {} +type GenerateQoGetGoals = (goalId: number) => UseQueryGoalsOptions; +export const generate_qo_getGoals: GenerateQoGetGoals = (goalId) => { return { - queryKey: [GOALS(id)], - queryFn: () => getGoals({ pathParams: { id } }).then((data) => data), + queryKey: [GOALS_DETAIL(goalId)], + queryFn: () => getGoals({ pathParams: { goalId } }).then((data) => data), staleTime: simpleGenerateSecond([[10, "s"]]) }; }; interface UseMutationGoalsAchieveOptions extends UseMutationOptions<{ point: number }, AxiosError, void, unknown> {} -type GenerateQoPostGoalsAchieve = ( - id: number -) => UseMutationGoalsAchieveOptions; -export const generate_qo_postGoalsAchieve: GenerateQoPostGoalsAchieve = ( - id -) => { +type GenerateQoPostGoalsAchieve = ({ + goalId, + userId, + latitude, + longitude +}: { + goalId: number; + userId: number; + latitude: number; + longitude: number; +}) => UseMutationGoalsAchieveOptions; +export const generate_qo_postGoalsAchieve: GenerateQoPostGoalsAchieve = ({ + goalId, + userId, + latitude, + longitude +}) => { return { - mutationFn: () => postGoalsAchieve({ pathParams: { id } }) + mutationFn: () => + postGoalsAchieve({ + pathParams: { goalId }, + data: { userId, latitude, longitude } + }) }; }; From 1efb2e8292d287ab68e4bd2830eee7f7f0868f6e Mon Sep 17 00:00:00 2001 From: Park-min-hyoung Date: Sat, 1 Mar 2025 17:44:48 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20=EB=AA=A9=ED=91=9C=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=84=9C=EB=B2=84=20DTO=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/goal/api/goal.ts | 13 ++++++---- src/features/goal/api/path.ts | 3 ++- .../goal/components/goal/complete-goal.tsx | 12 +++++++--- .../goal/components/goal/progress-goal.tsx | 24 ++++++++++--------- src/features/goal/types/index.ts | 19 ++++++++------- src/lib/react-query/queryOptions/goals.ts | 17 ++++++++----- 6 files changed, 55 insertions(+), 33 deletions(-) diff --git a/src/features/goal/api/goal.ts b/src/features/goal/api/goal.ts index a5d8690..211bcf1 100644 --- a/src/features/goal/api/goal.ts +++ b/src/features/goal/api/goal.ts @@ -1,12 +1,17 @@ import type { R } from "@/types/common.ts"; import { GET } from "@/lib/axios"; +import { RoOnlyDataType, RoOnlyPathParamsType } from "@/lib/axios/utils"; import type { CompleteGoal, ProgressGoal } from "../types"; import { GOALS_CHECK, GOALS_COMPLETE } from "./path"; -export function getGoalsCheck(): R { - return GET({ url: GOALS_CHECK }); +export function getGoalsCheck({ + data +}: RoOnlyDataType<{ userId: number }>): R { + return GET({ url: GOALS_CHECK, data }); } -export function getGoalsComplete(): R { - return GET({ url: GOALS_COMPLETE }); +export function getGoalsComplete({ + pathParams: { userId } +}: RoOnlyPathParamsType<{ userId: number }>): R { + return GET({ url: GOALS_COMPLETE(userId) }); } diff --git a/src/features/goal/api/path.ts b/src/features/goal/api/path.ts index bba9fe7..cec56aa 100644 --- a/src/features/goal/api/path.ts +++ b/src/features/goal/api/path.ts @@ -3,4 +3,5 @@ import { generatePathByBase, genreateBasePath } from "@/lib/axios/utils"; export const BASE_PATH = genreateBasePath("goals", "v1"); export const GOALS_CHECK = generatePathByBase(BASE_PATH, "check"); -export const GOALS_COMPLETE = generatePathByBase(BASE_PATH, "complete"); +export const GOALS_COMPLETE = (userId: number) => + generatePathByBase(GOALS_CHECK, "complete", String(userId)); diff --git a/src/features/goal/components/goal/complete-goal.tsx b/src/features/goal/components/goal/complete-goal.tsx index d1feb30..54a7009 100644 --- a/src/features/goal/components/goal/complete-goal.tsx +++ b/src/features/goal/components/goal/complete-goal.tsx @@ -3,20 +3,26 @@ import { useMemo } from "react"; import { useQuery } from "@tanstack/react-query"; import { join, map } from "es-toolkit/compat"; +import { useUserStore } from "@/stores/user"; import { generate_qo_getGoalsComplete } from "@/lib/react-query/queryOptions/goals"; import { generateDateString } from "./index.const"; function CompleteGoal() { + const { userId } = useUserStore(); const { data: completedGoals = [] } = useQuery( - generate_qo_getGoalsComplete() + generate_qo_getGoalsComplete(userId) ); const transformedCompletedGoals = useMemo( () => map(completedGoals, (goal) => { const dateString = `${generateDateString(goal.startDate)} ~ ${generateDateString(goal.endDate)}`; - const days = goal.days.length === 7 ? "매일" : join(goal.days, ","); - const achivePercentString = `${goal.achivePercent}% 달성`; + const daysArr = goal.dayOfWeek.split(","); + const days = daysArr.length === 7 ? "매일" : join(daysArr, ","); + const achivePercent = Math.floor( + (goal.achievedCount / goal.targetCount) * 100 + ); + const achivePercentString = `${achivePercent}% 달성`; return { ...goal, dateString, days, achivePercentString }; }), diff --git a/src/features/goal/components/goal/progress-goal.tsx b/src/features/goal/components/goal/progress-goal.tsx index 4f97cdb..9f8a3ad 100644 --- a/src/features/goal/components/goal/progress-goal.tsx +++ b/src/features/goal/components/goal/progress-goal.tsx @@ -3,6 +3,7 @@ import { useMemo } from "react"; import { useQuery } from "@tanstack/react-query"; import { join, map, slice } from "es-toolkit/compat"; +import { useUserStore } from "@/stores/user"; import { generate_qo_getGoalsCheck } from "@/lib/react-query/queryOptions/goals"; import type { CertificationInfo } from "../../types"; import { @@ -15,28 +16,28 @@ import { function ProgressGoal() { const todayString = generateTodyString(); + const { userId } = useUserStore(); - const { data: progressGoals = [] } = useQuery(generate_qo_getGoalsCheck()); + const { data: progressGoals = [] } = useQuery( + generate_qo_getGoalsCheck(userId) + ); const certificationInfoMaps = useMemo(() => { const generateMap = (certificationInfo: CertificationInfo[]) => { return new Map( - map(certificationInfo, ({ date, isCertification }) => [ - date, - isCertification - ]) + map(certificationInfo, ({ date, verified }) => [date, verified]) ); }; - return map(progressGoals, ({ certificationInfo }) => - generateMap(certificationInfo) + return map(progressGoals, ({ dateAuthentication }) => + generateMap(dateAuthentication) ); }, [progressGoals]); const transformedProgressGoals = useMemo( () => map(progressGoals, (goal, index) => { - const transFormViewDays = map(goal.viewDays, (viewDay) => { + const transFormViewDays = map(goal.calender, (viewDay) => { const isCertificationInfo = certificationInfoMaps[index].has(viewDay); if (!isCertificationInfo) { @@ -56,7 +57,8 @@ function ProgressGoal() { : generateNonCertificationItem({ viewDay, day, isToday }); }); const dateString = `${generateDateString(goal.startDate)} ~ ${generateDateString(goal.endDate)}`; - const days = goal.days.length === 7 ? "매일" : join(goal.days, ","); + const daysArr = goal.dayOfWeek.split(","); + const days = daysArr.length === 7 ? "매일" : join(daysArr, ","); const lastWeekDate = slice(transFormViewDays, 0, 7); const thiwWeekDate = slice(transFormViewDays, -7); @@ -73,7 +75,7 @@ function ProgressGoal() { return transformedProgressGoals.length > 0 ? ( transformedProgressGoals.map( - ({ id, name, dateString, days, goalDaycnt, allDays }, idx) => ( + ({ id, name, dateString, days, targetCount, allDays }, idx) => (
{dateString} - {goalDaycnt}일 + {targetCount}일 {days}
diff --git a/src/features/goal/types/index.ts b/src/features/goal/types/index.ts index 29db28b..89652a6 100644 --- a/src/features/goal/types/index.ts +++ b/src/features/goal/types/index.ts @@ -1,26 +1,29 @@ -import type { WeekDay } from "@/types/date"; +export type STATUS = "DRAF" | "ACTIVE" | "COMPLETE"; export interface CertificationInfo { date: string; - isCertification: boolean; + verified: boolean; } export interface ProgressGoal { id: number; + userId: number; name: string; + status: STATUS; startDate: string; endDate: string; - goalDaycnt: number; - days: WeekDay[]; - viewDays: string[]; - certificationInfo: CertificationInfo[]; + targetCount: number; + dayOfWeek: string; + calender: string[]; + dateAuthentication: CertificationInfo[]; } export interface CompleteGoal { id: number; name: string; - achivePercent: number; startDate: string; endDate: string; - days: WeekDay[]; + dayOfWeek: string; + targetCount: number; + achievedCount: number; } diff --git a/src/lib/react-query/queryOptions/goals.ts b/src/lib/react-query/queryOptions/goals.ts index 2cb296a..64ebd5c 100644 --- a/src/lib/react-query/queryOptions/goals.ts +++ b/src/lib/react-query/queryOptions/goals.ts @@ -49,20 +49,25 @@ export const generate_qo_postGoalsAchieve: GenerateQoPostGoalsAchieve = ({ interface UseQueryGoalsCheckOptions extends UseQueryOptions {} -type GenerateQoGetGoalsCheck = () => UseQueryGoalsCheckOptions; -export const generate_qo_getGoalsCheck: GenerateQoGetGoalsCheck = () => { +type GenerateQoGetGoalsCheck = (userId: number) => UseQueryGoalsCheckOptions; +export const generate_qo_getGoalsCheck: GenerateQoGetGoalsCheck = (userId) => { return { queryKey: [GOALS_CHECK], - queryFn: () => getGoalsCheck().then((data) => data) + queryFn: () => getGoalsCheck({ data: { userId } }).then((data) => data) }; }; interface UseQueryGoalsCompleteOptions extends UseQueryOptions {} -type GenerateQoGetGoalsComplete = () => UseQueryGoalsCompleteOptions; -export const generate_qo_getGoalsComplete: GenerateQoGetGoalsComplete = () => { +type GenerateQoGetGoalsComplete = ( + userId: number +) => UseQueryGoalsCompleteOptions; +export const generate_qo_getGoalsComplete: GenerateQoGetGoalsComplete = ( + userId +) => { return { queryKey: [GOALS_COMPLETE], - queryFn: () => getGoalsComplete().then((data) => data) + queryFn: () => + getGoalsComplete({ pathParams: { userId } }).then((data) => data) }; }; From 5220236b2029f12dbfc699b8f5c17b105e39fe3e Mon Sep 17 00:00:00 2001 From: Park-min-hyoung Date: Sat, 1 Mar 2025 18:30:11 +0900 Subject: [PATCH 04/10] =?UTF-8?q?feat:=20=EB=A6=AC=EC=9B=8C=EB=93=9C=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=84=9C=EB=B2=84=20DTO=20?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=20=EC=88=98=EC=A0=95(1=EC=B0=A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/reward/api/reward.ts | 12 ++++++++---- src/features/reward/components/reward.tsx | 5 +++++ src/lib/react-query/queryOptions/reward.ts | 7 ++++++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/features/reward/api/reward.ts b/src/features/reward/api/reward.ts index 93d9ef5..46d3497 100644 --- a/src/features/reward/api/reward.ts +++ b/src/features/reward/api/reward.ts @@ -1,10 +1,14 @@ import type { R } from "@/types/common"; import { POST } from "@/lib/axios"; -import type { RoOnlyPathParamsType } from "@/lib/axios/utils"; +import type { RoDataAndPathParamsType } from "@/lib/axios/utils"; import { POINT_DEDUC } from "./path"; export function postRewards({ - pathParams: { socialId } -}: RoOnlyPathParamsType<{ socialId: number }>): R<{ point: number }> { - return POST({ url: POINT_DEDUC(socialId) }); + pathParams: { socialId }, + data +}: RoDataAndPathParamsType<{ pointType: string }, { socialId: number }>): R<{ + point: number; + status: string; +}> { + return POST({ url: POINT_DEDUC(socialId), data }); } diff --git a/src/features/reward/components/reward.tsx b/src/features/reward/components/reward.tsx index 88963cc..de45295 100644 --- a/src/features/reward/components/reward.tsx +++ b/src/features/reward/components/reward.tsx @@ -15,6 +15,11 @@ export default function Reward() { const { mutate, isPending } = useMutation({ ...generate_qo_postRewards(1), onSuccess: (data) => { + if (data.status === "error") { + toast.error("리워드 신청에 실패했습니다."); + return; + } + setPoint(data.point); toast(, { position: "bottom-center", diff --git a/src/lib/react-query/queryOptions/reward.ts b/src/lib/react-query/queryOptions/reward.ts index 5c9b23c..bbea15e 100644 --- a/src/lib/react-query/queryOptions/reward.ts +++ b/src/lib/react-query/queryOptions/reward.ts @@ -3,7 +3,12 @@ import { UseMutationOptions } from "@tanstack/react-query"; import { AxiosError } from "axios"; interface UseMutationRewardsOptions - extends UseMutationOptions<{ point: number }, AxiosError, void, unknown> {} + extends UseMutationOptions< + { point: number; status: string }, + AxiosError, + void, + unknown + > {} type GenerateQoPostRewards = (id: number) => UseMutationRewardsOptions; export const generate_qo_postRewards: GenerateQoPostRewards = (socialId) => { return { From 9b440108f6325e4b493394dee3e5a726ac1e3992 Mon Sep 17 00:00:00 2001 From: Park-min-hyoung Date: Sun, 2 Mar 2025 12:47:47 +0900 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20=EB=A6=AC=EC=9B=8C=EB=93=9C=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=84=9C=EB=B2=84=20DTO=20?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=20=EC=88=98=EC=A0=95(2=EC=B0=A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/reward/api/path.ts | 4 +- src/features/reward/api/reward.ts | 9 ++- src/features/reward/components/index.const.ts | 12 ++-- src/features/reward/components/reward.tsx | 68 +++++++++++++------ src/lib/react-query/queryOptions/reward.ts | 15 ++-- 5 files changed, 73 insertions(+), 35 deletions(-) diff --git a/src/features/reward/api/path.ts b/src/features/reward/api/path.ts index e8b2618..2bec968 100644 --- a/src/features/reward/api/path.ts +++ b/src/features/reward/api/path.ts @@ -2,5 +2,5 @@ import { generatePathByBase, genreateBasePath } from "@/lib/axios/utils"; export const BASE_PATH = genreateBasePath("points", "v1"); -export const POINT_DEDUC = (socialId) => - generatePathByBase(BASE_PATH, socialId, "deduc"); +export const POINT_DEDUC = (userId) => + generatePathByBase(BASE_PATH, userId, "deduc"); diff --git a/src/features/reward/api/reward.ts b/src/features/reward/api/reward.ts index 46d3497..ffa8377 100644 --- a/src/features/reward/api/reward.ts +++ b/src/features/reward/api/reward.ts @@ -4,11 +4,14 @@ import type { RoDataAndPathParamsType } from "@/lib/axios/utils"; import { POINT_DEDUC } from "./path"; export function postRewards({ - pathParams: { socialId }, + pathParams: { userId }, data -}: RoDataAndPathParamsType<{ pointType: string }, { socialId: number }>): R<{ +}: RoDataAndPathParamsType< + { points: number; pointType: string; description: string }, + { userId: number } +>): R<{ point: number; status: string; }> { - return POST({ url: POINT_DEDUC(socialId), data }); + return POST({ url: POINT_DEDUC(userId), data }); } diff --git a/src/features/reward/components/index.const.ts b/src/features/reward/components/index.const.ts index e4c0329..4ad1870 100644 --- a/src/features/reward/components/index.const.ts +++ b/src/features/reward/components/index.const.ts @@ -9,18 +9,22 @@ export const generateCoupons = (point: number, isPending: boolean) => { { id: 1, name: "스타벅스 쿠폰(5,000p 소모)", - cost: 5000, + points: 5000, image: coffeeCouponUrl, isDisabled: point < 5000, - className: `${baseClass} ${point >= 5000 && !isPending ? "bg-green-500" : "cursor-not-allowed bg-gray-400"}` + className: `${baseClass} ${point >= 5000 && !isPending ? "bg-green-500" : "cursor-not-allowed bg-gray-400"}`, + pointType: "GIFT_STARBUCKS", + description: "스타벅스 기프티콘 지급" }, { id: 2, name: "편의점 1만원 쿠폰(10,000p 소모)", - cost: 10000, + points: 10000, image: convenienceStoreCouponUrl, isDisabled: point < 10000, - className: `${baseClass} ${point >= 10000 && !isPending ? "bg-green-500" : "cursor-not-allowed bg-gray-400"}` + className: `${baseClass} ${point >= 10000 && !isPending ? "bg-green-500" : "cursor-not-allowed bg-gray-400"}`, + pointType: "GIFT_COUPON", + description: "CU 기프티콘 지급" } ]; }; diff --git a/src/features/reward/components/reward.tsx b/src/features/reward/components/reward.tsx index de45295..6dc844f 100644 --- a/src/features/reward/components/reward.tsx +++ b/src/features/reward/components/reward.tsx @@ -10,10 +10,10 @@ import { generateCoupons } from "./index.const"; import SuccessToast from "./success-toast"; export default function Reward() { - const { point, setPoint } = useUserStore(); + const { point, setPoint, userId } = useUserStore(); const { mutate, isPending } = useMutation({ - ...generate_qo_postRewards(1), + ...generate_qo_postRewards(userId), onSuccess: (data) => { if (data.status === "error") { toast.error("리워드 신청에 실패했습니다."); @@ -31,6 +31,18 @@ export default function Reward() { } }); + const applyReward = ({ + points, + pointType, + description + }: { + points: number; + pointType: string; + description: string; + }) => { + mutate({ points, pointType, description }); + }; + const loaledPoint = useMemo(() => `${point.toLocaleString()}p`, [point]); const coupons = useMemo( () => generateCoupons(point, isPending), @@ -49,29 +61,41 @@ export default function Reward() {

- {map(coupons, ({ id, image, name, isDisabled, className }) => ( -
+ {map( + coupons, + ({ + id, + image, + name, + isDisabled, + className, + points, + pointType, + description + }) => (
+ key={id} + className="relative flex items-center gap-4 overflow-hidden rounded-xl" + > +
-

- {name} -

+

+ {name} +

- -
- ))} + +
+ ) + )}
); diff --git a/src/lib/react-query/queryOptions/reward.ts b/src/lib/react-query/queryOptions/reward.ts index bbea15e..dca8ba9 100644 --- a/src/lib/react-query/queryOptions/reward.ts +++ b/src/lib/react-query/queryOptions/reward.ts @@ -2,16 +2,23 @@ import { postRewards } from "@/features/reward/api/reward"; import { UseMutationOptions } from "@tanstack/react-query"; import { AxiosError } from "axios"; +interface RewardData { + points: number; + pointType: string; + description: string; +} + interface UseMutationRewardsOptions extends UseMutationOptions< { point: number; status: string }, AxiosError, - void, + RewardData, unknown > {} -type GenerateQoPostRewards = (id: number) => UseMutationRewardsOptions; -export const generate_qo_postRewards: GenerateQoPostRewards = (socialId) => { +type GenerateQoPostRewards = (userId: number) => UseMutationRewardsOptions; +export const generate_qo_postRewards: GenerateQoPostRewards = (userId) => { return { - mutationFn: () => postRewards({ pathParams: { socialId } }) + mutationFn: (data: RewardData) => + postRewards({ pathParams: { userId }, data }) }; }; From 70bf5d989d738b8248f71a3fb2f38fae8bbff659 Mon Sep 17 00:00:00 2001 From: Park-min-hyoung Date: Sun, 2 Mar 2025 13:02:06 +0900 Subject: [PATCH 06/10] =?UTF-8?q?feat:=20userId=20type=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/goal/api/path.ts | 4 ++-- src/features/home/api/home.ts | 2 +- src/features/home/api/paths.ts | 4 ++-- src/lib/react-query/queryOptions/goals.ts | 4 ++-- src/lib/react-query/queryOptions/home.ts | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/features/goal/api/path.ts b/src/features/goal/api/path.ts index cec56aa..a79cb0e 100644 --- a/src/features/goal/api/path.ts +++ b/src/features/goal/api/path.ts @@ -3,5 +3,5 @@ import { generatePathByBase, genreateBasePath } from "@/lib/axios/utils"; export const BASE_PATH = genreateBasePath("goals", "v1"); export const GOALS_CHECK = generatePathByBase(BASE_PATH, "check"); -export const GOALS_COMPLETE = (userId: number) => - generatePathByBase(GOALS_CHECK, "complete", String(userId)); +export const GOALS_COMPLETE = (userId: string) => + generatePathByBase(GOALS_CHECK, "complete", userId); diff --git a/src/features/home/api/home.ts b/src/features/home/api/home.ts index 9823a7c..0dfa696 100644 --- a/src/features/home/api/home.ts +++ b/src/features/home/api/home.ts @@ -6,6 +6,6 @@ import { HOME_PATH } from "./paths"; export function getGoals({ pathParams: { userId } -}: RoOnlyPathParamsType<{ userId: number }>): R { +}: RoOnlyPathParamsType<{ userId: string }>): R { return GET({ url: HOME_PATH(userId) }); } diff --git a/src/features/home/api/paths.ts b/src/features/home/api/paths.ts index 058926d..7735a33 100644 --- a/src/features/home/api/paths.ts +++ b/src/features/home/api/paths.ts @@ -2,5 +2,5 @@ import { generatePathByBase, genreateBasePath } from "@/lib/axios/utils"; export const BASE_PATH = genreateBasePath("goals", "v1"); -export const HOME_PATH = (userId: number) => - generatePathByBase(BASE_PATH, String(userId)); +export const HOME_PATH = (userId: string) => + generatePathByBase(BASE_PATH, userId); diff --git a/src/lib/react-query/queryOptions/goals.ts b/src/lib/react-query/queryOptions/goals.ts index 64ebd5c..966d2dc 100644 --- a/src/lib/react-query/queryOptions/goals.ts +++ b/src/lib/react-query/queryOptions/goals.ts @@ -49,7 +49,7 @@ export const generate_qo_postGoalsAchieve: GenerateQoPostGoalsAchieve = ({ interface UseQueryGoalsCheckOptions extends UseQueryOptions {} -type GenerateQoGetGoalsCheck = (userId: number) => UseQueryGoalsCheckOptions; +type GenerateQoGetGoalsCheck = (userId: string) => UseQueryGoalsCheckOptions; export const generate_qo_getGoalsCheck: GenerateQoGetGoalsCheck = (userId) => { return { queryKey: [GOALS_CHECK], @@ -60,7 +60,7 @@ export const generate_qo_getGoalsCheck: GenerateQoGetGoalsCheck = (userId) => { interface UseQueryGoalsCompleteOptions extends UseQueryOptions {} type GenerateQoGetGoalsComplete = ( - userId: number + userId: string ) => UseQueryGoalsCompleteOptions; export const generate_qo_getGoalsComplete: GenerateQoGetGoalsComplete = ( userId diff --git a/src/lib/react-query/queryOptions/home.ts b/src/lib/react-query/queryOptions/home.ts index d91096a..9800eec 100644 --- a/src/lib/react-query/queryOptions/home.ts +++ b/src/lib/react-query/queryOptions/home.ts @@ -6,7 +6,7 @@ import { AxiosError } from "axios"; interface UseQueryGoalsOptions extends UseQueryOptions {} -type GenerateQoGetGoals = (userId: number) => UseQueryGoalsOptions; +type GenerateQoGetGoals = (userId: string) => UseQueryGoalsOptions; export const generate_qo_getGoals: GenerateQoGetGoals = (userId) => { return { From b8f3e1d0073d0fb0f0e7ba2911379858c89ae4d4 Mon Sep 17 00:00:00 2001 From: Park-min-hyoung Date: Sun, 2 Mar 2025 14:14:50 +0900 Subject: [PATCH 07/10] =?UTF-8?q?feat:=20=EB=AA=A9=ED=91=9C=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20=EC=8B=9C=20=ED=99=88=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=BF=BC=EB=A6=AC=20=EB=AC=B4?= =?UTF-8?q?=ED=9A=A8=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/map-certification.tsx | 13 +++++++------ src/lib/react-query/queryOptions/home.ts | 12 ++++++++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/features/map-certification/components/map-certification.tsx b/src/features/map-certification/components/map-certification.tsx index f02ee06..bf22d87 100644 --- a/src/features/map-certification/components/map-certification.tsx +++ b/src/features/map-certification/components/map-certification.tsx @@ -17,6 +17,7 @@ import { generate_qo_getGoalsComplete, generate_qo_postGoalsAchieve } from "@/lib/react-query/queryOptions/goals"; +import { generate_qo_getGoals as generate_qo_home } from "@/lib/react-query/queryOptions/home.ts"; import useUserLocation from "@/hooks/useUserLocation"; import CertificationButton from "./certification-button"; import GoalsInfo from "./goals-info"; @@ -46,7 +47,7 @@ function MapCertification() { latitude: serverLatitude, longitude: serverLongitude } = generateInitialGoalsData() - } = useQuery(generate_qo_getGoals(1)); + } = useQuery(generate_qo_getGoals(userId)); const { mutate, isPending } = useMutation({ ...generate_qo_postGoalsAchieve({ @@ -56,17 +57,17 @@ function MapCertification() { longitude: position.lng }), onSuccess: (data) => { - const progressGoalKey = generate_qo_getGoalsCheck.DELETE_KEY(); - const completeGoalKey = generate_qo_getGoalsComplete.DELETE_KEY(); + const progressGoalKey = generate_qo_getGoalsCheck.DELETE_KEY(userId); + const completeGoalKey = generate_qo_getGoalsComplete.DELETE_KEY(userId); + const homeKey = generate_qo_home.DELETE_KEY(userId); Promise.all([ client.invalidateQueries({ queryKey: progressGoalKey }), - client.invalidateQueries({ queryKey: completeGoalKey }) - // client.invalidateQueries({ queryKey: ["home"] }) // 홈페이지 데이터 무효화 + client.invalidateQueries({ queryKey: completeGoalKey }), + client.invalidateQueries({ queryKey: homeKey }) ]); addPoint(data.point); - navigate("/map/certification/success", { state: { name, point: data.point } }); diff --git a/src/lib/react-query/queryOptions/home.ts b/src/lib/react-query/queryOptions/home.ts index 9800eec..b2bba8c 100644 --- a/src/lib/react-query/queryOptions/home.ts +++ b/src/lib/react-query/queryOptions/home.ts @@ -8,12 +8,16 @@ interface UseQueryGoalsOptions extends UseQueryOptions {} type GenerateQoGetGoals = (userId: string) => UseQueryGoalsOptions; -export const generate_qo_getGoals: GenerateQoGetGoals = (userId) => { - return { - queryKey: [BASE_PATH], +export const generate_qo_getGoals = ((userId) => { + const options: UseQueryGoalsOptions = { + queryKey: [BASE_PATH, userId], queryFn: () => getGoals({ pathParams: { userId } }).then((data) => { return data; }) }; -}; + + return options; +}) as GenerateQoGetGoals & { DELETE_KEY: (userId: string) => string[] }; + +generate_qo_getGoals.DELETE_KEY = (userId) => [BASE_PATH, userId]; From 02f27eed29f0ff06aba659fd152eb3f745d31506 Mon Sep 17 00:00:00 2001 From: Park-min-hyoung Date: Sun, 2 Mar 2025 14:27:45 +0900 Subject: [PATCH 08/10] =?UTF-8?q?feat:=20=EB=A9=94=EC=9D=B8=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=B3=B4=EC=99=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ui/header/index.const.ts | 8 +-- src/components/ui/header/profile-header.tsx | 49 ++++++++++++------- .../map-certification/components/success.tsx | 1 - src/stores/user.ts | 4 +- 4 files changed, 38 insertions(+), 24 deletions(-) diff --git a/src/components/ui/header/index.const.ts b/src/components/ui/header/index.const.ts index b4fc1ce..0dbaefc 100644 --- a/src/components/ui/header/index.const.ts +++ b/src/components/ui/header/index.const.ts @@ -27,8 +27,8 @@ export const backgroundImages = [ ]; export const generateKeyword = (point: number) => { - if (point <= 500) return "coffee"; - else if (point > 500) return "convenienceStore"; + if (point <= 5000) return "coffee"; + else if (point > 5000 && point < 10000) return "convenienceStore"; else return "reward"; }; @@ -41,7 +41,7 @@ export const generateIcon = ( }; export const generateProgressPercent = (point: number) => { - if (point <= 500) return (point / 500) * 100; - else if (point > 500) return (point / 1000) * 100; + if (point <= 5000) return (point / 5000) * 100; + else if (point > 5000 && point < 10000) return (point / 5000) * 100; else return 100; }; diff --git a/src/components/ui/header/profile-header.tsx b/src/components/ui/header/profile-header.tsx index 8f8a755..3dcafcc 100644 --- a/src/components/ui/header/profile-header.tsx +++ b/src/components/ui/header/profile-header.tsx @@ -8,7 +8,10 @@ import { Progress } from "@/components/ui/progress"; import { useEffect, useMemo, useState } from "react"; import InfoIcon from "@/asset/common/info.svg?react"; +import { Link } from "react-router"; +import { useUserStore } from "@/stores/user"; +import { paths } from "@/config/paths"; import { backgroundImages, generateIcon, @@ -16,37 +19,49 @@ import { generateProgressPercent } from "./index.const"; -/** @todo 전역 값 사용, 관련 데이터 전역 값으로 수정 */ -const fakePoint1 = 550; - function ProfileHeader() { - /** @todo 전역 상태 정의(사용자명, 포인트) **/ + const { userName, point } = useUserStore(); const [bgImage, setBgImage] = useState(""); - /** @todo 전역 상태 사용(포인트) **/ const rewarndInfo = useMemo(() => { - const keyword = generateKeyword(fakePoint1); + const keyword = generateKeyword(point); const Icon = generateIcon(keyword); - const progressPercent = generateProgressPercent(fakePoint1); + const progressPercent = generateProgressPercent(point); const currentPointText = ( <> - 현재 포인트 {fakePoint1}p - - ); - const targetPointText = ( - <> - 커피쿠폰까지 {fakePoint1}p + 현재 포인트 {point}p ); + const generateTagetPointText = () => { + if (keyword === "coffee") { + return ( + <> + 커피쿠폰까지 {point}p + + ); + } else if (keyword === "convenienceStore") { + return ( + <> + 편의점쿠폰까지 {point}p + + ); + } else { + return ( + + 리워드를 수령해주세요. + + ); + } + }; return { icon: , progressPercent, currentPointText, - targetPointText + targetPointText: generateTagetPointText() }; - }, []); + }, [point]); useEffect(() => { const randomBg = @@ -76,8 +91,8 @@ function ProfileHeader() {

- {/* @todo 전역 Username 사용 */} - 안녕하세요! 홍길동님 + 안녕하세요! + {userName}

오늘도 일단 가볼까요?

diff --git a/src/features/map-certification/components/success.tsx b/src/features/map-certification/components/success.tsx index b1cf60f..47e8558 100644 --- a/src/features/map-certification/components/success.tsx +++ b/src/features/map-certification/components/success.tsx @@ -11,7 +11,6 @@ function MapCertificationSuccess() { } = location; const navigateToHome = () => { - /** @todo 인증 페이지의 인증하기 버튼 기반 쿼리 무효화했기 떄문에 홈페이지 이동 시 최신 데이터가 렌더링되어야함 */ const homePath = paths.home; navigate(homePath.getHref()); diff --git a/src/stores/user.ts b/src/stores/user.ts index 6aafb0e..3fdb948 100644 --- a/src/stores/user.ts +++ b/src/stores/user.ts @@ -11,8 +11,8 @@ interface UserState { } export const useUserStore = create((set) => ({ - userName: "park", - point: 100000, + userName: "", + point: 0, userId: "0", setUserId: (userId) => set({ userId }), setPoint: (point) => set({ point }), From 515e2718f9177467a80a807353d236f2cdfeedf5 Mon Sep 17 00:00:00 2001 From: yj-leee Date: Mon, 3 Mar 2025 02:43:21 +0900 Subject: [PATCH 09/10] =?UTF-8?q?fix:=20=EC=97=90=EB=9F=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ui/header/index.const.ts | 2 +- src/components/ui/header/profile-header.tsx | 5 +- src/components/ui/navbar/index.const.ts | 7 +- src/features/auth/api/auth.ts | 4 +- src/features/auth/components/Agreement.tsx | 11 +- src/features/auth/components/PhoneNumber.tsx | 3 +- src/features/auth/routes/GoogleCallback.tsx | 5 +- src/features/auth/routes/KakaoCallback.tsx | 11 +- src/features/goal/api/goal.ts | 12 +- .../components/create-goal/BalanceInfo.tsx | 6 +- .../goal/components/goal/progress-goal.tsx | 6 +- src/features/goal/routes/CreateGoal.tsx | 64 ++++-- src/features/goal/types/goal-create.tsx | 16 +- src/features/home/api/home.ts | 2 +- src/features/home/api/paths.ts | 4 +- src/features/home/components/goal-list.tsx | 8 +- src/features/home/components/home.tsx | 10 +- src/features/home/types/index.ts | 4 +- src/features/map-certification/api/index.ts | 2 +- .../components/map-certification.const.ts | 2 +- .../components/map-certification.tsx | 9 +- src/features/map-certification/types/index.ts | 2 +- src/features/point/api/path.ts | 2 +- src/features/user/api/user.ts | 4 +- src/features/user/routes/Account.tsx | 2 +- src/features/user/routes/MyPage.tsx | 207 +++++++----------- src/features/user/types/user-info.tsx | 2 +- src/lib/axios/index.ts | 25 ++- src/lib/react-query/queryOptions/goals.ts | 17 +- src/lib/react-query/queryOptions/home.ts | 4 +- src/stores/auth-store.ts | 6 +- src/stores/user.ts | 6 +- 32 files changed, 250 insertions(+), 220 deletions(-) diff --git a/src/components/ui/header/index.const.ts b/src/components/ui/header/index.const.ts index 0dbaefc..89bd46b 100644 --- a/src/components/ui/header/index.const.ts +++ b/src/components/ui/header/index.const.ts @@ -42,6 +42,6 @@ export const generateIcon = ( export const generateProgressPercent = (point: number) => { if (point <= 5000) return (point / 5000) * 100; - else if (point > 5000 && point < 10000) return (point / 5000) * 100; + else if (point > 5000 && point < 10000) return (point / 10000) * 100; else return 100; }; diff --git a/src/components/ui/header/profile-header.tsx b/src/components/ui/header/profile-header.tsx index 3dcafcc..689a712 100644 --- a/src/components/ui/header/profile-header.tsx +++ b/src/components/ui/header/profile-header.tsx @@ -37,13 +37,14 @@ function ProfileHeader() { if (keyword === "coffee") { return ( <> - 커피쿠폰까지 {point}p + 커피쿠폰까지 {5000 - point}p ); } else if (keyword === "convenienceStore") { return ( <> - 편의점쿠폰까지 {point}p + 편의점쿠폰까지{" "} + {10000 - point}p ); } else { diff --git a/src/components/ui/navbar/index.const.ts b/src/components/ui/navbar/index.const.ts index 7882c08..6e9fe00 100644 --- a/src/components/ui/navbar/index.const.ts +++ b/src/components/ui/navbar/index.const.ts @@ -12,7 +12,8 @@ export const NOT_VISIBLE_NAVBAR_PAGES = [ paths.auth.agreement.getHref(), paths.auth.phoneNumber.getHref(), paths.map.search.getHref(), - paths.map.certification.getHref() + paths.map.certification.getHref(), + paths.auth.login.getHref() ]; const isEqualPath = (locationPath: string, path: string) => { @@ -38,7 +39,7 @@ export const generateNavbarInfo = (locationPath: string) => [ label: "내정보", activeIcon: User, notActiveIcon: UserDisable, - to: "/profile", - isActvie: isEqualPath(locationPath, "/profile") + to: "/user/my-page", + isActvie: isEqualPath(locationPath, "/user/my-page") } ]; diff --git a/src/features/auth/api/auth.ts b/src/features/auth/api/auth.ts index 2a5a49c..cb006fb 100644 --- a/src/features/auth/api/auth.ts +++ b/src/features/auth/api/auth.ts @@ -22,6 +22,6 @@ export function postPhoneNumber({ data }: { data: { phoneNumber: string } }) { return POST({ url: `${BASE_PATH}/user/phone`, data }); } -export function postTermsAgree({ data }: { data: { userId: number } }) { - return POST({ url: `/terms/agree`, data }); +export function postTermsAgree(pathParam: { userId: number }) { + return POST({ url: `/api/v1/terms/agree/${pathParam.userId}` }); } diff --git a/src/features/auth/components/Agreement.tsx b/src/features/auth/components/Agreement.tsx index f9d285d..8f0ccfe 100644 --- a/src/features/auth/components/Agreement.tsx +++ b/src/features/auth/components/Agreement.tsx @@ -5,6 +5,7 @@ import CheckedItemIcon from "@/asset/agreement/checked-item.svg?url"; import UncheckedAllIcon from "@/asset/agreement/unchecked-all.svg?url"; import UncheckedItemIcon from "@/asset/agreement/unchecked-item.svg?url"; import { postTermsAgree } from "@/features/auth/api/auth"; +import { getUserInfo } from "@/features/user/api/user"; import { useNavigate } from "react-router"; import { useAuthStore } from "@/stores/auth-store"; @@ -68,11 +69,11 @@ const Agreement = () => { if (!userId) return; try { - const response = await postTermsAgree({ - data: { userId } - }); + await postTermsAgree({ userId }); - if (response.data.success) { + const { phoneNumber } = await getUserInfo({ pathParam: userId }); + + if (!phoneNumber) { navigate(paths.auth.phoneNumber.path); } else { throw new Error("약관 동의에 실패했습니다."); @@ -159,7 +160,7 @@ const Agreement = () => { disabled={!checkState.all} onClick={handleSubmit} className={`fixed bottom-6 ml-[20px] flex h-[56px] w-[335px] items-center justify-center rounded-[8px] py-[10px] text-[16px] font-semibold leading-[20px] ${ - !checkState.all + checkState.all ? "cursor-pointer bg-[#3CC360] text-white" : "cursor-not-allowed bg-gray-300 text-gray-500" }`} diff --git a/src/features/auth/components/PhoneNumber.tsx b/src/features/auth/components/PhoneNumber.tsx index d9cecab..33e49fc 100644 --- a/src/features/auth/components/PhoneNumber.tsx +++ b/src/features/auth/components/PhoneNumber.tsx @@ -22,8 +22,7 @@ const PhoneNumber = () => { const response = await postPhoneNumber({ data: { phoneNumber } }); - - if (response.data.success) { + if (response.message === "전화번호가 성공적으로 저장됨") { navigate(paths.home.path); } else { throw new Error("약관 동의에 실패했습니다."); diff --git a/src/features/auth/routes/GoogleCallback.tsx b/src/features/auth/routes/GoogleCallback.tsx index 32dc43b..258f2d3 100644 --- a/src/features/auth/routes/GoogleCallback.tsx +++ b/src/features/auth/routes/GoogleCallback.tsx @@ -28,9 +28,10 @@ const GoogleCallback = (): JSX.Element | null => { throw new Error("구글 인증에 실패했습니다."); } - const { accessToken, refreshToken, username, userId } = response.data; + const { accessToken, refreshToken, username, id, userId } = + response.data; - setTokens(accessToken, refreshToken, userId); + setTokens(accessToken, refreshToken, id); setUserName(username); setUserId(userId); diff --git a/src/features/auth/routes/KakaoCallback.tsx b/src/features/auth/routes/KakaoCallback.tsx index 6ee6909..478c970 100644 --- a/src/features/auth/routes/KakaoCallback.tsx +++ b/src/features/auth/routes/KakaoCallback.tsx @@ -30,16 +30,19 @@ const KakaoCallback = (): JSX.Element | null => { throw new Error("카카오 인증에 실패했습니다."); } - const { accessToken, refreshToken, username, userId } = response.data; + const { accessToken, refreshToken, username, id } = response.data; - setTokens(accessToken, refreshToken, userId); + console.log(response.data); + + setTokens(accessToken, refreshToken, id); setUserName(username); - setUserId(userId); + setUserId(id); - const { totalPoints } = await getPoint({ pathParams: { userId } }); + const { totalPoints } = await getPoint({ pathParams: { userId: id } }); setPoint(totalPoints); navigate(paths.home.path); + // navigate(paths.auth.agreement.path); } catch (error) { console.error(error); setIsError(true); diff --git a/src/features/goal/api/goal.ts b/src/features/goal/api/goal.ts index de76867..bab3a4c 100644 --- a/src/features/goal/api/goal.ts +++ b/src/features/goal/api/goal.ts @@ -3,14 +3,14 @@ import { GoalData } from "@/features/goal/types/goal-create"; import type { R } from "@/types/common.ts"; import { GET, POST } from "@/lib/axios"; -import { RoOnlyDataType, RoOnlyPathParamsType } from "@/lib/axios/utils"; +import { RoOnlyPathParamsType, RoOnlyQueryType } from "@/lib/axios/utils"; import type { CompleteGoal, ProgressGoal } from "../types"; import { GOALS_CHECK, GOALS_COMPLETE } from "./path"; export function getGoalsCheck({ - data -}: RoOnlyDataType<{ userId: string }>): R { - return GET({ url: GOALS_CHECK, data }); + query +}: RoOnlyQueryType<{ userId: number }>): R { + return GET({ url: GOALS_CHECK, params: query }); } export function getGoalsComplete({ @@ -21,14 +21,14 @@ export function getGoalsComplete({ export function postCreateGoal({ data }: { data: GoalData }) { return POST({ - url: `${BASE_PATH}`, + url: `${BASE_PATH}/create`, data }); } export function postCreateTempSaveGoal({ data }: { data: GoalData }) { return POST({ - url: `${BASE_PATH}`, + url: `${BASE_PATH}/create`, data }); } diff --git a/src/features/goal/components/create-goal/BalanceInfo.tsx b/src/features/goal/components/create-goal/BalanceInfo.tsx index 8c56aca..e82c25b 100644 --- a/src/features/goal/components/create-goal/BalanceInfo.tsx +++ b/src/features/goal/components/create-goal/BalanceInfo.tsx @@ -15,13 +15,13 @@ const BalanceInfo: React.FC = ({ balancePoint }) => ( -
+
보유 포인트 -
+
- {balancePoint} + {balancePoint.toLocaleString()} p diff --git a/src/features/goal/components/goal/progress-goal.tsx b/src/features/goal/components/goal/progress-goal.tsx index 9f8a3ad..21faebd 100644 --- a/src/features/goal/components/goal/progress-goal.tsx +++ b/src/features/goal/components/goal/progress-goal.tsx @@ -1,5 +1,6 @@ import { useMemo } from "react"; +import { DAYS_STRING_MAP } from "@/features/map-certification/components/map-certification.const.ts"; import { useQuery } from "@tanstack/react-query"; import { join, map, slice } from "es-toolkit/compat"; @@ -37,6 +38,7 @@ function ProgressGoal() { const transformedProgressGoals = useMemo( () => map(progressGoals, (goal, index) => { + console.log(progressGoals); const transFormViewDays = map(goal.calender, (viewDay) => { const isCertificationInfo = certificationInfoMaps[index].has(viewDay); @@ -57,7 +59,9 @@ function ProgressGoal() { : generateNonCertificationItem({ viewDay, day, isToday }); }); const dateString = `${generateDateString(goal.startDate)} ~ ${generateDateString(goal.endDate)}`; - const daysArr = goal.dayOfWeek.split(","); + const daysArr = map(goal.dayOfWeek.split(","), (day) => + DAYS_STRING_MAP.get(day) + ); const days = daysArr.length === 7 ? "매일" : join(daysArr, ","); const lastWeekDate = slice(transFormViewDays, 0, 7); const thiwWeekDate = slice(transFormViewDays, -7); diff --git a/src/features/goal/routes/CreateGoal.tsx b/src/features/goal/routes/CreateGoal.tsx index 9d8ed44..b8a6c5e 100644 --- a/src/features/goal/routes/CreateGoal.tsx +++ b/src/features/goal/routes/CreateGoal.tsx @@ -13,14 +13,18 @@ import { DAY_MAPPING } from "@/features/goal/components/create-goal/goal.constan import SaveButtons from "@/features/goal/components/create-goal/SaveButtons"; import { GoalData, GoalStatus } from "@/features/goal/types/goal-create"; import { getPoint } from "@/features/point/api/point"; -import { debounce } from "es-toolkit"; +import { useQueryClient } from "@tanstack/react-query"; +import { format } from "date-fns"; +import { debounce, isNull } from "es-toolkit"; import { Map, MapMarker } from "react-kakao-maps-sdk"; import { useLocation, useNavigate } from "react-router"; import { toast } from "react-toastify"; import { Nullable } from "@/types/common"; import { useAuthStore } from "@/stores/auth-store"; +import { useUserStore } from "@/stores/user"; import { paths } from "@/config/paths"; +import { generate_qo_getGoals as generate_qo_home } from "@/lib/react-query/queryOptions/home.ts"; interface CreateGoalProps { goalId?: number; @@ -29,7 +33,9 @@ interface CreateGoalProps { const CreateGoal: React.FC = ({ goalId }) => { const location = useLocation(); const navigate = useNavigate(); - const userId = useAuthStore((state) => state.userId); + const client = useQueryClient(); + const { userId } = useAuthStore(); + const { setPoint } = useUserStore(); const [goalName, setGoalName] = useState( location.state?.goalName || "" @@ -57,8 +63,8 @@ const CreateGoal: React.FC = ({ goalId }) => { }, [goalName, startDate, endDate, selectedDays]); const fetchBalancePoint = useCallback(async (): Promise => { - if (!userId) return; try { + if (!userId) return; const { totalPoints } = await getPoint({ userId }); setBalancePoint(totalPoints); } catch (error) { @@ -78,11 +84,25 @@ const CreateGoal: React.FC = ({ goalId }) => { ); useEffect(() => { - if (location.state) { + if (isNull(location.state)) return; + + console.log(location.state); + + if (location.state.position && location.state.placeName) { const { position, placeName } = location.state; setTargetLocation(placeName); setPosition(position); setCenter(position); + + const goalInfo = JSON.parse(localStorage.getItem("goalInfo") ?? "{}"); + + if (goalInfo === "{}") return; + + const { goalName, startDate, endDate, selectedDays } = goalInfo; + setGoalName(goalName); + setStartDate(startDate ? new Date(startDate) : null); + setEndDate(endDate ? new Date(endDate) : null); + setSelectedDays(selectedDays); } else { navigator.geolocation.getCurrentPosition( ({ coords: { latitude, longitude } }) => { @@ -138,28 +158,44 @@ const CreateGoal: React.FC = ({ goalId }) => { } const goalData: GoalData = { - goal: { - userId, - name: goalName, - startDate: startDate ? startDate.toISOString() : null, - endDate: endDate ? endDate.toISOString() : null, - locationName: targetLocation - }, + userId, + name: goalName, + startDate: startDate ? format(startDate, "yyyy-MM-dd") : null, + endDate: endDate ? format(endDate, "yyyy-MM-dd") : null, + locationName: targetLocation, status, - days: selectedDays.map((day) => DAY_MAPPING[day]) + latitude: position?.lat, + longitude: position?.lng, + selectedDays: selectedDays.map((day) => DAY_MAPPING[day]) }; try { status === GoalStatus.DRAFT ? await postCreateTempSaveGoal({ data: goalData }) : await postCreateGoal({ data: goalData }); - navigate(paths.goal.root.path); + + setPoint(useUserStore.getState().point - 200); + const homeKey = generate_qo_home.DELETE_KEY(userId); + client.invalidateQueries({ queryKey: homeKey }); + + navigate(paths.home.path); } catch (error) { console.error(error); } }; - const navigatePositionSearch = () => navigate(paths.map.search.getHref()); + const navigatePositionSearch = () => { + navigate(paths.map.search.getHref()); + localStorage.setItem( + "goalInfo", + JSON.stringify({ + goalName, + startDate, + endDate, + selectedDays + }) + ); + }; return (
diff --git a/src/features/goal/types/goal-create.tsx b/src/features/goal/types/goal-create.tsx index 86bdb62..1f41658 100644 --- a/src/features/goal/types/goal-create.tsx +++ b/src/features/goal/types/goal-create.tsx @@ -4,13 +4,13 @@ export enum GoalStatus { } export type GoalData = { - goal: { - userId: number; - name: string; - startDate: string | null; - endDate: string | null; - locationName: string; - }; + userId: number; + name: string; + startDate: string | null; + endDate: string | null; + latitude: number; + longitude: number; + locationName: string; status: GoalStatus; - days: string[]; + selectedDays: string[]; }; diff --git a/src/features/home/api/home.ts b/src/features/home/api/home.ts index 0dfa696..9823a7c 100644 --- a/src/features/home/api/home.ts +++ b/src/features/home/api/home.ts @@ -6,6 +6,6 @@ import { HOME_PATH } from "./paths"; export function getGoals({ pathParams: { userId } -}: RoOnlyPathParamsType<{ userId: string }>): R { +}: RoOnlyPathParamsType<{ userId: number }>): R { return GET({ url: HOME_PATH(userId) }); } diff --git a/src/features/home/api/paths.ts b/src/features/home/api/paths.ts index 7735a33..058926d 100644 --- a/src/features/home/api/paths.ts +++ b/src/features/home/api/paths.ts @@ -2,5 +2,5 @@ import { generatePathByBase, genreateBasePath } from "@/lib/axios/utils"; export const BASE_PATH = genreateBasePath("goals", "v1"); -export const HOME_PATH = (userId: string) => - generatePathByBase(BASE_PATH, userId); +export const HOME_PATH = (userId: number) => + generatePathByBase(BASE_PATH, String(userId)); diff --git a/src/features/home/components/goal-list.tsx b/src/features/home/components/goal-list.tsx index 5b2c050..32f05aa 100644 --- a/src/features/home/components/goal-list.tsx +++ b/src/features/home/components/goal-list.tsx @@ -21,7 +21,7 @@ function GoalList({ goalCount, transformGoals }: GoalListProps) { {map( transformGoals, ({ - id, + goalId, title, goalName, lastRowText, @@ -31,18 +31,18 @@ function GoalList({ goalCount, transformGoals }: GoalListProps) { redirectionCallback }) => (

{title}

{goalName}

diff --git a/src/features/home/components/home.tsx b/src/features/home/components/home.tsx index 8faf709..aacc315 100644 --- a/src/features/home/components/home.tsx +++ b/src/features/home/components/home.tsx @@ -45,22 +45,22 @@ const HomePage = () => { () => map(goals, (item) => { const lastRowText = - item.status === "DRAF" + item.status === "DRAFT" ? "목표등록을 마무리하고 바로 시작해보세요!" : `${generatDdateText(item.startDate, item.endDate)} ${generateDayText(item.dayOfWeek)}`; - const buttonText = `${item.status === "DRAF" ? "완성하기" : "인증하기"} >`; + const buttonText = `${item.status === "DRAFT" ? "완성하기" : "인증하기"} >`; const redirectionCallback = () => { const url = - item.status === "DRAF" + item.status === "DRAFT" ? paths.goal.create.getHref() : paths.map.certification.getHref(); - navigate(url, { state: { goalId: item.id, userId } }); + navigate(url, { state: { goalId: item.goalId, userId } }); }; return { ...item, - title: `${item.status === "DRAF" ? "임시저장" : "진행중"} 목표`, + title: `${item.status === "DRAFT" ? "임시저장" : "진행중"} 목표`, name: item.goalName, lastRowText, buttonText, diff --git a/src/features/home/types/index.ts b/src/features/home/types/index.ts index 362b7c7..3812866 100644 --- a/src/features/home/types/index.ts +++ b/src/features/home/types/index.ts @@ -1,7 +1,7 @@ -export type STATUS = "DRAF" | "ACTIVE" | "COMPLETE"; +export type STATUS = "DRAFT" | "ACTIVE" | "COMPLETE"; export interface Goals { - id: number; + goalId: number; goalName: string; startDate: string; endDate: string; diff --git a/src/features/map-certification/api/index.ts b/src/features/map-certification/api/index.ts index 1b27078..86da477 100644 --- a/src/features/map-certification/api/index.ts +++ b/src/features/map-certification/api/index.ts @@ -19,6 +19,6 @@ export function postGoalsAchieve({ }: RoDataAndPathParamsType< { userId: number; latitude: number; longitude: number }, { goalId: number } ->): R<{ point: number }> { +>): R<{ totalPoints: number; bonusPoints: number }> { return POST({ url: GOALS_ACHIEVE(goalId), data }); } diff --git a/src/features/map-certification/components/map-certification.const.ts b/src/features/map-certification/components/map-certification.const.ts index 5b5b7b0..c206ee3 100644 --- a/src/features/map-certification/components/map-certification.const.ts +++ b/src/features/map-certification/components/map-certification.const.ts @@ -1,6 +1,6 @@ export const generateInitialGoalsData = () => ({ id: 0, - goalName: "", + name: "", startDate: "", endDate: "", latitude: 33.450701, diff --git a/src/features/map-certification/components/map-certification.tsx b/src/features/map-certification/components/map-certification.tsx index bf22d87..e6d5c71 100644 --- a/src/features/map-certification/components/map-certification.tsx +++ b/src/features/map-certification/components/map-certification.tsx @@ -40,14 +40,14 @@ function MapCertification() { useUserLocation(); const { data: { - goalName: name, + name, startDate, endDate, dayOfWeek: days, latitude: serverLatitude, longitude: serverLongitude } = generateInitialGoalsData() - } = useQuery(generate_qo_getGoals(userId)); + } = useQuery(generate_qo_getGoals(goalId)); const { mutate, isPending } = useMutation({ ...generate_qo_postGoalsAchieve({ @@ -67,15 +67,16 @@ function MapCertification() { client.invalidateQueries({ queryKey: homeKey }) ]); - addPoint(data.point); + addPoint(data.totalPoints); navigate("/map/certification/success", { - state: { name, point: data.point } + state: { name, point: data.bonusPoints } }); } }); // useMemos const isContainRadar = useMemo(() => { + console.log(position.lat, position.lng, serverLatitude, serverLongitude); if (!serverLatitude || !serverLongitude) return false; const distance = diff --git a/src/features/map-certification/types/index.ts b/src/features/map-certification/types/index.ts index 666e3f1..a8801f8 100644 --- a/src/features/map-certification/types/index.ts +++ b/src/features/map-certification/types/index.ts @@ -1,7 +1,7 @@ export type STATUS = "DRAF" | "ACTIVE" | "COMPLETE"; export interface GoalDetail { - goalName: string; + name: string; startDate: string; endDate: string; latitude: number; diff --git a/src/features/point/api/path.ts b/src/features/point/api/path.ts index f262bcd..4ccdab1 100644 --- a/src/features/point/api/path.ts +++ b/src/features/point/api/path.ts @@ -1,3 +1,3 @@ import { genreateBasePath } from "@/lib/axios/utils"; -export const BASE_PATH = genreateBasePath("point"); +export const BASE_PATH = genreateBasePath("points"); diff --git a/src/features/user/api/user.ts b/src/features/user/api/user.ts index 2f865f5..62bcaf9 100644 --- a/src/features/user/api/user.ts +++ b/src/features/user/api/user.ts @@ -1,10 +1,10 @@ -import { BASE_PATH } from "@/features/auth/api/paths"; +import { BASE_PATH } from "@/features/user/api/paths"; import { DELETE, GET, POST } from "@/lib/axios"; export function getUserInfo({ pathParam }: { pathParam: number }) { return GET({ - url: `${BASE_PATH}?userId=${pathParam}` + url: `${BASE_PATH}/${pathParam}/check` }); } diff --git a/src/features/user/routes/Account.tsx b/src/features/user/routes/Account.tsx index ca9d199..36f07a9 100644 --- a/src/features/user/routes/Account.tsx +++ b/src/features/user/routes/Account.tsx @@ -100,7 +100,7 @@ const AccountPage: React.FC = () => { 휴대폰 번호

- {userInfo?.phone} + {userInfo?.phoneNumber}

diff --git a/src/features/user/routes/MyPage.tsx b/src/features/user/routes/MyPage.tsx index eb32ecf..566283b 100644 --- a/src/features/user/routes/MyPage.tsx +++ b/src/features/user/routes/MyPage.tsx @@ -1,3 +1,5 @@ +import { ProfileHeader } from "@/components/ui/header"; + import React, { useEffect, useState } from "react"; import { getUserInfo, postLogout } from "@/features/user/api/user"; @@ -11,16 +13,15 @@ const MyPage: React.FC = () => { const [userInfo, setUserInfo] = useState(null); const navigate = useNavigate(); const userId: number | null = useAuthStore((state) => state.userId); - + const { logout } = useAuthStore(); useEffect(() => { - if (!userId) { - return; - } + if (!userId) return; const fetchUserInfo = async (): Promise => { try { const response = await getUserInfo({ pathParam: userId }); - setUserInfo(response.data); + + setUserInfo(response); } catch (error) { console.error("사용자 정보를 불러오는 데 실패했습니다.", error); } @@ -34,6 +35,7 @@ const MyPage: React.FC = () => { try { await postLogout({ pathParam: userId }); + logout(); navigate(paths.home.path); } catch (error) { console.error("로그아웃 실패:", error); @@ -45,130 +47,89 @@ const MyPage: React.FC = () => { console.error("사용자 정보가 없습니다."); return; } + console.log(userInfo, "userinfo"); navigate(paths.user.account.path, { state: { ...userInfo, userId } }); }; + const navigateReward = () => navigate(paths.profile.reward.path); + return (
-
-
-
- - 내정보 - -
+ -
-
-

- 안녕하세요!{" "} - - {userInfo ? userInfo?.name : "홍길동"} - - 님 -

-

- 오늘도 일단 가볼까요? -

-
-
-
-
-
-
-

- 현재 포인트:{" "} - - {userInfo ? userInfo.points : 3000}p - -

-

- 커피 쿠폰까지{" "} - - {userInfo ? 5000 - userInfo.points : 500}p - -

-
- -
- - - -
-
- -
-
+
+ + + +
+
+
); diff --git a/src/features/user/types/user-info.tsx b/src/features/user/types/user-info.tsx index a12fb3e..cbfdcdf 100644 --- a/src/features/user/types/user-info.tsx +++ b/src/features/user/types/user-info.tsx @@ -5,7 +5,7 @@ export enum SocialType { export type UserInfo = { name: string; - phone: string; + phoneNumber: string; email?: string; socialType: SocialType; points: number; diff --git a/src/lib/axios/index.ts b/src/lib/axios/index.ts index f7f0857..29ad696 100644 --- a/src/lib/axios/index.ts +++ b/src/lib/axios/index.ts @@ -87,14 +87,23 @@ const requestInterceptor: Interceptor = { const responseInterceptor: Interceptor = { onFulfilled: (config) => { - if ( - config.data && - config.headers["content-type"]?.toString().includes("application/json") - ) { - try { - config.data = JSON.parse(config.data); - } catch { - throw new Error("Axios Parse Error"); + const isJson = config.headers["content-type"]?.includes("application/json"); + + if (isJson) { + if ( + config.data && + config.headers["content-type"]?.toString().includes("application/json") + ) { + try { + if (config.data === "목표 생성 성공") return config; + if (typeof config.data === "string") { + config.data = JSON.parse(config.data); + } else if (typeof config.data !== "object") { + throw new Error("Invalid JSON response"); + } + } catch { + throw new Error("Axios Parse Error"); + } } } diff --git a/src/lib/react-query/queryOptions/goals.ts b/src/lib/react-query/queryOptions/goals.ts index 56abc2a..f00d85c 100644 --- a/src/lib/react-query/queryOptions/goals.ts +++ b/src/lib/react-query/queryOptions/goals.ts @@ -14,13 +14,22 @@ type GenerateQoGetGoals = (goalId: number) => UseQueryGoalsOptions; export const generate_qo_getGoals: GenerateQoGetGoals = (goalId) => { return { queryKey: [GOALS_DETAIL(goalId)], - queryFn: () => getGoals({ pathParams: { goalId } }).then((data) => data), + queryFn: () => + getGoals({ pathParams: { goalId } }).then((data) => { + console.log(data, "목표 인증 데이터"); + return data; + }), staleTime: simpleGenerateSecond([[10, "s"]]) }; }; interface UseMutationGoalsAchieveOptions - extends UseMutationOptions<{ point: number }, AxiosError, void, unknown> {} + extends UseMutationOptions< + { totalPoints: number; bonusPoints: number }, + AxiosError, + void, + unknown + > {} type GenerateQoPostGoalsAchieve = ({ goalId, userId, @@ -49,11 +58,11 @@ export const generate_qo_postGoalsAchieve: GenerateQoPostGoalsAchieve = ({ interface UseQueryGoalsCheckOptions extends UseQueryOptions {} -type GenerateQoGetGoalsCheck = (userId: string) => UseQueryGoalsCheckOptions; +type GenerateQoGetGoalsCheck = (userId: number) => UseQueryGoalsCheckOptions; export const generate_qo_getGoalsCheck = ((userId) => { const options: UseQueryGoalsCheckOptions = { queryKey: [GOALS_CHECK, userId], - queryFn: () => getGoalsCheck({ data: { userId } }).then((data) => data) + queryFn: () => getGoalsCheck({ query: { userId } }).then((data) => data) }; return options; diff --git a/src/lib/react-query/queryOptions/home.ts b/src/lib/react-query/queryOptions/home.ts index b2bba8c..95336fb 100644 --- a/src/lib/react-query/queryOptions/home.ts +++ b/src/lib/react-query/queryOptions/home.ts @@ -6,7 +6,7 @@ import { AxiosError } from "axios"; interface UseQueryGoalsOptions extends UseQueryOptions {} -type GenerateQoGetGoals = (userId: string) => UseQueryGoalsOptions; +type GenerateQoGetGoals = (userId: number) => UseQueryGoalsOptions; export const generate_qo_getGoals = ((userId) => { const options: UseQueryGoalsOptions = { @@ -18,6 +18,6 @@ export const generate_qo_getGoals = ((userId) => { }; return options; -}) as GenerateQoGetGoals & { DELETE_KEY: (userId: string) => string[] }; +}) as GenerateQoGetGoals & { DELETE_KEY: (userId: number) => [string, number] }; generate_qo_getGoals.DELETE_KEY = (userId) => [BASE_PATH, userId]; diff --git a/src/stores/auth-store.ts b/src/stores/auth-store.ts index 9c81b1b..6710b40 100644 --- a/src/stores/auth-store.ts +++ b/src/stores/auth-store.ts @@ -5,18 +5,22 @@ interface AuthState { refreshToken: string | null; isAuthenticated: boolean; userId: number | null; + socialId: number | null; setTokens: ( accessToken: string, refreshToken: string, userId: number ) => void; + logout: () => void; } export const useAuthStore = create((set) => ({ accessToken: null, refreshToken: null, isAuthenticated: false, + socialId: null, userId: null, setTokens: (accessToken, refreshToken, userId) => - set({ accessToken, refreshToken, isAuthenticated: true, userId }) + set({ accessToken, refreshToken, isAuthenticated: true, userId }), + logout: () => set({ isAuthenticated: false }) })); diff --git a/src/stores/user.ts b/src/stores/user.ts index 3fdb948..258e82b 100644 --- a/src/stores/user.ts +++ b/src/stores/user.ts @@ -3,8 +3,8 @@ import { create } from "zustand"; interface UserState { userName: string; point: number; - userId: string; - setUserId: (userId: string) => void; + userId: number; + setUserId: (userId: number) => void; setPoint: (point: number) => void; addPoint: (addPoint: number) => void; setUserName: (userName: string) => void; @@ -13,7 +13,7 @@ interface UserState { export const useUserStore = create((set) => ({ userName: "", point: 0, - userId: "0", + userId: 0, setUserId: (userId) => set({ userId }), setPoint: (point) => set({ point }), addPoint: (addPoint: number) => From 94c5ed3b3e451d3a2360824f9fbe78e21b5249eb Mon Sep 17 00:00:00 2001 From: yj-leee Date: Mon, 3 Mar 2025 03:16:09 +0900 Subject: [PATCH 10/10] =?UTF-8?q?fix:=20=ED=83=80=EC=9E=85=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/goal/api/goal.ts | 2 +- src/features/goal/api/path.ts | 4 ++-- src/features/goal/routes/CreateGoal.tsx | 8 +++++--- .../map-certification/components/certification-button.tsx | 7 ++++++- src/features/reward/api/path.ts | 7 ++++--- src/features/reward/api/reward.ts | 4 ++-- src/lib/react-query/queryOptions/goals.ts | 4 ++-- src/lib/react-query/queryOptions/reward.ts | 2 +- src/testing/mocks/handlers/browser.ts | 2 +- 9 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/features/goal/api/goal.ts b/src/features/goal/api/goal.ts index bab3a4c..2e72256 100644 --- a/src/features/goal/api/goal.ts +++ b/src/features/goal/api/goal.ts @@ -15,7 +15,7 @@ export function getGoalsCheck({ export function getGoalsComplete({ pathParams: { userId } -}: RoOnlyPathParamsType<{ userId: string }>): R { +}: RoOnlyPathParamsType<{ userId: number }>): R { return GET({ url: GOALS_COMPLETE(userId) }); } diff --git a/src/features/goal/api/path.ts b/src/features/goal/api/path.ts index a79cb0e..cec56aa 100644 --- a/src/features/goal/api/path.ts +++ b/src/features/goal/api/path.ts @@ -3,5 +3,5 @@ import { generatePathByBase, genreateBasePath } from "@/lib/axios/utils"; export const BASE_PATH = genreateBasePath("goals", "v1"); export const GOALS_CHECK = generatePathByBase(BASE_PATH, "check"); -export const GOALS_COMPLETE = (userId: string) => - generatePathByBase(GOALS_CHECK, "complete", userId); +export const GOALS_COMPLETE = (userId: number) => + generatePathByBase(GOALS_CHECK, "complete", String(userId)); diff --git a/src/features/goal/routes/CreateGoal.tsx b/src/features/goal/routes/CreateGoal.tsx index b8a6c5e..0c466d5 100644 --- a/src/features/goal/routes/CreateGoal.tsx +++ b/src/features/goal/routes/CreateGoal.tsx @@ -51,7 +51,9 @@ const CreateGoal: React.FC = ({ goalId }) => { const [balancePoint, setBalancePoint] = useState(0); const [selectedDays, setSelectedDays] = useState([]); const [center, setCenter] = useState({ lat: 33.450701, lng: 126.570667 }); - const [position, setPosition] = useState(null); + const [position, setPosition] = useState<{ lat: number; lng: number } | null>( + null + ); const isFormValid = useMemo(() => { return ( @@ -164,8 +166,8 @@ const CreateGoal: React.FC = ({ goalId }) => { endDate: endDate ? format(endDate, "yyyy-MM-dd") : null, locationName: targetLocation, status, - latitude: position?.lat, - longitude: position?.lng, + latitude: position?.lat ?? 0, + longitude: position?.lng ?? 0, selectedDays: selectedDays.map((day) => DAY_MAPPING[day]) }; diff --git a/src/features/map-certification/components/certification-button.tsx b/src/features/map-certification/components/certification-button.tsx index fb65a60..e330def 100644 --- a/src/features/map-certification/components/certification-button.tsx +++ b/src/features/map-certification/components/certification-button.tsx @@ -5,7 +5,12 @@ interface CertificationButtonProps { buttonDisabled: boolean; isContainRadar: boolean; isPending: boolean; - mutate: UseMutateFunction<{ point: number }, AxiosError, void, unknown>; + mutate: UseMutateFunction< + { totalPoints: number; bonusPoints: number }, + AxiosError, + void, + unknown + >; } function CertificationButton({ diff --git a/src/features/reward/api/path.ts b/src/features/reward/api/path.ts index 487cbeb..736e5ed 100644 --- a/src/features/reward/api/path.ts +++ b/src/features/reward/api/path.ts @@ -2,7 +2,8 @@ import { generatePathByBase, genreateBasePath } from "@/lib/axios/utils"; export const BASE_PATH = genreateBasePath("points", "v1"); -export const POINTS = (userId: string) => generatePathByBase(BASE_PATH, userId); +export const POINTS = (userId: number) => + generatePathByBase(BASE_PATH, String(userId)); -export const POINT_DEDUC = (userId: string) => - generatePathByBase(BASE_PATH, userId, "deduc"); +export const POINT_DEDUC = (userId: number) => + generatePathByBase(BASE_PATH, String(userId), "deduc"); diff --git a/src/features/reward/api/reward.ts b/src/features/reward/api/reward.ts index ea78646..c1e6825 100644 --- a/src/features/reward/api/reward.ts +++ b/src/features/reward/api/reward.ts @@ -12,7 +12,7 @@ export function postRewards({ data }: RoDataAndPathParamsType< { points: number; pointType: string; description: string }, - { userId: string } + { userId: number } >): R<{ point: number; status: string; @@ -22,6 +22,6 @@ export function postRewards({ export function getPoint({ pathParams: { userId } -}: RoOnlyPathParamsType<{ userId: string }>): R { +}: RoOnlyPathParamsType<{ userId: number }>): R { return GET({ url: POINTS(userId) }); } diff --git a/src/lib/react-query/queryOptions/goals.ts b/src/lib/react-query/queryOptions/goals.ts index f00d85c..b404f29 100644 --- a/src/lib/react-query/queryOptions/goals.ts +++ b/src/lib/react-query/queryOptions/goals.ts @@ -73,7 +73,7 @@ generate_qo_getGoalsCheck.DELETE_KEY = (userId) => [GOALS_CHECK, userId]; interface UseQueryGoalsCompleteOptions extends UseQueryOptions {} type GenerateQoGetGoalsComplete = ( - userId: string + userId: number ) => UseQueryGoalsCompleteOptions; export const generate_qo_getGoalsComplete = ((userId) => { const options: UseQueryGoalsCompleteOptions = { @@ -83,6 +83,6 @@ export const generate_qo_getGoalsComplete = ((userId) => { }; return options; -}) as GenerateQoGetGoalsComplete & { DELETE_KEY: (userId: string) => string[] }; +}) as GenerateQoGetGoalsComplete & { DELETE_KEY: (userId: number) => string[] }; generate_qo_getGoalsComplete.DELETE_KEY = (userId) => [GOALS_COMPLETE(userId)]; diff --git a/src/lib/react-query/queryOptions/reward.ts b/src/lib/react-query/queryOptions/reward.ts index 0d798cd..dca8ba9 100644 --- a/src/lib/react-query/queryOptions/reward.ts +++ b/src/lib/react-query/queryOptions/reward.ts @@ -15,7 +15,7 @@ interface UseMutationRewardsOptions RewardData, unknown > {} -type GenerateQoPostRewards = (userId: string) => UseMutationRewardsOptions; +type GenerateQoPostRewards = (userId: number) => UseMutationRewardsOptions; export const generate_qo_postRewards: GenerateQoPostRewards = (userId) => { return { mutationFn: (data: RewardData) => diff --git a/src/testing/mocks/handlers/browser.ts b/src/testing/mocks/handlers/browser.ts index 454e61f..a676037 100644 --- a/src/testing/mocks/handlers/browser.ts +++ b/src/testing/mocks/handlers/browser.ts @@ -35,7 +35,7 @@ export const handlers = [ return HttpResponse.json({ message: "success" }); }), - http.post(`${ENDPOINT_URL}${POINT_DEDUC("1")}`, async () => { + http.post(`${ENDPOINT_URL}${POINT_DEDUC(1)}`, async () => { await delay(500); return HttpResponse.json({ point: 10000 });