diff --git a/src/components/ui/header/index.const.ts b/src/components/ui/header/index.const.ts index b4fc1ce..89bd46b 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 / 10000) * 100; else return 100; }; diff --git a/src/components/ui/header/profile-header.tsx b/src/components/ui/header/profile-header.tsx index 8f8a755..689a712 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,50 @@ 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 ( + <> + 커피쿠폰까지 {5000 - point}p + + ); + } else if (keyword === "convenienceStore") { + return ( + <> + 편의점쿠폰까지{" "} + {10000 - point}p + + ); + } else { + return ( + + 리워드를 수령해주세요. + + ); + } + }; return { icon: , progressPercent, currentPointText, - targetPointText + targetPointText: generateTagetPointText() }; - }, []); + }, [point]); useEffect(() => { const randomBg = @@ -76,8 +92,8 @@ function ProfileHeader() {

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

오늘도 일단 가볼까요?

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 0d7a8ac..258f2d3 100644 --- a/src/features/auth/routes/GoogleCallback.tsx +++ b/src/features/auth/routes/GoogleCallback.tsx @@ -12,7 +12,7 @@ import { useUserStore } from "@/stores/user"; import { paths } from "@/config/paths"; const GoogleCallback = (): JSX.Element | null => { - const { setUserName, setPoint } = useUserStore(); + const { setUserName, setPoint, setUserId } = useUserStore(); const [searchParams] = useSearchParams(); const navigate = useNavigate(); const setTokens = useAuthStore((state) => state.setTokens); @@ -28,10 +28,12 @@ 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); const { totalPoints } = await getPoint({ pathParams: { userId } }); setPoint(totalPoints); @@ -44,7 +46,7 @@ const GoogleCallback = (): JSX.Element | null => { setIsLoading(false); } }, - [navigate, setTokens, setUserName, setPoint] + [navigate, setTokens, setUserName, setPoint, setUserId] ); useEffect(() => { diff --git a/src/features/auth/routes/KakaoCallback.tsx b/src/features/auth/routes/KakaoCallback.tsx index f9d9fdd..478c970 100644 --- a/src/features/auth/routes/KakaoCallback.tsx +++ b/src/features/auth/routes/KakaoCallback.tsx @@ -12,7 +12,7 @@ import { useUserStore } from "@/stores/user"; import { paths } from "@/config/paths"; const KakaoCallback = (): JSX.Element | null => { - const { setUserName, setPoint } = useUserStore(); + const { setUserName, setPoint, setUserId } = useUserStore(); const [searchParams] = useSearchParams(); const navigate = useNavigate(); const setTokens = useAuthStore((state) => state.setTokens); @@ -30,15 +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(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); @@ -46,7 +50,7 @@ const KakaoCallback = (): JSX.Element | null => { setIsLoading(false); } }, - [navigate, setTokens, setUserName, setPoint] + [navigate, setTokens, setUserName, setPoint, setUserId] ); useEffect(() => { diff --git a/src/features/goal/api/goal.ts b/src/features/goal/api/goal.ts index cb39fb2..2e72256 100644 --- a/src/features/goal/api/goal.ts +++ b/src/features/goal/api/goal.ts @@ -3,27 +3,32 @@ import { GoalData } from "@/features/goal/types/goal-create"; import type { R } from "@/types/common.ts"; import { GET, POST } from "@/lib/axios"; +import { RoOnlyPathParamsType, RoOnlyQueryType } 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({ + query +}: RoOnlyQueryType<{ userId: number }>): R { + return GET({ url: GOALS_CHECK, params: query }); } -export function getGoalsComplete(): R { - return GET({ url: GOALS_COMPLETE }); +export function getGoalsComplete({ + pathParams: { userId } +}: RoOnlyPathParamsType<{ userId: number }>): R { + return GET({ url: GOALS_COMPLETE(userId) }); } 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/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/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/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..21faebd 100644 --- a/src/features/goal/components/goal/progress-goal.tsx +++ b/src/features/goal/components/goal/progress-goal.tsx @@ -1,8 +1,10 @@ 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"; +import { useUserStore } from "@/stores/user"; import { generate_qo_getGoalsCheck } from "@/lib/react-query/queryOptions/goals"; import type { CertificationInfo } from "../../types"; import { @@ -15,28 +17,29 @@ 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) => { + console.log(progressGoals); + const transFormViewDays = map(goal.calender, (viewDay) => { const isCertificationInfo = certificationInfoMaps[index].has(viewDay); if (!isCertificationInfo) { @@ -56,7 +59,10 @@ 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 = 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); @@ -73,7 +79,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/routes/CreateGoal.tsx b/src/features/goal/routes/CreateGoal.tsx index 9d8ed44..0c466d5 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 || "" @@ -45,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 ( @@ -57,8 +65,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 +86,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 +160,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 ?? 0, + longitude: position?.lng ?? 0, + 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/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/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..32f05aa 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"; @@ -22,41 +21,41 @@ function GoalList({ goalCount, transformGoals }: GoalListProps) { {map( transformGoals, ({ - id, + goalId, title, - name, + goalName, lastRowText, - isAchieved, - isTemporarySaved, + achievedToday, + status, buttonText, - redirectionUrl + redirectionCallback }) => (

{title}

- {name} + {goalName}

{lastRowText}

- - - + +
) diff --git a/src/features/home/components/home.tsx b/src/features/home/components/home.tsx index b553594..aacc315 100644 --- a/src/features/home/components/home.tsx +++ b/src/features/home/components/home.tsx @@ -6,15 +6,18 @@ 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"; 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 navigate = useNavigate(); + const { userId } = useUserStore(); + const { data: goals = [] } = useQuery(generate_qo_getGoals(userId)); const goalCount = useMemo(() => { if (goals.length === 0) return 0; @@ -41,24 +44,30 @@ 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 === "DRAFT" + ? "목표등록을 마무리하고 바로 시작해보세요!" + : `${generatDdateText(item.startDate, item.endDate)} ${generateDayText(item.dayOfWeek)}`; + const buttonText = `${item.status === "DRAFT" ? "완성하기" : "인증하기"} >`; + const redirectionCallback = () => { + const url = + item.status === "DRAFT" + ? paths.goal.create.getHref() + : paths.map.certification.getHref(); + + navigate(url, { state: { goalId: item.goalId, userId } }); + }; return { ...item, - title: `${item.isTemporarySaved ? "임시저장" : "진행중"} 목표`, - name: item.name, + title: `${item.status === "DRAFT" ? "임시저장" : "진행중"} 목표`, + name: item.goalName, lastRowText, buttonText, - redirectionUrl + redirectionCallback }; }), - [goals] + [goals, navigate, userId] ); return ( 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..3812866 100644 --- a/src/features/home/types/index.ts +++ b/src/features/home/types/index.ts @@ -1,18 +1,18 @@ -export type WeekDay = "MON" | "TUE" | "WED" | "THU" | "FRI" | "SAT" | "SUN"; +export type STATUS = "DRAFT" | "ACTIVE" | "COMPLETE"; export interface Goals { - id: number; - name: string; + goalId: number; + goalName: string; startDate: string; endDate: string; - days: WeekDay[]; - isTemporarySaved: boolean; - isAchieved: boolean; + dayOfWeek: string; + status: STATUS; + achievedToday: boolean; } 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..86da477 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<{ totalPoints: number; bonusPoints: 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/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/map-certification/components/map-certification.const.ts b/src/features/map-certification/components/map-certification.const.ts index 720ff71..c206ee3 100644 --- a/src/features/map-certification/components/map-certification.const.ts +++ b/src/features/map-certification/components/map-certification.const.ts @@ -5,7 +5,7 @@ export const generateInitialGoalsData = () => ({ 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 ef0e426..e6d5c71 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, useQueryClient } 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 { useUserStore } from "@/stores/user"; import { @@ -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"; @@ -31,7 +32,9 @@ function MapCertification() { // Hooks const { addPoint } = useUserStore(); const navigate = useNavigate(); + const location = useLocation(); const client = useQueryClient(); + const { userId, goalId } = location.state || {}; const { center, position, setCenterToMyPosition, updateCenterWhenMapMoved } = useUserLocation(); @@ -40,34 +43,40 @@ function MapCertification() { name, startDate, endDate, - days, + dayOfWeek: days, latitude: serverLatitude, longitude: serverLongitude } = generateInitialGoalsData() - } = useQuery(generate_qo_getGoals(1)); + } = useQuery(generate_qo_getGoals(goalId)); const { mutate, isPending } = useMutation({ - ...generate_qo_postGoalsAchieve(1), + ...generate_qo_postGoalsAchieve({ + goalId, + userId, + latitude: position.lat, + 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); - + 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 = @@ -83,8 +92,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/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/features/map-certification/types/index.ts b/src/features/map-certification/types/index.ts index c6936bc..a8801f8 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; +export interface GoalDetail { name: string; startDate: string; endDate: string; latitude: number; longitude: number; - days: WeekDay[]; + dayOfWeek: string; + status: STATUS; + achievedToday: boolean; } 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/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 492af7b..c1e6825 100644 --- a/src/features/reward/api/reward.ts +++ b/src/features/reward/api/reward.ts @@ -1,17 +1,27 @@ import type { R } from "@/types/common"; import { GET, POST } from "@/lib/axios"; -import type { RoOnlyPathParamsType } from "@/lib/axios/utils"; +import type { + RoDataAndPathParamsType, + RoOnlyPathParamsType +} from "@/lib/axios/utils"; import type { Point } from "../types"; import { POINT_DEDUC, POINTS } from "./path"; export function postRewards({ - pathParams: { userId } -}: RoOnlyPathParamsType<{ userId: string }>): R<{ point: number }> { - return POST({ url: POINT_DEDUC(userId) }); + pathParams: { userId }, + data +}: RoDataAndPathParamsType< + { points: number; pointType: string; description: string }, + { userId: number } +>): R<{ + point: number; + status: string; +}> { + return POST({ url: POINT_DEDUC(userId), data }); } export function getPoint({ pathParams: { userId } -}: RoOnlyPathParamsType<{ userId: string }>): R { +}: RoOnlyPathParamsType<{ userId: number }>): R { return GET({ url: POINTS(userId) }); } 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 c6c7b63..6dc844f 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(userId), onSuccess: (data) => { + if (data.status === "error") { + toast.error("리워드 신청에 실패했습니다."); + return; + } + setPoint(data.point); toast(, { position: "bottom-center", @@ -26,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), @@ -44,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/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 2800583..b404f29 100644 --- a/src/lib/react-query/queryOptions/goals.ts +++ b/src/lib/react-query/queryOptions/goals.ts @@ -2,62 +2,87 @@ 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) => { + console.log(data, "목표 인증 데이터"); + return 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 -) => { + extends UseMutationOptions< + { totalPoints: number; bonusPoints: number }, + AxiosError, + void, + unknown + > {} +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 } + }) }; }; interface UseQueryGoalsCheckOptions - extends UseQueryOptions { - DELETE_KEY?: [string]; -} -type GenerateQoGetGoalsCheck = () => UseQueryGoalsCheckOptions; -export const generate_qo_getGoalsCheck = (() => { + extends UseQueryOptions {} +type GenerateQoGetGoalsCheck = (userId: number) => UseQueryGoalsCheckOptions; +export const generate_qo_getGoalsCheck = ((userId) => { const options: UseQueryGoalsCheckOptions = { - queryKey: [GOALS_CHECK], - queryFn: () => getGoalsCheck().then((data) => data) + queryKey: [GOALS_CHECK, userId], + queryFn: () => getGoalsCheck({ query: { userId } }).then((data) => data) }; return options; -}) as GenerateQoGetGoalsCheck & { DELETE_KEY: () => [string] }; +}) as GenerateQoGetGoalsCheck & { DELETE_KEY: (userId: string) => string[] }; -generate_qo_getGoalsCheck.DELETE_KEY = () => [GOALS_CHECK]; +generate_qo_getGoalsCheck.DELETE_KEY = (userId) => [GOALS_CHECK, userId]; interface UseQueryGoalsCompleteOptions extends UseQueryOptions {} -type GenerateQoGetGoalsComplete = () => UseQueryGoalsCompleteOptions; -export const generate_qo_getGoalsComplete = (() => { +type GenerateQoGetGoalsComplete = ( + userId: number +) => UseQueryGoalsCompleteOptions; +export const generate_qo_getGoalsComplete = ((userId) => { const options: UseQueryGoalsCompleteOptions = { - queryKey: [GOALS_COMPLETE], - queryFn: () => getGoalsComplete().then((data) => data) + queryKey: [GOALS_COMPLETE(userId)], + queryFn: () => + getGoalsComplete({ pathParams: { userId } }).then((data) => data) }; return options; -}) as GenerateQoGetGoalsComplete & { DELETE_KEY: () => [string] }; +}) as GenerateQoGetGoalsComplete & { DELETE_KEY: (userId: number) => string[] }; -generate_qo_getGoalsComplete.DELETE_KEY = () => [GOALS_COMPLETE]; +generate_qo_getGoalsComplete.DELETE_KEY = (userId) => [GOALS_COMPLETE(userId)]; diff --git a/src/lib/react-query/queryOptions/home.ts b/src/lib/react-query/queryOptions/home.ts index f4d1351..95336fb 100644 --- a/src/lib/react-query/queryOptions/home.ts +++ b/src/lib/react-query/queryOptions/home.ts @@ -6,14 +6,18 @@ import { AxiosError } from "axios"; interface UseQueryGoalsOptions extends UseQueryOptions {} -type GenerateQoGetGoals = () => UseQueryGoalsOptions; +type GenerateQoGetGoals = (userId: number) => UseQueryGoalsOptions; -export const generate_qo_getGoals: GenerateQoGetGoals = () => { - return { - queryKey: [BASE_PATH], +export const generate_qo_getGoals = ((userId) => { + const options: UseQueryGoalsOptions = { + queryKey: [BASE_PATH, userId], queryFn: () => - getGoals().then((data) => { + getGoals({ pathParams: { userId } }).then((data) => { return data; }) }; -}; + + return options; +}) as GenerateQoGetGoals & { DELETE_KEY: (userId: number) => [string, number] }; + +generate_qo_getGoals.DELETE_KEY = (userId) => [BASE_PATH, userId]; diff --git a/src/lib/react-query/queryOptions/reward.ts b/src/lib/react-query/queryOptions/reward.ts index f2e6bf7..dca8ba9 100644 --- a/src/lib/react-query/queryOptions/reward.ts +++ b/src/lib/react-query/queryOptions/reward.ts @@ -2,11 +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 }, AxiosError, void, unknown> {} -type GenerateQoPostRewards = (userId: string) => UseMutationRewardsOptions; + extends UseMutationOptions< + { point: number; status: string }, + AxiosError, + RewardData, + unknown + > {} +type GenerateQoPostRewards = (userId: number) => UseMutationRewardsOptions; export const generate_qo_postRewards: GenerateQoPostRewards = (userId) => { return { - mutationFn: () => postRewards({ pathParams: { userId } }) + mutationFn: (data: RewardData) => + postRewards({ pathParams: { userId }, data }) }; }; 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 58e8bb7..258e82b 100644 --- a/src/stores/user.ts +++ b/src/stores/user.ts @@ -3,16 +3,18 @@ import { create } from "zustand"; interface UserState { userName: string; point: number; - userId: string; + userId: number; + setUserId: (userId: number) => void; setPoint: (point: number) => void; addPoint: (addPoint: number) => void; setUserName: (userName: string) => void; } export const useUserStore = create((set) => ({ - userName: "park", - point: 100000, - userId: "0", + userName: "", + point: 0, + userId: 0, + setUserId: (userId) => set({ userId }), setPoint: (point) => set({ point }), addPoint: (addPoint: number) => set((state) => ({ point: state.point + addPoint })), 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 });