Skip to content

Commit 3c87325

Browse files
authored
Merge pull request #100 from Chaellimi/hotfix/#99
[hotfix/#99] 발표전 여러 에러 수정
2 parents cc88cbb + 9410991 commit 3c87325

File tree

10 files changed

+150
-22
lines changed

10 files changed

+150
-22
lines changed

src/app/(admin)/admin/inventory/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ const InventoryManagement = () => {
248248

249249
<div className="flex items-center justify-between mb-3">
250250
<span className="text-lg font-bold text-gray-900">
251-
{inventoryData.product.price}P
251+
{Number(inventoryData.product.price).toLocaleString()}P
252252
</span>
253253
<span className="px-2 py-1 text-xs text-gray-700 bg-gray-100 rounded-full">
254254
{inventoryData.product.category}

src/app/(admin)/admin/product/page.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ const ProductManagement = () => {
270270

271271
<div className="flex items-center justify-between mb-3">
272272
<span className="text-lg font-bold text-gray-900">
273-
{product.price}P
273+
{Number(product.price).toLocaleString()}P
274274
</span>
275275
<span className="px-2 py-1 text-xs text-gray-700 bg-gray-100 rounded-full">
276276
{product.category}
@@ -352,7 +352,9 @@ const ProductManagement = () => {
352352
<label className="block text-sm font-medium text-gray-700">
353353
가격
354354
</label>
355-
<p className="text-gray-900">{selectedProduct.price}P</p>
355+
<p className="text-gray-900">
356+
{Number(selectedProduct.price).toLocaleString()}P
357+
</p>
356358
</div>
357359
<div>
358360
<label className="block text-sm font-medium text-gray-700">

src/app/(private)/challenge/[id]/page.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
import { ChallengeWriteType } from '@/types/Challenge';
3131
import { useGetUserRole } from '@/service/shared/shared.query';
3232
import useShareBridge from '@/lib/hooks/useShareBridge';
33+
import { useBookmark } from '@/hooks/useBookmark';
3334

3435
interface recentChallengesType {
3536
id: number;
@@ -51,7 +52,6 @@ const ChallengeSingle = () => {
5152
const backPath = useSearchParams().get('back');
5253

5354
const [actionSheet, setActionSheet] = useState(false);
54-
const [isBookmarked, setIsBookmarked] = useState(false);
5555
const [isOpenConfirmModal, setIsOpenConfirmModal] = useState(false);
5656
const [isOpenDeleteModal, setIsOpenDeleteModal] = useState(false);
5757
const [isOpenRefusalModal, setIsOpenRefusalModal] = useState(false);
@@ -67,6 +67,7 @@ const ChallengeSingle = () => {
6767
recentChallenges: recentChallengesType[];
6868
totalChallenges: number;
6969
joinStatus: string;
70+
isBookmarked: boolean;
7071
};
7172
};
7273

@@ -77,6 +78,16 @@ const ChallengeSingle = () => {
7778
const recentChallenges = challengeData?.recentChallenges ?? [];
7879
const totalChallenges = challengeData?.totalChallenges ?? [];
7980
const isJoinedChallenge = challengeData?.joinStatus ?? false;
81+
const isBookmarkedData = challengeData?.isBookmarked ?? false;
82+
83+
const {
84+
isBookmarked,
85+
toggleBookmark,
86+
isLoading: isBookmarkLoading,
87+
} = useBookmark({
88+
challengeId: id ? Number(id) : 0,
89+
initialBookmarkState: isBookmarkedData,
90+
});
8091

8192
useStatusBarBridge(
8293
{
@@ -346,8 +357,9 @@ const ChallengeSingle = () => {
346357
<div className="flex items-center justify-center w-full h-16 gap-4 px-6 pt-3 border-t bg-gray-white border-gray-50 custom601:mb-6">
347358
<div
348359
onClick={() => {
349-
setIsBookmarked(!isBookmarked);
360+
toggleBookmark();
350361
}}
362+
className={`cursor-pointer ${isBookmarkLoading ? 'opacity-50 pointer-events-none' : ''}`}
351363
>
352364
<BookmarkIcon
353365
width="24"

src/app/(private)/challenge/[id]/progress/page.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ const Progress = () => {
138138
<div className="grid grid-cols-3 gap-y-4 gap-x-2 justify-items-center">
139139
{progressDates.map((dateStr, index) => {
140140
const today = dayjs().format('YYYY-MM-DD');
141-
const isFeature = new Date(dateStr) <= new Date(today);
141+
const isFeature = new Date(dateStr) < new Date(today);
142142
const isCertified = certifiedSet.has(dateStr);
143143

144144
return (
@@ -193,7 +193,9 @@ const Progress = () => {
193193
<div className="text-gray-black text-b2">적립내역</div>
194194

195195
{progressLog?.pointSavingLog.length === 0 ? (
196-
<div> 이건 뜨면 안됨... </div>
196+
<div className="flex items-center justify-center w-full h-48">
197+
적립금 내역이 없습니다.
198+
</div>
197199
) : (
198200
<div className="flex flex-col">
199201
{progressLog?.pointSavingLog.map((item: pointSavingLogType) => (

src/app/(private)/challenge/page.tsx

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import ChallengeContent from '@/components/Challenge/ChallengeContent';
88
import Header from '@/components/shared/Header';
99
import { ChallengeType, ChallengeFilter } from '@/types/Challenge';
1010
import { useGetChallenge } from '@/service/Challenge/challenge.query';
11+
import { useGetBookmarkList } from '@/service/shared/shared.query';
12+
import { useBookmark } from '@/hooks/useBookmark';
1113
import Loading from '@/components/shared/Loading';
1214

1315
const challengeCategories = [
@@ -75,6 +77,43 @@ const Challenge = () => {
7577
};
7678

7779
const { data: ChallengeData, isPending } = useGetChallenge(filterParams);
80+
const { data: bookmarkData } = useGetBookmarkList();
81+
82+
// 북마크된 챌린지 ID들을 배열로 저장
83+
const bookmarkedChallengeIds =
84+
bookmarkData?.data?.map(
85+
(bookmark: { challengeId: number }) => bookmark.challengeId
86+
) || [];
87+
88+
// 개별 챌린지의 북마크 기능을 위한 컴포넌트
89+
const ChallengeWithBookmark = ({
90+
challenge,
91+
}: {
92+
challenge: ChallengeType;
93+
}) => {
94+
const isInitiallyBookmarked = bookmarkedChallengeIds.includes(challenge.id);
95+
const { isBookmarked, toggleBookmark, isLoading } = useBookmark({
96+
challengeId: challenge.id,
97+
initialBookmarkState: isInitiallyBookmarked,
98+
});
99+
100+
return (
101+
<ChallengeContent
102+
key={challenge.id}
103+
id={challenge.id}
104+
count={challenge.participantCount}
105+
title={challenge.title}
106+
imgUrl={challenge.imgURL}
107+
days={Number(challenge.day)}
108+
difficulty={challenge.difficulty}
109+
createrName={challenge.User.name}
110+
createrImgUrl={challenge.User.profileImg}
111+
isChecked={isBookmarked}
112+
onClickBookmark={toggleBookmark}
113+
isBookmarkLoading={isLoading}
114+
/>
115+
);
116+
};
78117

79118
const hasActiveFilters = Object.values(filters).some(
80119
(v) => v !== '전체' && v !== '인기순'
@@ -156,17 +195,7 @@ const Challenge = () => {
156195
<div className="flex-1 h-0 min-h-0 px-8 pb-16 mt-2 overflow-y-scroll scrollbar-hide -webkit-overflow-scrolling-touch overscroll-contain">
157196
<div className="grid grid-cols-2 gap-x-5 gap-y-5">
158197
{ChallengeData?.data?.challenges?.map((item: ChallengeType) => (
159-
<ChallengeContent
160-
key={item.id}
161-
id={item.id}
162-
count={item.participantCount}
163-
title={item.title}
164-
imgUrl={item.imgURL}
165-
days={Number(item.day)}
166-
difficulty={item.difficulty}
167-
createrName={item.User.name}
168-
createrImgUrl={item.User.profileImg}
169-
/>
198+
<ChallengeWithBookmark key={item.id} challenge={item} />
170199
))}
171200
</div>
172201
</div>

src/app/api/challenge/[id]/route.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ import { NextRequest } from 'next/server';
44
import { Op, fn, col } from 'sequelize';
55
import resUtil from '@/lib/utils/responseUtil';
66
import getUserFromRequest from '@/lib/utils/getUserFromRequest';
7-
import { Challenge, ChallengeParticipants, Users } from '@/database/models';
7+
import {
8+
Bookmark,
9+
Challenge,
10+
ChallengeParticipants,
11+
Users,
12+
} from '@/database/models';
813

914
async function getHandler(req: NextRequest) {
1015
try {
@@ -82,6 +87,18 @@ async function getHandler(req: NextRequest) {
8287
}
8388
}
8489

90+
let isBookmarked = false;
91+
if (loginUser) {
92+
isBookmarked = (await Bookmark.findOne({
93+
where: {
94+
userId: loginUser.id,
95+
challengeId,
96+
},
97+
}))
98+
? true
99+
: false;
100+
}
101+
85102
return resUtil.successTrue({
86103
status: 200,
87104
message: '챌린지 조회 성공',
@@ -90,6 +107,7 @@ async function getHandler(req: NextRequest) {
90107
totalChallenges,
91108
recentChallenges,
92109
joinStatus,
110+
isBookmarked,
93111
},
94112
});
95113
} catch (err) {

src/components/Challenge/ChallengeContent.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ interface OwnProps {
1414
days: number;
1515
isChecked?: boolean;
1616
onClickBookmark?: () => void;
17+
isBookmarkLoading?: boolean;
1718
link?: string;
1819
}
1920

@@ -28,6 +29,7 @@ const ChallengeContent = ({
2829
days,
2930
isChecked = false,
3031
onClickBookmark,
32+
isBookmarkLoading = false,
3133
link = `/challenge/${id}`,
3234
}: OwnProps) => {
3335
return (
@@ -43,14 +45,15 @@ const ChallengeContent = ({
4345
>
4446
<div className="flex flex-col justify-between h-full w-full px-[0.66rem] py-2">
4547
<button
46-
className="flex justify-end w-full"
48+
className={`flex justify-end w-full ${isBookmarkLoading ? 'opacity-50 pointer-events-none' : ''}`}
4749
onClick={(e) => {
4850
e.stopPropagation();
4951
e.preventDefault();
50-
if (onClickBookmark) {
52+
if (onClickBookmark && !isBookmarkLoading) {
5153
onClickBookmark();
5254
}
5355
}}
56+
disabled={isBookmarkLoading}
5457
>
5558
<BookmarkIcon isChecked={isChecked && isChecked} />
5659
</button>

src/hooks/useBookmark.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { useState, useEffect } from 'react';
2+
import { usePostBookmarkUpdate } from '@/service/shared/shared.query';
3+
import { useQueryClient } from '@tanstack/react-query';
4+
import { sharedKeys } from '@/service/shared/shared.key';
5+
6+
interface UseBookmarkProps {
7+
challengeId: number;
8+
initialBookmarkState?: boolean;
9+
}
10+
11+
export const useBookmark = ({
12+
challengeId,
13+
initialBookmarkState = false,
14+
}: UseBookmarkProps) => {
15+
const [isBookmarked, setIsBookmarked] = useState(initialBookmarkState);
16+
const queryClient = useQueryClient();
17+
const bookmarkMutation = usePostBookmarkUpdate();
18+
19+
// 초기 북마크 상태가 변경되면 로컬 상태도 업데이트
20+
useEffect(() => {
21+
setIsBookmarked(initialBookmarkState);
22+
}, [initialBookmarkState]);
23+
24+
const toggleBookmark = async () => {
25+
// 낙관적 업데이트: UI를 즉시 변경
26+
const previousState = isBookmarked;
27+
setIsBookmarked(!isBookmarked);
28+
29+
try {
30+
await bookmarkMutation.mutateAsync(challengeId);
31+
32+
// 성공 시 북마크 리스트 다시 가져오기
33+
queryClient.invalidateQueries({
34+
queryKey: [sharedKeys.useGetBookmarkList],
35+
});
36+
} catch (error) {
37+
// 실패 시 이전 상태로 롤백
38+
setIsBookmarked(previousState);
39+
console.error('북마크 업데이트 실패:', error);
40+
// 사용자에게 에러 알림 (선택사항)
41+
alert('북마크 업데이트에 실패했습니다. 다시 시도해주세요.');
42+
}
43+
};
44+
45+
return {
46+
isBookmarked,
47+
toggleBookmark,
48+
isLoading: bookmarkMutation.isPending,
49+
};
50+
};

src/service/Admin/admin.api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const editAdminUser = async (userData: {
1818
role: string;
1919
profileImg?: string;
2020
}) => {
21-
const { data } = await axios.post('/api/admin/users/edit', userData);
21+
const { data } = await axios.put('/api/admin/users', userData);
2222
return data;
2323
};
2424

src/service/shared/shared.query.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,15 @@ export const usePostUploadImg = () => {
2828
mutationFn: API.postUploadImg,
2929
});
3030
};
31+
32+
export const usePostBookmarkUpdate = () => {
33+
return useMutation({
34+
mutationFn: API.postBookmarkUpdate,
35+
});
36+
};
37+
38+
export const useLogoutUser = () => {
39+
return useMutation({
40+
mutationFn: API.logoutUser,
41+
});
42+
};

0 commit comments

Comments
 (0)