Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
44d254e
๐Ÿ› fix: ๋ฐ์ดํ„ฐ ๊ฐ’ ์ˆ˜์ •
HaeJungg Nov 14, 2024
77585af
โ™ป๏ธ refactor: ์ฝ”๋“œ ๋ฆฌํŒฉํ† ๋ง
HaeJungg Nov 14, 2024
0344be1
๐Ÿ› fix: liked ์ „๋‹ฌ
HaeJungg Nov 14, 2024
340752f
๐Ÿ“ docs: ์ฃผ์„์‚ญ์ œ
HaeJungg Nov 14, 2024
7a31287
โœจ feat: ์ฐœํ•˜๊ธฐ api ์ถ”๊ฐ€
HaeJungg Nov 14, 2024
c1fc0d7
โœจ feat: ์ฐœํ•œ ๋ฆฌ์ŠคํŠธ ์ปดํฌ๋„ŒํŠธ ์ƒˆ๋กœ ์ž‘์„ฑ
HaeJungg Nov 14, 2024
1f75511
โœจ feat: ์ฐœํ•˜๊ธฐ ๊ธฐ๋Šฅ ์ถ”๊ฐ€, ๋กœ๊ทธ์ธ์—ฌ๋ถ€ ์ถ”๊ฐ€
HaeJungg Nov 14, 2024
d3649f7
๐Ÿ› fix: ์ปดํฌ๋„ŒํŠธ ๋ณ€๊ฒฝ์— ๋”ฐ๋ผ import ์ˆ˜์ •
HaeJungg Nov 14, 2024
e075627
โœจ feat: ์ฐœํ•˜๊ธฐ ๊ธฐ๋Šฅ ์ถ”๊ฐ€
HaeJungg Nov 14, 2024
79d9191
๐Ÿ’„ design: ํ™•์ธ ๋ชจ๋‹ฌ ๋””์ž์ธ ์ˆ˜์ •
HaeJungg Nov 14, 2024
ef72b00
๐Ÿ› fix: ํƒ€์ž… ๋ณ€๊ฒฝ์— ๋”ฐ๋ผ ๋”๋ฏธ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ
HaeJungg Nov 14, 2024
84a8539
๐Ÿ› fix: ํƒ€์ž… ๋ณ€๊ฒฝ์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ, ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ํŒŒ์ผ ์‚ญ์ œ
HaeJungg Nov 14, 2024
b243456
Merge branch 'develop' into Feat/121/LikedApi
HaeJungg Nov 15, 2024
9e1b752
โ™ป๏ธ refactor: ์ค‘๋ณต ์ฝ”๋“œ ๋ฆฌํŒฉํ† ๋ง
HaeJungg Nov 15, 2024
4317a4c
๐Ÿ› fix: ์ž˜๋ชป๋œ ํ† ์ŠคํŠธ ์‚ฌ์šฉ๋ฒ• ๊ฐœ์„ 
HaeJungg Nov 15, 2024
5bf99c2
๐Ÿ› fix: ๋ฆฌ๋””๋ ‰์…˜ ๊ฐœ์„ 
HaeJungg Nov 15, 2024
e82ff4c
๐Ÿ“ฆ chore: ํ† ์ŠคํŠธ ์Šคํƒ€์ผ๋ง import
HaeJungg Nov 15, 2024
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
39 changes: 39 additions & 0 deletions src/_apis/liked/liked-apis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { fetchApi } from '@/src/utils/api';
import { GatheringResponseType } from '@/src/types/gathering-data';

// ์ฐœ ๋ชฉ๋ก ์กฐํšŒ
export async function getLikedList(page: number): Promise<GatheringResponseType> {
const url = `/api/liked/memberLikes?page=${page}&size=6`;

const response = await fetchApi<{ data: GatheringResponseType }>(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
return response.data;
}

// ์ฐœ ์ถ”๊ฐ€ํ•˜๊ธฐ
export async function addLike(gatheringId: number): Promise<void> {
const url = `/api/liked/${gatheringId}`;

await fetchApi<void>(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
});
}

