Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
7 commits
Select commit Hold shift + click to select a range
523c362
fix:[#330] zod๋กœ ์ธํ•˜์—ฌ, ํ™ˆ์ด ๋‚˜์˜ค์ง€ ์•Š๋˜ ๋ถ€๋ถ„ ์ˆ˜์ •
cywin1018 Sep 7, 2025
e289db6
fix:[#330] a๋งํฌ๋ฅผ ์ด์šฉํ•˜์—ฌ url์„ ํ†ตํ•ด ์ด๋™์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ์ˆ˜์ •
cywin1018 Sep 7, 2025
06bca4e
feat: [#330]ํ™ˆ ๋ฆฌํฌ๋ฃจํŠธ ์นด๋“œ ํด๋ฆญ ์‹œ ์ƒ์„ธ ๋ชจ๋‹ฌ ์ถ”๊ฐ€ ๋ฐ ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ ๋กœ์ง ๊ตฌํ˜„
cywin1018 Sep 7, 2025
6a0aea2
fix: [#330] ์ฒดํฌ๋ฆฌ์ŠคํŠธ์—์„œ AddIcon ์ œ๊ฑฐ ๋ฐ ์ปค๋ฎค๋‹ˆํ‹ฐ ์™ผ์ชฝ ์‚ฌ์ด๋“œ๋ฐ”์—์„œ ๊ธฐ๋ณธ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์ถ”๊ฐ€, ํ™ˆ ๋“œ๋ฆฌ๋จธ์—โ€ฆ
cywin1018 Sep 7, 2025
7453db5
feat: [#330] ํ• ์ผ ์ถ”๊ฐ€ ๋ฐ ์ทจ์†Œ ๊ธฐ๋Šฅ ๊ตฌํ˜„, ํ† ์ŠคํŠธ ๋ฉ”์‹œ์ง€ ์ถ”๊ฐ€
cywin1018 Sep 7, 2025
516a051
feat: [#330] HotPopularItem ์ธํ„ฐํŽ˜์ด์Šค์— todoGroupId ์ถ”๊ฐ€ ๋ฐ ๋ถˆํ•„์š”ํ•œ console.log ์ œ๊ฑฐ
cywin1018 Sep 7, 2025
a5186aa
feat: [#330] ํ• ์ผ ์ถ”๊ฐ€ ์‹œ Amplitude ์ด๋ฒคํŠธ ํŠธ๋ž˜ํ‚น ๊ธฐ๋Šฅ ์ถ”๊ฐ€ ๋ฐ ๊ด€๋ จ ์ฝ”๋“œ ์ˆ˜์ •
cywin1018 Sep 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions src/common/CheckList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { useDeleteTodoMutation } from '@hook/todo/useDeleteTodoMutation';
import { ReactTagManager } from 'react-gtm-ts';
import { useAddTodoMutation } from '@hook/todo/useAddTodoMutation.ts';
import Plus from '@assets/icons/plus.svg?react';
import AddIcon from '@assets/icons/AddIcon.svg?react';
import BookMarkIcon from '@assets/icons/bookmark.svg?react';
import { useUpdateTodoMutation } from '@hook/todo/useUpdateTodoMutation.ts';

Expand Down Expand Up @@ -84,7 +83,7 @@ const CheckList = ({
});
console.log('Amplitude event sent: todo_create_attempt (inpage)');
}

mutate(
{ todoTitle: trimmedText },
{
Expand Down Expand Up @@ -263,10 +262,6 @@ const CheckList = ({
</>
) : (
<>
<div className="mr-3 flex items-center gap-1 text-gray-500">
<AddIcon className="h-[18px] w-[18px]" />
<span className="text-sm font-B03-SB">3</span>
</div>
<div className="mr-3 flex items-center gap-1 text-gray-500">
<BookMarkIcon className="h-[18px] w-[18px]" />
<span className="text-sm font-B03-SB">999</span>
Expand Down
1 change: 1 addition & 0 deletions src/hook/useHomeQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ interface RecruitProps {
active: number;
jobName: string;
postDate: string;
url: string;
}
const NewRecruit = async (pageNum: number, postDate: string) => {
const token = localStorage.getItem('accessToken');
Expand Down
11 changes: 6 additions & 5 deletions src/pages/community/components/CommunityLeftSide.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useGetHotPopularQuery } from '@hook/community/query/useGetHotPopularQue
import Bookmark from '@assets/icons/bookmark.svg?react';
import Arrow from '@assets/icons/arrow.svg?react';
import jobNames, { findJobIdByName } from '@utils/data/community/jobs';
import BaseImage from '@assets/images/profile.png';

const CommunityLeftSide = () => {
const navigate = useNavigate();
Expand All @@ -27,7 +28,6 @@ const CommunityLeftSide = () => {
<div
className="mt-[30px] flex w-full cursor-pointer flex-row items-center justify-end text-gray-500 font-B02-SB"
onClick={() => {
// ํ•˜๋“œ์ฝ”๋”ฉ ๋งคํ•‘๋œ jobId ์‚ฌ์šฉ
const id = findJobIdByName(selectedJobName);
navigate(`/others/${id ?? 1}`);
}}
Expand All @@ -46,23 +46,24 @@ const CommunityLeftSide = () => {
{popularTodos.map((item, idx) => (
<div
key={item.id}
className="flex w-full flex-row items-start justify-between py-4"
className="flex w-full cursor-pointer flex-row items-start justify-between py-4"
onClick={() => navigate(`/otherslist/${item.id}`)}
>
<div className="flex flex-row items-center gap-[15px]">
<div className="text-purple-500 font-T05-SB">{idx + 1}</div>
<div className="flex flex-row items-center gap-[15px]">
<img
src={item.imageUrl}
src={item.imageUrl || BaseImage}
alt="ํ”„๋กœํ•„์ด๋ฏธ์ง€"
className="h-[30px] w-[30px] rounded-full bg-gray-50"
/>

<div className="flex flex-col">
<div className="flex w-full flex-row items-start justify-between gap-[10px]">
<div className="flex-1 break-words text-black font-B01-SB">
<div className="w-[170px] min-w-0 flex-1 truncate text-black font-B01-SB">
{item.description}
</div>
<div className="mr-2 shrink-0 whitespace-nowrap text-gray-500 font-C01-R">
<div className="shrink-0 whitespace-nowrap text-gray-500 font-C01-R">
{item.dDay}
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/home/components/HomeDreamer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const HomeDreamer = () => {
<div className="text-gray-900 font-T02-B">{title}</div>
<div
className="flex cursor-pointer flex-row items-center text-gray-500 font-B02-SB"
onClick={() => navigate('/jobfound')}
onClick={() => navigate('/community')}
>
๋” ๋ณด๋Ÿฌ๊ฐ€๊ธฐ
<Arrow />
Expand Down
78 changes: 77 additions & 1 deletion src/pages/home/components/HomeRecruit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,77 @@ import { useRecruitQuery } from '@hook/useHomeQuery';
import LoadingSpinner from '@common/LoadingSpinner';
import { useFilterStore } from '@store/filterStore';
import { useNavigate } from 'react-router-dom';
import CardDetail from '@pages/jobSearch/components/CardDetail';
import { RecruitItem } from '@validation/recruit/recruitSchema';

interface RecruitData {
id: number;
title: string;
companyName: string;
jobName: string;
postDate: string;
count: number | string;
url: string;
active: number;
locationName?: string | null;
jobTypeName?: string;
experienceLevel?: string;
requiredEducationLevel?: string;
closeType?: string;
salary?: string;
postTimestamp?: string;
'expiration-timestamp'?: string;
'expiration-date'?: string;
deadline?: string;
}

const HomeRecruit = () => {
const regionName = useUserStore((state) => state.regionName);
const setSelection = useFilterStore((state) => state.setSelection);
const [likedItems, setLikedItems] = useState<{ [key: number]: boolean }>({});
const [selectedCard, setSelectedCard] = useState<RecruitItem | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);

const toggleLike = (id: number) => {
setLikedItems((prev) => ({
...prev,
[id]: !prev[id],
}));
};

const convertToRecruitItem = (data: RecruitData): RecruitItem => {
return {
url: data.url || '',
active: data.active || 1,
title: data.title || '',
jobName: data.jobName || '',
companyName: data.companyName || '',
locationName: data.locationName || null,
jobTypeName: data.jobTypeName || '',
experienceLevel: data.experienceLevel || '',
requiredEducationLevel: data.requiredEducationLevel || '',
closeType: data.closeType || '',
salary: data.salary || '',
id: String(data.id),
postTimestamp: data.postTimestamp || '',
postDate: data.postDate || '',
'expiration-timestamp': data['expiration-timestamp'] || '',
'expiration-date': data['expiration-date'] || '',
deadline: data.deadline || '',
count:
typeof data.count === 'string' ? parseInt(data.count) : data.count || 0,
};
};
Comment on lines +49 to +71
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

โš ๏ธ Potential issue

ํ™œ์„ฑ ์ƒํƒœ(active) 0์ด 1๋กœ ์˜ค์ธ๋  ์ˆ˜ ์žˆ์Œ + ์ •์ˆ˜ ํŒŒ์‹ฑ radix ๋ช…์‹œ

  • active: data.active || 1์€ 0(๋น„ํ™œ์„ฑ)์„ 1๋กœ ๋ฎ์–ด์จ ๋ฒ„๋ฆฝ๋‹ˆ๋‹ค. nullish ๋ณ‘ํ•ฉ(??)์„ ์“ฐ์„ธ์š”.
  • count: parseInt ์‚ฌ์šฉ ์‹œ 10์ง„์ˆ˜ radix๋ฅผ ๋ช…์‹œํ•˜์„ธ์š”.

์ ์šฉ ์ œ์•ˆ:

   const convertToRecruitItem = (data: RecruitData): RecruitItem => {
     return {
       url: data.url || '',
-      active: data.active || 1,
+      active: data.active ?? 1,
       title: data.title || '',
       jobName: data.jobName || '',
       companyName: data.companyName || '',
       locationName: data.locationName || null,
       jobTypeName: data.jobTypeName || '',
       experienceLevel: data.experienceLevel || '',
       requiredEducationLevel: data.requiredEducationLevel || '',
       closeType: data.closeType || '',
       salary: data.salary || '',
       id: String(data.id),
       postTimestamp: data.postTimestamp || '',
       postDate: data.postDate || '',
       'expiration-timestamp': data['expiration-timestamp'] || '',
       'expiration-date': data['expiration-date'] || '',
       deadline: data.deadline || '',
       count:
-        typeof data.count === 'string' ? parseInt(data.count) : data.count || 0,
+        typeof data.count === 'string'
+          ? parseInt(data.count, 10)
+          : (data.count ?? 0),
     };
   };
๐Ÿ“ Committable suggestion

โ€ผ๏ธ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const convertToRecruitItem = (data: RecruitData): RecruitItem => {
return {
url: data.url || '',
active: data.active || 1,
title: data.title || '',
jobName: data.jobName || '',
companyName: data.companyName || '',
locationName: data.locationName || null,
jobTypeName: data.jobTypeName || '',
experienceLevel: data.experienceLevel || '',
requiredEducationLevel: data.requiredEducationLevel || '',
closeType: data.closeType || '',
salary: data.salary || '',
id: String(data.id),
postTimestamp: data.postTimestamp || '',
postDate: data.postDate || '',
'expiration-timestamp': data['expiration-timestamp'] || '',
'expiration-date': data['expiration-date'] || '',
deadline: data.deadline || '',
count:
typeof data.count === 'string' ? parseInt(data.count) : data.count || 0,
};
};
const convertToRecruitItem = (data: RecruitData): RecruitItem => {
return {
url: data.url || '',
active: data.active ?? 1,
title: data.title || '',
jobName: data.jobName || '',
companyName: data.companyName || '',
locationName: data.locationName || null,
jobTypeName: data.jobTypeName || '',
experienceLevel: data.experienceLevel || '',
requiredEducationLevel: data.requiredEducationLevel || '',
closeType: data.closeType || '',
salary: data.salary || '',
id: String(data.id),
postTimestamp: data.postTimestamp || '',
postDate: data.postDate || '',
'expiration-timestamp': data['expiration-timestamp'] || '',
'expiration-date': data['expiration-date'] || '',
deadline: data.deadline || '',
count:
typeof data.count === 'string'
? parseInt(data.count, 10)
: (data.count ?? 0),
};
};
๐Ÿค– Prompt for AI Agents
In src/pages/home/components/HomeRecruit.tsx around lines 49 to 71, the
converter incorrectly treats active: data.active || 1 which turns a legitimate 0
into 1 and parseInt lacks a radix; change to use nullish coalescing for active
(active: data.active ?? 1) so only null/undefined default to 1, and when parsing
count explicitly pass a radix 10 (e.g. parseInt(data.count, 10)) while
preserving the existing fallback to 0 when count is missing or not a number.


const handleCardClick = (data: RecruitData) => {
console.log('Card clicked:', data);
const recruitItem = convertToRecruitItem(data);
console.log('Converted item:', recruitItem);
setSelectedCard(recruitItem);
setIsModalOpen(true);
console.log('Modal should open');
};

const isLoggedIn = localStorage.getItem('accessToken');
const navigate = useNavigate();

Expand Down Expand Up @@ -63,10 +123,14 @@ const HomeRecruit = () => {
<div
key={data.id}
className="flex min-h-[312px] w-[384px] cursor-pointer flex-col items-start rounded-[30px] border-[1.2px] border-gray-300 p-[30px] hover:shadow-shadow2"
onClick={() => handleCardClick(data)}
>
<div
className="flex w-full flex-col items-end"
onClick={() => toggleLike(data.id)}
onClick={(e) => {
e.stopPropagation();
toggleLike(data.id);
}}
>
{likedItems[data.id] ? <FullLike /> : <Like />}
</div>
Expand All @@ -92,6 +156,18 @@ const HomeRecruit = () => {
</div>
))}
</div>

{isModalOpen && selectedCard && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
<CardDetail
item={selectedCard}
onClose={() => {
console.log('Closing modal');
setIsModalOpen(false);
}}
/>
</div>
)}
</div>
);
};
Expand Down
134 changes: 112 additions & 22 deletions src/pages/otherTodoList/components/OtherTodoCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@ import { useNavigate } from 'react-router-dom';
import { useState, useEffect } from 'react';
import LoadingSpinner from '@common/LoadingSpinner';
import { useMemoQuery } from '@hook/mydream/useGetMemo';
import BookMarkIcon from '@assets/icons/bookmark.svg?react';
import { useCommunityAddTodoMutation } from '@hook/community/useCommunityAddTodoMutation';
import { useDeleteCommunityTodosMutation } from '@hook/community/useDeleteCommunityTodos';
import ToastModal from '@common/modal/ToastModal';
import Info from '@assets/icons/info.svg?react';

interface TodoItem {
todoId: number;
title: string;
completed: boolean;

isPublic: boolean;
saveCount?: number;
isSaved?: boolean;
}

interface TodoCardProps {
Expand All @@ -21,6 +27,47 @@ const OtherTodoCard = ({ todos }: TodoCardProps) => {
const navigate = useNavigate();
const [isLoading, setIsLoading] = useState(false);
const [selectedTodoId, setSelectedTodoId] = useState<number | null>(null);
const [added, setAdded] = useState<Record<number, boolean>>({});
const addTodoMutation = useCommunityAddTodoMutation();
const deleteTodoMutation = useDeleteCommunityTodosMutation();
const [showToast, setShowToast] = useState(false);
const [toastMessage, setToastMessage] = useState('');

const toggleAdd = (id: number, isAdded: boolean) => {
if (isAdded) {
deleteTodoMutation.mutate(
{ id },
{
onSuccess: () => {
setAdded((prev) => ({ ...prev, [id]: false }));
setToastMessage('ํ• ์ผ์ด ์ทจ์†Œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
setShowToast(true);
setTimeout(() => setShowToast(false), 2500);
},
onError: () => {
alert('์ถ”๊ฐ€ ์ทจ์†Œ์— ์‹คํŒจํ–ˆ์–ด์š”.');
},
}
);
} else {
addTodoMutation.mutate(
{ id },
{
onSuccess: () => {
setShowToast(true);
setAdded((prev) => ({ ...prev, [id]: true }));
setToastMessage('ํ• ์ผ์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
setTimeout(() => {
setShowToast(false);
}, 2500);
},
onError: () => {
alert('๋‚ด ํ• ์ผ ์ถ”๊ฐ€์— ์‹คํŒจํ–ˆ์–ด์š”.');
},
}
);
}
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐Ÿ› ๏ธ Refactor suggestion

๋น ๋ฅธ ์—ฐํƒ€/์ค‘๋ณต ์š”์ฒญ ๋ฐฉ์ง€ ๋ฐ ํƒ€์ž„์•„์›ƒ ๊ด€๋ฆฌ

์—ฐ์† ํด๋ฆญ ์‹œ add/delete๊ฐ€ ์ค‘์ฒฉ ํ˜ธ์ถœ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ mutation์˜ isPending ์ƒํƒœ๋กœ ๋ฒ„ํŠผ์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๊ณ , setTimeout์€ ํด๋ฆฐ์—…์œผ๋กœ ํ•ด์ œํ•˜์„ธ์š”.

๋‹ค์Œ ํŒจ์น˜ ์˜ˆ์‹œ:

-  const addTodoMutation = useCommunityAddTodoMutation();
-  const deleteTodoMutation = useDeleteCommunityTodosMutation();
+  const {
+    mutate: addMutate,
+    isPending: isAdding,
+  } = useCommunityAddTodoMutation();
+  const {
+    mutate: deleteMutate,
+    isPending: isDeleting,
+  } = useDeleteCommunityTodosMutation();
+  const isMutating = isAdding || isDeleting;
+  const toastTimers = useRef<number[]>([]);
...
-      deleteTodoMutation.mutate(
+      deleteMutate(
         { id },
         {
           onSuccess: () => {
             setAdded((prev) => ({ ...prev, [id]: false }));
             setToastMessage('ํ• ์ผ์ด ์ทจ์†Œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
             setShowToast(true);
-            setTimeout(() => setShowToast(false), 2500);
+            const t = window.setTimeout(() => setShowToast(false), 2500);
+            toastTimers.current.push(t);
           },
...
-      addTodoMutation.mutate(
+      addMutate(
         { id },
         {
           onSuccess: () => {
             setShowToast(true);
             setAdded((prev) => ({ ...prev, [id]: true }));
             setToastMessage('ํ• ์ผ์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
-            setTimeout(() => {
-              setShowToast(false);
-            }, 2500);
+            const t = window.setTimeout(() => setShowToast(false), 2500);
+            toastTimers.current.push(t);
           },
...
+  useEffect(() => {
+    return () => {
+      toastTimers.current.forEach((t) => clearTimeout(t));
+      toastTimers.current = [];
+    };
+  }, []);

๋ฒ„ํŠผ ๋น„ํ™œ์„ฑํ™”๋Š” ํ•˜๋‹จ ๋ฒ„ํŠผ ์˜์—ญ์—์„œ isMutating์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค(์•„๋ž˜ ์ฝ”๋ฉ˜ํŠธ ์ฐธ๊ณ ).

๐Ÿ“ Committable suggestion

โ€ผ๏ธ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const toggleAdd = (id: number, isAdded: boolean) => {
if (isAdded) {
deleteTodoMutation.mutate(
{ id },
{
onSuccess: () => {
setAdded((prev) => ({ ...prev, [id]: false }));
setToastMessage('ํ• ์ผ์ด ์ทจ์†Œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
setShowToast(true);
setTimeout(() => setShowToast(false), 2500);
},
onError: () => {
alert('์ถ”๊ฐ€ ์ทจ์†Œ์— ์‹คํŒจํ–ˆ์–ด์š”.');
},
}
);
} else {
addTodoMutation.mutate(
{ id },
{
onSuccess: () => {
setShowToast(true);
setAdded((prev) => ({ ...prev, [id]: true }));
setToastMessage('ํ• ์ผ์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
setTimeout(() => {
setShowToast(false);
}, 2500);
},
onError: () => {
alert('๋‚ด ํ• ์ผ ์ถ”๊ฐ€์— ์‹คํŒจํ–ˆ์–ด์š”.');
},
}
);
}
};
// Destructure mutate + pending states, track combined mutating status and toast timers
const {
mutate: addMutate,
isPending: isAdding,
} = useCommunityAddTodoMutation();
const {
mutate: deleteMutate,
isPending: isDeleting,
} = useDeleteCommunityTodosMutation();
const isMutating = isAdding || isDeleting;
const toastTimers = useRef<number[]>([]);
const toggleAdd = (id: number, isAdded: boolean) => {
if (isAdded) {
deleteMutate(
{ id },
{
onSuccess: () => {
setAdded((prev) => ({ ...prev, [id]: false }));
setToastMessage('ํ• ์ผ์ด ์ทจ์†Œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
setShowToast(true);
const t = window.setTimeout(() => setShowToast(false), 2500);
toastTimers.current.push(t);
},
onError: () => {
alert('์ถ”๊ฐ€ ์ทจ์†Œ์— ์‹คํŒจํ–ˆ์–ด์š”.');
},
}
);
} else {
addMutate(
{ id },
{
onSuccess: () => {
setShowToast(true);
setAdded((prev) => ({ ...prev, [id]: true }));
setToastMessage('ํ• ์ผ์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
const t = window.setTimeout(() => setShowToast(false), 2500);
toastTimers.current.push(t);
},
onError: () => {
alert('๋‚ด ํ• ์ผ ์ถ”๊ฐ€์— ์‹คํŒจํ–ˆ์–ด์š”.');
},
}
);
}
};
useEffect(() => {
return () => {
toastTimers.current.forEach((t) => clearTimeout(t));
toastTimers.current = [];
};
}, []);


const {
data,
Expand Down Expand Up @@ -54,30 +101,73 @@ const OtherTodoCard = ({ todos }: TodoCardProps) => {
)}

<ul className="w-full flex-grow space-y-4">
{todos.map((item, index) => (
<li key={index} className={`flex items-center px-2 py-1`}>
<div
className={`flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded-[8px] border ${
item.completed
? 'border-gray-500 bg-gray-300'
: 'border-gray-300 bg-gray-100'
}`}
{todos.map((item, index) => {
const isAdded =
typeof added[item.todoId] === 'boolean'
? added[item.todoId]
: item.isSaved || false;

return (
<li
key={index}
className={`flex items-center justify-between px-2 py-1`}
>
{item.completed && <CheckIcon className="h-4 w-8 text-white" />}
</div>
<div className="flex items-center gap-3">
<div
className={`flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded-[8px] border ${
item.completed
? 'border-gray-500 bg-gray-300'
: 'border-gray-300 bg-gray-100'
}`}
>
{item.completed && (
<CheckIcon className="h-4 w-8 text-white" />
)}
</div>

<span
className={`font-B01-M ${
item.completed ? 'text-gray-500' : 'text-gray-900'
}`}
>
{item.title}
</span>
</div>

<div className="flex flex-1 items-center gap-3 pl-3">
<span
className={`font-B01-M ${
item.completed ? 'text-gray-500' : 'text-gray-900'
}`}
>
{item.title}
</span>
</div>
</li>
))}
<div className="flex items-center gap-3">
<div className="flex items-center gap-1 text-purple-500">
<BookMarkIcon className="h-[18px] w-[18px]" />
<span className="text-sm font-B03-SB">
{item.saveCount || 0}
</span>
</div>

<button
type="button"
onClick={() => toggleAdd(item.todoId, isAdded)}
className={
isAdded
? 'p-2 text-purple-500 font-B03-SB'
: 'flex items-center justify-center rounded-[10px] bg-purple-500 p-2 text-purple-50 font-B03-SB'
}
>
{isAdded ? '์ถ”๊ฐ€ ์ทจ์†Œํ•˜๊ธฐ' : '๋‚ด ํ• ์ผ์— ์ถ”๊ฐ€'}
</button>
</div>
</li>
);
})}
</ul>

{showToast && (
<div className="fixed inset-0 z-50 flex items-center justify-center">
<ToastModal
icon={<Info className="h-[26px] w-[26px] text-white" />}
text={toastMessage}
width="w-[274px]"
/>
</div>
)}
</div>
);
};
Expand Down
2 changes: 1 addition & 1 deletion src/validation/home/popularSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { z } from 'zod';
export const PopularTodoSchema = z.object({
todoId: z.number(),
title: z.string(),
profileImage: z.string().url(),
profileImage: z.string().url().nullable(),
memberNickname: z.string(),
memberLevel: z.string().nullable(),
jobName: z.string(),
Expand Down