Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@hello-pangea/dnd": "^18.0.1",
"@hookform/resolvers": "^4.1.3",
"@tanstack/react-query": "^5.61.3",
"@tanstack/react-query-devtools": "^5.75.7",
"autoprefixer": "^10.4.20",
"axios": "^1.8.1",
"date-fns": "^4.1.0",
Expand Down
2 changes: 2 additions & 0 deletions src/app/routes/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import TicketOptionResponsePage from '../../pages/dashboard/ui/ticket/TicketOpti
import AuthCallback from '../../pages/join/AuthCallback';
import LogoutPage from '../../pages/join/LogoutPage';
import BookmarkPage from '../../pages/bookmark/ui/BookmarkPage';
import CategoryPage from '../../pages/event/ui/CategoryPage';

const mainRoutes = [
{ path: MAIN_ROUTES.main, element: <MainPage />, requiresAuth: false },
Expand All @@ -46,6 +47,7 @@ const mainRoutes = [
{ path: MAIN_ROUTES.menu, element: <MenuPage />, requiresAuth: false },
{ path: MAIN_ROUTES.payment, element: <PaymentPage />, requiresAuth: false },
{ path: MAIN_ROUTES.bookmark, element: <BookmarkPage />, requiresAuth: false },
{ path: MAIN_ROUTES.category, element: <CategoryPage />, requiresAuth: false },
];

const joinRoutes = [
Expand Down
3 changes: 2 additions & 1 deletion src/app/routes/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ export const MAIN_ROUTES = {
menu: '/menu',
dashboard: '/dashboard/:id',
payment: '/payment',
bookmark: '/bookmark'
bookmark: '/bookmark',
category: '/category',
};

export const AUTH_ROUTES = {
Expand Down
33 changes: 29 additions & 4 deletions src/entities/event/api/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,36 @@ export const getAllEventsInfinite = async ({
size,
tag,
}: PaginationParams & { tag?: TagType }): Promise<{ items: EventItem[]; hasNextPage: boolean }> => {
const response = await axiosClient.get<ApiResponse<EventItem[]>>(
`/events${tag ? `?tags=${tag}` : ''}${tag ? '&' : '?'}page=${page}&size=${size}`
);
const params = new URLSearchParams();

if (tag) params.append('tags', tag);
params.append('page', page.toString());
params.append('size', size.toString());

const response = await axiosClient.get<ApiResponse<EventItem[]>>(`/events?${params.toString()}`);

const items = response.data.result ?? [];

return {
items,
hasNextPage: items.length === size,
};
};

// 카테고리별 이벤트 목록 조회 (무한 스크롤)
export const getCategoryEventsInfinite = async ({
page,
size,
category,
}: PaginationParams & { category: CategoryType }): Promise<{ items: EventItem[]; hasNextPage: boolean }> => {
const params = new URLSearchParams();

params.append('category', category);
params.append('page', page.toString());
params.append('size', size.toString());

const response = await axiosClient.get<ApiResponse<EventItem[]>>(`/events/categories?${params.toString()}`);

// ApiResponse의 result는 옵셔널 -> result?: T
const items = response.data.result ?? [];

return {
Expand Down
85 changes: 58 additions & 27 deletions src/features/event-manage/event-list/ui/EventList.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,37 @@
import { useRef, useEffect } from 'react';
import { useInfiniteScroll } from '../../../../shared/hooks/useInfiniteScroll';
import { getAllEventsInfinite, getCategoryEventsInfinite } from '../../../../entities/event/api/event';
import EventCard from '../../../../shared/ui/EventCard';
import { BaseEvent, CategoryType, TagType } from '../../../../shared/types/baseEventType';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import useEventList from '../../../../entities/event/hook/useEventListHook';
import type { EventList } from '../model/eventList';

const EventList = () => {
const { data, hasNextPage, isFetching, fetchNextPage } = useEventList();
interface EventListProps extends BaseEvent {
id: number;
hostChannelName: string;
remainDays: string;
}

interface EventListComponentProps {
category?: CategoryType;
tag?: TagType;
}

const EventList = ({ category, tag }: EventListComponentProps) => {
const { data, fetchNextPage, hasNextPage, isFetching } = useInfiniteScroll<EventListProps>({
queryKey: ['events', 'infinite', category ?? '', tag ?? ''],
queryFn: params => {
if (category) {
return getCategoryEventsInfinite({ ...params, category });
}
return getAllEventsInfinite({ ...params, tag });
},
size: 10,
filters: { tag, category },
});

const observerRef = useRef<IntersectionObserver>();
const lastEventCardRef = useRef<HTMLDivElement | null>(null);

console.log('EventList data.pages:', data?.pages);

useEffect(() => {
if (!hasNextPage || isFetching) return;
if (observerRef.current) observerRef.current.disconnect();
Expand All @@ -32,27 +53,37 @@ const EventList = () => {

return (
<>
<div className="grid grid-cols-2 gap-4 mx-6 mt-2 md:grid-cols-2 lg:grid-cols-2">
{data?.pages.map((page, pageIndex) =>
page.items.map((event: EventList, eventIndex) => {
const isLastElement = pageIndex === data.pages.length - 1 && eventIndex === page.items.length - 1;
return (
<div key={event.id} ref={isLastElement ? lastEventCardRef : null}>
<EventCard
id={event.id}
img={event.bannerImageUrl}
eventTitle={event.title}
eventDate={event.startDate}
location={event.address}
host={event.hostChannelName}
hashtags={event.hashtags}
dDay={event.remainDays}
/>
</div>
);
})
)}
</div>
{data?.pages[0]?.items.length === 0 ? (
<div className="sm:text-12 md:text-14 lg:text-16 py-8 text-placeholderText ">
{tag ? (
<div>생성된 이벤트가 없습니다.</div>
) : category ? (
<div>생성된 {category} 이벤트가 없습니다.</div>
) : null}
</div>
) : (
<div className="grid grid-cols-2 gap-4 mx-6 mt-2 md:grid-cols-2 lg:grid-cols-2">
{data?.pages.map((page, pageIndex) =>
page.items.map((event: EventListProps, eventIndex) => {
const isLastElement = pageIndex === data.pages.length - 1 && eventIndex === page.items.length - 1;
return (
<div key={event.id} ref={isLastElement ? lastEventCardRef : null}>
<EventCard
id={event.id}
img={event.bannerImageUrl}
eventTitle={event.title}
eventDate={event.startDate}
location={event.address}
host={event.hostChannelName}
hashtags={event.hashtags}
dDay={event.remainDays}
/>
</div>
);
})
)}
</div>
)}
{isFetching && <div className="text-center py-4">Loading...</div>}
<ReactQueryDevtools initialIsOpen={false} position="left" />
</>
Expand Down
16 changes: 14 additions & 2 deletions src/pages/event/ui/AllEventsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ import searchIcon from '../../../../design-system/icons/Search.svg';
import BottomBar from '../../../widgets/main/ui/BottomBar';
import EventList from '../../../features/event-manage/event-list/ui/EventList';
import { useNavigate } from 'react-router-dom';
import useAuthStore from '../../../app/provider/authStore';
import { AnimatePresence } from 'framer-motion';
import LoginModal from '../../../widgets/main/ui/LoginModal';

const AllEventsPage = () => {
const navigater = useNavigate();
const { isModalOpen, openModal, closeModal, isLoggedIn, name } = useAuthStore();

return (
<div className="flex flex-col items-center mb-28">
Expand All @@ -23,10 +27,18 @@ const AllEventsPage = () => {
leftButtonClassName="sm:text-lg md:text-xl lg:text-2xl font-extrabold font-nexon"
leftButtonClick={() => navigater('/')}
leftButtonLabel="같이가요"
rightContent={<SecondaryButton size="large" color="black" label="로그인" onClick={() => {}} />}
rightContent={
<SecondaryButton
size="large"
color="black"
label={isLoggedIn ? `${name}님` : '로그인'}
onClick={isLoggedIn ? closeModal : openModal}
/>
}
/>
<AnimatePresence>{isModalOpen && <LoginModal onClose={closeModal} />}</AnimatePresence>
{/* 이벤트 카드 목록 */}
<EventList />
<EventList tag="current" />
<BottomBar />
</div>
);
Expand Down
49 changes: 49 additions & 0 deletions src/pages/event/ui/CategoryPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import SecondaryButton from '../../../../design-system/ui/buttons/SecondaryButton';
import Header from '../../../../design-system/ui/Header';
import SearchTextField from '../../../../design-system/ui/textFields/SearchTextField';
import searchIcon from '../../../../design-system/icons/Search.svg';
import BottomBar from '../../../widgets/main/ui/BottomBar';
import EventList from '../../../features/event-manage/event-list/ui/EventList';
import { useNavigate, useLocation } from 'react-router-dom';
import useAuthStore from '../../../app/provider/authStore';
import { AnimatePresence } from 'framer-motion';
import LoginModal from '../../../widgets/main/ui/LoginModal';

const CategoryPage = () => {
const navigater = useNavigate();
const location = useLocation();
const category = location.state?.category;
const { isModalOpen, openModal, closeModal, isLoggedIn, name } = useAuthStore();

return (
<div className="flex flex-col items-center mb-28">
<Header
centerContent={
<SearchTextField
iconPath={<img src={searchIcon} alt="searchIcon" />}
onClick={() => navigater('/search')}
onChange={() => {}}
placeholder="입력해주세요"
/>
}
leftButtonClassName="sm:text-lg md:text-xl lg:text-2xl font-extrabold font-nexon"
leftButtonClick={() => navigater('/')}
leftButtonLabel="같이가요"
rightContent={
<SecondaryButton
size="large"
color="black"
label={isLoggedIn ? `${name}님` : '로그인'}
onClick={isLoggedIn ? closeModal : openModal}
/>
}
/>
<AnimatePresence>{isModalOpen && <LoginModal onClose={closeModal} />}</AnimatePresence>
{/* 이벤트 카드 목록 */}
<EventList category={category} />
<BottomBar />
</div>
);
};

export default CategoryPage;
2 changes: 1 addition & 1 deletion src/pages/home/ui/MainPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const MainPage = () => {
iconPath={<img src={button.iconPath} alt="메인 아이콘" />}
label={button.label}
size="lg"
onClick={button.onClick}
onClick={() => navigate('/category', {state: {category: button.category} })}
className="font-semibold"
/>
))}
Expand Down
4 changes: 4 additions & 0 deletions src/shared/types/mainCardButtonType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,25 @@ export const cardButtons = [
{
iconPath: '/assets/main/DevStudy.svg',
label: '개발/스터디',
category: 'DEVELOPMENT_STUDY',
onClick: () => console.log('DevStudy clicked'),
},
{
iconPath: '/assets/main/Networking.svg',
label: '네트워킹',
category: 'NETWORKING',
onClick: () => console.log('Networking clicked'),
},
{
iconPath: '/assets/main/Hackathon.svg',
label: '해커톤',
category: 'HACKATHON',
onClick: () => console.log('Hackathon clicked'),
},
{
iconPath: '/assets/main/Conference.svg',
label: '컨퍼런스',
category: 'CONFERENCE',
onClick: () => console.log('Conference clicked'),
},
];
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -978,6 +978,18 @@
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.69.0.tgz#c434505987ade936dc53e6e27aa1406b0295516f"
integrity sha512-Kn410jq6vs1P8Nm+ZsRj9H+U3C0kjuEkYLxbiCyn3MDEiYor1j2DGVULqAz62SLZtUZ/e9Xt6xMXiJ3NJ65WyQ==

"@tanstack/[email protected]":
version "5.74.7"
resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.74.7.tgz#c9b022b386ac86e6395228b5d6912e6444b3b971"
integrity sha512-nSNlfuGdnHf4yB0S+BoNYOE1o3oAH093weAYZolIHfS2stulyA/gWfSk/9H4ZFk5mAAHb5vNqAeJOmbdcGPEQw==

"@tanstack/react-query-devtools@^5.75.7":
version "5.75.7"
resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.75.7.tgz#3d50ff93d2aff0de236f53aaca7bbed1ae82908d"
integrity sha512-VUzHvxcUAz7oSeX/TlVyDgNxajLAF+b12Z3OfSxCrAdWynELfWohwzCn1iT2NEjnGTb3X3ryzQxeWuWMyMwCmQ==
dependencies:
"@tanstack/query-devtools" "5.74.7"

"@tanstack/react-query@^5.61.3":
version "5.69.0"
resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.69.0.tgz#8d58e800854cc11d0aa2c39569f53ae32ba442a9"
Expand Down