// ์ฐœ ํ•ด์ œํ•˜๊ธฐ
export async function removeLike(gatheringId: number): Promise<void> {
const url = `/api/liked/${gatheringId}`;

await fetchApi<void>(url, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
});
}
9 changes: 9 additions & 0 deletions src/_queries/liked/liked-queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useQuery } from '@tanstack/react-query';
import { getLikedList } from '@/src/_apis/liked/liked-apis';

export function useGetLikedListQuery(page: number) {
return useQuery({
queryKey: ['likedList', page],
queryFn: () => getLikedList(page),
});
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
'use client';

import { toast } from 'react-toastify';
import {
CancelGathering,
JoinGathering,
LeaveGathering,
} from '@/src/_apis/gathering/gathering-detail-apis';
import { ApiError } from '@/src/utils/api';
import Toast from '@/src/components/common/toast';
import { GatheringDetailType } from '@/src/types/gathering-data';
import GatheringDetailModalPresenter from './presenter';

Expand All @@ -24,7 +24,7 @@ export default function GatheringDetailModalContainer({
data,
}: GatheringDetailModalContainerProps) {
const showToast = (message: string, type: 'success' | 'error' | 'warning') => {
Toast({ message, type });
toast(message, { type });
};

const handleJoin = async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
'use client';

import { useState } from 'react';
import { toast } from 'react-toastify';
import { useRouter } from 'next/navigation';
import { addLike, removeLike } from '@/src/_apis/liked/liked-apis';
import { useGetGatheringListQuery } from '@/src/_queries/crew/gathering-list-queries';
import { ApiError } from '@/src/utils/api';
import ConfirmModal from '@/src/components/common/modal/confirm-modal';
import GatheringCardCarousel from '@/src/components/gathering-list/gathering-card-carousel';

interface GatheringListSectionProps {
Expand All @@ -9,12 +15,57 @@ interface GatheringListSectionProps {

export default function GatheringListSection({ id }: GatheringListSectionProps) {
const { data: gatheringList, isLoading, error } = useGetGatheringListQuery(id);
const [showLoginModal, setShowLoginModal] = useState(false);
const router = useRouter();

if (isLoading) return <p>๋กœ๋”ฉ ์ค‘...</p>;
const handleLike = async (gatheringId: number) => {
try {
await addLike(gatheringId);
} catch (apiError) {
if (apiError instanceof ApiError) {
toast.error(`์ฐœํ•˜๊ธฐ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค: ${apiError.message}`);
}
}
};
Comment on lines +21 to +29
Copy link

Choose a reason for hiding this comment

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

๐Ÿ› ๏ธ Refactor suggestion

์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋กœ์ง ๊ฐœ์„  ํ•„์š”

์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋กœ์ง์ด handleLike์™€ handleUnlike์—์„œ ์ค‘๋ณต๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์ผ๋ฐ˜์ ์ธ ์—๋Ÿฌ ์ƒํ™ฉ์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๊ฐ€ ๋ˆ„๋ฝ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ฐœ์„ ํ•˜๋Š” ๊ฒƒ์„ ์ œ์•ˆ๋“œ๋ฆฝ๋‹ˆ๋‹ค:

+ const handleApiError = (action: string, error: unknown) => {
+   if (error instanceof ApiError) {
+     toast.error(`${action}์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค: ${error.message}`);
+   } else {
+     toast.error(`${action} ์ค‘ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค`);
+     console.error(error);
+   }
+ };

  const handleLike = async (gatheringId: number) => {
    try {
      await addLike(gatheringId);
    } catch (error) {
-     if (apiError instanceof ApiError) {
-       toast.error(`์ฐœํ•˜๊ธฐ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค: ${apiError.message}`);
-     }
+     handleApiError('์ฐœํ•˜๊ธฐ', error);
    }
  };

  const handleUnlike = async (gatheringId: number) => {
    try {
      await removeLike(gatheringId);
    } catch (error) {
-     if (apiError instanceof ApiError) {
-       toast.error(`์ฐœํ•˜๊ธฐ ํ•ด์ œ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค: ${apiError.message}`);
-     }
+     handleApiError('์ฐœํ•˜๊ธฐ ํ•ด์ œ', error);
    }
  };

Also applies to: 31-39


const handleUnlike = async (gatheringId: number) => {
try {
await removeLike(gatheringId);
} catch (apiError) {
if (apiError instanceof ApiError) {
toast.error(`์ฐœํ•˜๊ธฐ ํ•ด์ œ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค: ${apiError.message}`);
}
}
};

if (error) return <p>๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค: {error.message}</p>;
const handleLoginRedirect = () => {
const currentPath = window.location.pathname || '/';
router.push(`/login?redirect=${encodeURIComponent(currentPath)}`);
};

// TODO: ์ถ”ํ›„ ์—๋Ÿฌ, ๋กœ๋”ฉ ์ˆ˜์ •
if (isLoading) return <p>๋กœ๋”ฉ ์ค‘...</p>;
if (error) return <p>๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค</p>;
Comment on lines +46 to +48
Copy link

Choose a reason for hiding this comment

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

๐Ÿ› ๏ธ Refactor suggestion

๋กœ๋”ฉ ๋ฐ ์—๋Ÿฌ ์ƒํƒœ ์ฒ˜๋ฆฌ๋ฅผ ๊ฐœ์„ ํ•ด์ฃผ์„ธ์š”

ํ˜„์žฌ ๊ตฌํ˜„์€ ๊ธฐ๋ณธ์ ์ธ ํ…์ŠคํŠธ๋งŒ ํ‘œ์‹œํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํ–ฅ์ƒ์‹œํ‚ค๊ธฐ ์œ„ํ•ด ๋” ๋‚˜์€ UI ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฐœ์„ ์‚ฌํ•ญ์„ ์ œ์•ˆํ•ฉ๋‹ˆ๋‹ค:

  1. ๋กœ๋”ฉ ์ƒํƒœ: Skeleton UI ๋˜๋Š” ๋กœ๋”ฉ ์Šคํ”ผ๋„ˆ ์‚ฌ์šฉ
  2. ์—๋Ÿฌ ์ƒํƒœ: ์—๋Ÿฌ ๋ฉ”์‹œ์ง€์™€ ํ•จ๊ป˜ ์žฌ์‹œ๋„ ๋ฒ„ํŠผ ์ถ”๊ฐ€
  3. ๋ฐ์ดํ„ฐ ์—†์Œ ์ƒํƒœ: ์ ์ ˆํ•œ ์•ˆ๋‚ด ๋ฉ”์‹œ์ง€์™€ ํ•จ๊ป˜ ์‹œ๊ฐ์  ํ”ผ๋“œ๋ฐฑ ์ œ๊ณต
if (isLoading) return <LoadingSpinner />;
if (error) return (
  <ErrorState 
    message="๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค" 
    onRetry={() => refetch()} 
  />
);
if (!gatheringList || gatheringList.length === 0) return (
  <EmptyState message="์•„์ง ๋“ฑ๋ก๋œ ๋ชจ์ž„์ด ์—†์Šต๋‹ˆ๋‹ค" />
);

if (!gatheringList || gatheringList.length === 0) return <p>๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.</p>;

return <GatheringCardCarousel gatheringData={gatheringList} crewId={id} />;
return (
<>
<GatheringCardCarousel
gatheringData={gatheringList}
crewId={id}
onLike={handleLike}
onUnlike={handleUnlike}
onShowLoginModal={() => setShowLoginModal(true)}
/>
{showLoginModal && (
<ConfirmModal
opened={showLoginModal}
onClose={() => setShowLoginModal(false)}
onConfirm={handleLoginRedirect}
>
๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค!
</ConfirmModal>
)}
</>
);
}
15 changes: 15 additions & 0 deletions src/app/(crew)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Bounce, ToastContainer } from 'react-toastify';
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue

์Šคํƒ€์ผ import ์ˆœ์„œ ๊ฐœ์„ ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค

react-toastify์˜ ์Šคํƒ€์ผ ํŒŒ์ผ์ด import๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. Toast ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์Šคํƒ€์ผ๋ง๋˜๋ ค๋ฉด CSS import๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•ด์ฃผ์„ธ์š”:

import { Bounce, ToastContainer } from 'react-toastify';
+import 'react-toastify/dist/ReactToastify.css';
import '@mantine/core/styles.css';
๐Ÿ“ 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
import { Bounce, ToastContainer } from 'react-toastify';
import { Bounce, ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';

import 'react-toastify/dist/ReactToastify.css';
import '@mantine/core/styles.css';
import Header from '@/src/components/common/header/container';
import '@/src/styles/globals.css';
Expand All @@ -10,6 +12,19 @@ export default function RootLayout({
return (
<>
<Header />
<ToastContainer
position="bottom-right"
autoClose={3000}
hideProgressBar={false}
newestOnTop={false}
closeOnClick
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover
theme="light"
transition={Bounce}
/>
<div className="flex min-h-screen flex-col items-center bg-gray-50">
<main className="container flex min-h-screen max-w-pc flex-1 flex-col md:shadow-bg">
{children}
Expand Down
5 changes: 2 additions & 3 deletions src/app/(crew)/my-favorite/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import GatheringList from '@/src/components/gathering-list/gathering-list';
import { gatheringData } from '@/src/mock/gathering-data';
import LikedList from '@/src/components/gathering-list/liked-list-container';

export default function FavoritePage() {
return (
<div className="mt-4 md:mt-10">
<GatheringList gatheringData={gatheringData} />
<LikedList />
</div>
);
}
25 changes: 0 additions & 25 deletions src/app/api/test-api/route.ts

This file was deleted.

29 changes: 21 additions & 8 deletions src/components/common/gathering-card/container.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
'use client';

import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useDisclosure } from '@mantine/hooks';
import { useGetGatheringDetailQuery } from '@/src/_queries/gathering/gathering-detail-queries';
import { ApiError } from '@/src/utils/api';
import GatheringDetailModalContainer from '@/src/app/(crew)/crew/_components/gathering-detail-modal/container';
import Toast from '@/src/components/common/toast';
import { GatheringType } from '@/src/types/gathering-data';
import GatheringCardPresenter from './presenter';

interface GatheringCardContainerProps extends GatheringType {
className?: string;
crewId: number;
onLike: (gatheringId: number) => Promise<void>;
onUnlike: (gatheringId: number) => Promise<void>;
}

export default function GatheringCard({
Expand All @@ -25,9 +27,10 @@ export default function GatheringCard({
liked: initialIsLiked,
className,
crewId,
onLike,
onUnlike,
}: GatheringCardContainerProps) {
const [opened, { open, close }] = useDisclosure(false);
// ์ž„์‹œ ์ฐœํ•˜๊ธฐ
const [isLiked, setIsLiked] = useState(initialIsLiked);

// ๋‚ ์งœ ๋น„๊ต
Expand All @@ -42,9 +45,19 @@ export default function GatheringCard({
// ๋งˆ๊ฐ ์‹œ๊ฐ„ ๋ฌธ์ž์—ด ์ƒ์„ฑ
const deadlineMessage = `์˜ค๋Š˜ ${gatheringDate.getHours()}์‹œ ๋งˆ๊ฐ`;

// ์ถ”ํ›„ ์ฐœํ•˜๊ธฐ ์ปดํฌ๋„ŒํŠธ ์ž‘์„ฑ๋˜๋ฉด ์ˆ˜์ •
const handleLikeToggle = () => {
setIsLiked((prev) => !prev);
// ์ฐœํ•˜๊ธฐ ์ƒํƒœ ์—…๋ฐ์ดํŠธ
const handleLikeToggle = async () => {
try {
if (isLiked) {
await onUnlike(id);
setIsLiked(false);
} else {
await onLike(id);
setIsLiked(true);
}
} catch (error) {
toast.error('์ฐœ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.');
}
};

const { data: gatheringData, error } = useGetGatheringDetailQuery(crewId, id);
Expand All @@ -56,13 +69,13 @@ export default function GatheringCard({
const errorData = JSON.parse(error.message);

if (errorData.status === 'NOT_FOUND') {
Toast({ message: '๋ชจ์ž„ ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.', type: 'error' });
toast.error('๋ชจ์ž„ ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.');
}
} catch {
Toast({ message: `Error ${error.status}: ${error.message}`, type: 'error' });
toast.error(`Error ${error.status}: ${error.message}`);
}
} else {
Toast({ message: '๋ฐ์ดํ„ฐ ํ†ต์‹ ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.', type: 'error' });
toast.error('๋ฐ์ดํ„ฐ ํ†ต์‹ ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.');
}
}
}, [error]);
Expand Down
6 changes: 3 additions & 3 deletions src/components/common/modal/confirm-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export default function ConfirmModal({ children, opened, onClose, onConfirm }: C
opened={opened}
onClose={onClose}
centered
withCloseButton
withCloseButton={false}
size="xs"
styles={{
content: { boxShadow: '0 25px 50px -12px rgba(0,0,0,0.1)', borderRadius: '12px' },
Expand All @@ -46,8 +46,8 @@ export default function ConfirmModal({ children, opened, onClose, onConfirm }: C
blur: 2,
}}
>
<div className="space-y-8 text-center">
{children}
<div className="space-y-8 p-4 text-center">
<div>{children}</div>
<div className="flex justify-end">
<Button className="btn-filled w-full" onClick={handleConfirm}>
ํ™•์ธ
Expand Down
27 changes: 26 additions & 1 deletion src/components/gathering-list/gathering-card-carousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { useEffect, useState } from 'react';
import Image from 'next/image';
import { useAuthStore } from '@/src/store/use-auth-store';
import GatheringCard from '@/src/components/common/gathering-card/container';
import { GatheringType } from '@/src/types/gathering-data';
import IcoLeft from '@/public/assets/icons/ic-left.svg';
Expand All @@ -10,16 +11,26 @@ import IcoRight from '@/public/assets/icons/ic-right.svg';
interface GatheringCardCarouselProps {
gatheringData: GatheringType[];
crewId: number;
onLike: (gatheringId: number) => Promise<void>;
onUnlike: (gatheringId: number) => Promise<void>;
onShowLoginModal: () => void;
}

export default function CustomGatheringCardCarousel({
gatheringData,
crewId,
onLike,
onUnlike,
onShowLoginModal,
}: GatheringCardCarouselProps) {
const [currentIndex, setCurrentIndex] = useState(0);
const [slidesToShow, setSlidesToShow] = useState(1);
const [slideSize, setSlideSize] = useState('w-full');

// ๋กœ๊ทธ์ธ ์—ฌ๋ถ€ ํ™•์ธ
const token = useAuthStore((state) => state.token);
const isLoggedIn = !!token;

useEffect(() => {
const handleResize = () => {
const screenWidth = window.innerWidth;
Expand Down Expand Up @@ -59,6 +70,14 @@ export default function CustomGatheringCardCarousel({
);
};

const handleLikeAction = (actionType: 'like' | 'unlike', gatheringId: number) => {
if (isLoggedIn) {
return actionType === 'like' ? onLike(gatheringId) : onUnlike(gatheringId);
}
onShowLoginModal();
return Promise.resolve();
};

return (
<div className="relative w-full">
<div className="flex overflow-x-hidden">
Expand All @@ -75,7 +94,13 @@ export default function CustomGatheringCardCarousel({
>
{gatheringData.map((card) => (
<div key={card.id} className={`flex-shrink-0 ${slideSize} mb-5 lg:min-w-[362px]`}>
<GatheringCard crewId={crewId} {...card} className="w-full" />
<GatheringCard
crewId={crewId}
{...card}
className="w-full"
onLike={() => handleLikeAction('like', card.id)}
onUnlike={() => handleLikeAction('unlike', card.id)}
/>
</div>
))}
</div>
Expand Down
Loading