-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: [FE] add event list page (#40)
- Loading branch information
Showing
13 changed files
with
416 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const SERVER_URL = process.env.NEXT_PUBLIC_API_SERVER_URL |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { IGetEventsByCategoryResponse } from '@/types/event' | ||
import { SERVER_URL } from '../../config' | ||
|
||
export async function getEventsByCategory(category: string, page: number, limit: number): Promise<IGetEventsByCategoryResponse> { | ||
const res = await fetch(`${SERVER_URL}/v1/events/category/${category}?page=${page}&limit=${limit}`, { | ||
method: 'GET', | ||
// TODO: add authorization Header | ||
}) | ||
|
||
if (!res.ok) { | ||
throw new Error(`code: ${res.status}\ndescription: ${res.statusText}`) | ||
} | ||
return res.json() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
'use client' | ||
|
||
import { useRouter } from 'next/navigation' | ||
|
||
import { EventList } from '@/components/events/eventList' | ||
import { BackButtonIcon } from '@/components/icon' | ||
import { categoryStore } from '@/store' | ||
|
||
export default function Page() { | ||
const router = useRouter() | ||
const { category, setCategory } = categoryStore() | ||
|
||
const onClick = () => { | ||
router.back() | ||
setCategory('') | ||
} | ||
if (!category) { | ||
return ( | ||
<div className="h-screen flex items-center justify-center px-4"> | ||
<div className="text-lg text-center">잘못된 접근입니다.</div> | ||
</div> | ||
) | ||
} | ||
return ( | ||
<div className="h-min-screen items-center justify-items-center min-h-screen bg-inherit overflow-hidden"> | ||
{/*TODO: Remove temporary header*/} | ||
<div className="flex flex-row items-center w-full pt-10 p-2 fixed top-0 left-0 right-0 z-10 bg-inherit"> | ||
<button className="absolute z-1 ps-4" onClick={onClick}> | ||
<BackButtonIcon width={30} height={30} /> | ||
</button> | ||
<div className="text-3xl text-center font-semibold w-full">{category}</div> | ||
</div> | ||
<div className="w-full h-full bg-inherit mt-24 overflow-y-auto"> | ||
<EventList category={category} /> | ||
</div> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
'use client' | ||
|
||
import { CategoryItem } from '@/components/events/categoryItem' | ||
|
||
export default function Page() { | ||
// TODO: add get category names | ||
return ( | ||
<div className="grid justify-items-center px-8 mt-6"> | ||
<div className="grid border-b gap-4 w-full py-2 border-gray-300"> | ||
<CategoryItem category={'내가 등록한 행사'} isFavorites={false} /> | ||
<CategoryItem category={'인기 행사'} isFavorites={false} /> | ||
</div> | ||
{/*User Favorites Categories*/} | ||
<div className="grid border-b gap-4 w-full py-2 border-gray-300"> | ||
<CategoryItem category={'축제'} isFavorites={true} /> | ||
<CategoryItem category={'축제'} isFavorites={true} /> | ||
<CategoryItem category={'축제'} isFavorites={true} /> | ||
</div> | ||
{/*Other Categories*/} | ||
<div className="grid border-b gap-4 w-full py-2 border-gray-300"> | ||
<CategoryItem category={'축제'} isFavorites={false} /> | ||
<CategoryItem category={'축제'} isFavorites={false} /> | ||
<CategoryItem category={'축제'} isFavorites={false} /> | ||
</div> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import Link from 'next/link' | ||
|
||
import { BaseCategoryIcon, HottestCategoryIcon, WrittenCategoryIcon } from '@/components/icon' | ||
import { categoryStore } from '@/store' | ||
|
||
interface Props { | ||
category: string | ||
isFavorites: boolean | ||
} | ||
|
||
export function CategoryItem({ category, isFavorites }: Props) { | ||
const { setCategory } = categoryStore() | ||
const onClick = () => { | ||
setCategory(category) | ||
} | ||
// TODO: change category names | ||
if (category === '내가 등록한 행사') { | ||
return ( | ||
<Link className="flex flex-row w-full items-center justify-start p-4 gap-4" href={`/events/category`} onClick={onClick}> | ||
<WrittenCategoryIcon width={40} height={40} /> | ||
<div className="text-3xl text-center">내가 등록한 행사</div> | ||
</Link> | ||
) | ||
} else if (category === '인기 행사') { | ||
return ( | ||
<Link className="flex flex-row w-full items-center justify-start p-4 gap-4" href={`/events/category`} onClick={onClick}> | ||
<HottestCategoryIcon width={40} height={40} /> | ||
<div className="text-3xl text-center">인기 행사</div> | ||
</Link> | ||
) | ||
} else { | ||
// TODO: add logic making to Favorites | ||
return ( | ||
<Link className="flex flex-row w-full items-center justify-start p-4 gap-4" href={`/events/category`} onClick={onClick}> | ||
<BaseCategoryIcon width={40} height={40} isFavorites={isFavorites} /> | ||
<div className="text-3xl text-center pt-1">{category}</div> | ||
</Link> | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { IEvent } from '@/types/event' | ||
|
||
interface Props { | ||
event: IEvent | undefined | ||
} | ||
|
||
const formatDate = (date: Date) => { | ||
const formattedDate = new Date(date).toLocaleDateString('ko-KR', { | ||
year: 'numeric', | ||
month: '2-digit', | ||
day: '2-digit', | ||
}) | ||
return formattedDate.replace(/\s/g, '').replace(/\.$/, '') | ||
} | ||
|
||
export default function Event({ event }: Props) { | ||
if (!event) { | ||
return ( | ||
<div className="flex justify-between gap-4 px-4 my-6 animate-pulse"> | ||
<div className="flex flex-col w-2/3 ps-2 gap-2"> | ||
<div className="w-4/5 h-6 bg-[#D9D9D9] my-2" /> | ||
<div className="w-3/4 h-3 bg-[#D9D9D9] my-2" /> | ||
<div className="w-2/3 h-3 bg-[#D9D9D9] my-2" /> | ||
</div> | ||
<div className="flex justify-center items-center w-28 h-28 bg-[#D9D9D9] overflow-hidden me-2" /> | ||
</div> | ||
) | ||
} | ||
|
||
return ( | ||
<div className="flex justify-between items-start gap-4 px-4 mb-12"> | ||
<div className="flex flex-col w-2/3 ps-2 pt-2 gap-2"> | ||
<div className="text-xl font-semibold">{event.name}</div> | ||
<div className="text-lg text-gray-600">{`${formatDate(event.startDate)}~${formatDate(event.endDate)}`}</div> | ||
<div className="text-lg text-gray-600">{event.address}</div> | ||
</div> | ||
<div className="flex justify-center items-center w-28 h-28 bg-[#D9D9D9] overflow-hidden me-2"> | ||
{/*TODO: change image tag*/} | ||
<img src={event.photo[0]} alt="대표 사진" className="w-full h-full object-cover" /> | ||
</div> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
'use client' | ||
|
||
import { useEffect } from 'react' | ||
import { useInfiniteQuery } from '@tanstack/react-query' | ||
import { useInView } from 'react-intersection-observer' | ||
|
||
import { getEventsByCategory } from '@/api/event' | ||
import { IGetEventsByCategoryResponse } from '@/types/event' | ||
import Event from '@/components/events/event' | ||
|
||
interface useEventsByCategoryQueryProps { | ||
category: string | ||
startPage: number | ||
} | ||
|
||
export function EventList({ category }: { category: string }) { | ||
const { data, isLoading, isError, fetchNextPage, isFetchingNextPage } = useEventsByCategoryQuery({ | ||
category: category, | ||
startPage: 1, | ||
}) | ||
const { ref, inView } = useInView() | ||
useEffect(() => { | ||
if (inView) { | ||
console.log('무한 스크롤 요청중') | ||
fetchNextPage() | ||
} | ||
}, [inView]) | ||
|
||
if (isLoading) { | ||
return ( | ||
<div className="h-full w-full"> | ||
<div className="h-full w-full px-2 mt-4"> | ||
{Array.from({ length: 6 }, (_, index) => ( | ||
<Event event={undefined} key={index} /> | ||
))} | ||
</div> | ||
{isFetchingNextPage ? <Event event={undefined} /> : <div ref={ref} />} | ||
</div> | ||
) | ||
} | ||
|
||
if (isError) { | ||
return ( | ||
<div className="h-screen flex items-center justify-center px-4"> | ||
<div className="text-lg text-center"> | ||
행사를 불러오는 데 실패하였습니다. | ||
<br /> | ||
다시 시도해 주시길 바랍니다. | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
return ( | ||
<div className="h-full w-full"> | ||
<div className="h-full w-full px-2 mt-4"> | ||
{/*TODO: add navigation to event detail page*/} | ||
{data && data.pages.map(events => events.events.map(event => <Event event={event} key={event._id} />))} | ||
</div> | ||
{isFetchingNextPage ? <Event event={undefined} /> : <div ref={ref} />} | ||
</div> | ||
) | ||
} | ||
|
||
const useEventsByCategoryQuery = ({ category, startPage }: useEventsByCategoryQueryProps) => { | ||
const { data, isLoading, isError, fetchNextPage, isFetchingNextPage } = useInfiniteQuery<IGetEventsByCategoryResponse>({ | ||
queryKey: ['events', category], | ||
queryFn: ({ pageParam }) => getEventsByCategory(category, pageParam as number, 6), | ||
initialPageParam: startPage, | ||
getNextPageParam: lastPage => { | ||
if (lastPage.page.hasNextPage) { | ||
return lastPage.page.page + 1 | ||
} | ||
}, | ||
getPreviousPageParam: lastPage => { | ||
if (lastPage.page.hasPrevPage) { | ||
return lastPage.page.page - 1 | ||
} | ||
}, | ||
}) | ||
|
||
return { data, isLoading, isError, fetchNextPage, isFetchingNextPage } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
interface IconProps { | ||
width: number | ||
height: number | ||
} | ||
|
||
interface BaseCategoryProps extends IconProps { | ||
isFavorites: boolean | ||
} | ||
|
||
export function BackButtonIcon({ width, height }: IconProps) { | ||
return ( | ||
<svg width={width} height={height} viewBox="0 0 14 26" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||
<path d="M13 25L1 13L13 1" stroke="black" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> | ||
</svg> | ||
) | ||
} | ||
|
||
export function WrittenCategoryIcon({ width, height }: IconProps) { | ||
return ( | ||
<svg width={width} height={height} viewBox="0 0 24 30" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||
<path | ||
d="M16 1.66675H8.00004C7.26366 1.66675 6.66671 2.2637 6.66671 3.00008V5.66675C6.66671 6.40313 7.26366 7.00008 8.00004 7.00008H16C16.7364 7.00008 17.3334 6.40313 17.3334 5.66675V3.00008C17.3334 2.2637 16.7364 1.66675 16 1.66675Z" | ||
fill="#7DDEFF" | ||
/> | ||
<path | ||
d="M17.3334 4.33341H20C20.7073 4.33341 21.3856 4.61437 21.8857 5.11446C22.3858 5.61456 22.6667 6.29284 22.6667 7.00008V25.6667C22.6667 26.374 22.3858 27.0523 21.8857 27.5524C21.3856 28.0525 20.7073 28.3334 20 28.3334H4.00004C3.2928 28.3334 2.61452 28.0525 2.11442 27.5524C1.61433 27.0523 1.33337 26.374 1.33337 25.6667V7.00008C1.33337 6.29284 1.61433 5.61456 2.11442 5.11446C2.61452 4.61437 3.2928 4.33341 4.00004 4.33341H6.66671" | ||
fill="#7DDEFF" | ||
/> | ||
<path d="M12 13.6667H17.3334H12Z" fill="#7DDEFF" /> | ||
<path d="M12 20.3334H17.3334H12Z" fill="#7DDEFF" /> | ||
<path d="M6.66671 13.6667H6.68004H6.66671Z" fill="#7DDEFF" /> | ||
<path d="M6.66671 20.3334H6.68004H6.66671Z" fill="#7DDEFF" /> | ||
<path | ||
d="M17.3334 4.33341H20C20.7073 4.33341 21.3856 4.61437 21.8857 5.11446C22.3858 5.61456 22.6667 6.29284 22.6667 7.00008V25.6667C22.6667 26.374 22.3858 27.0523 21.8857 27.5524C21.3856 28.0525 20.7073 28.3334 20 28.3334H4.00004C3.2928 28.3334 2.61452 28.0525 2.11442 27.5524C1.61433 27.0523 1.33337 26.374 1.33337 25.6667V7.00008C1.33337 6.29284 1.61433 5.61456 2.11442 5.11446C2.61452 4.61437 3.2928 4.33341 4.00004 4.33341H6.66671M12 13.6667H17.3334M12 20.3334H17.3334M6.66671 13.6667H6.68004M6.66671 20.3334H6.68004M8.00004 1.66675H16C16.7364 1.66675 17.3334 2.2637 17.3334 3.00008V5.66675C17.3334 6.40313 16.7364 7.00008 16 7.00008H8.00004C7.26366 7.00008 6.66671 6.40313 6.66671 5.66675V3.00008C6.66671 2.2637 7.26366 1.66675 8.00004 1.66675Z" | ||
stroke="black" | ||
strokeWidth="2" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
/> | ||
</svg> | ||
) | ||
} | ||
|
||
export function HottestCategoryIcon({ width, height }: IconProps) { | ||
return ( | ||
<svg width={width} height={height} viewBox="0 0 22 28" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||
<path | ||
d="M6.33329 16.3333C7.21735 16.3333 8.06519 15.9821 8.69031 15.357C9.31544 14.7319 9.66663 13.8841 9.66663 13C9.66663 11.16 8.99996 10.3333 8.33329 9C6.90396 6.14267 8.03463 3.59467 11 1C11.6666 4.33333 13.6666 7.53333 16.3333 9.66667C19 11.8 20.3333 14.3333 20.3333 17C20.3333 18.2257 20.0919 19.4393 19.6228 20.5717C19.1538 21.7041 18.4663 22.733 17.5996 23.5997C16.7329 24.4663 15.704 25.1538 14.5717 25.6229C13.4393 26.0919 12.2256 26.3333 11 26.3333C9.77429 26.3333 8.56062 26.0919 7.42825 25.6229C6.29587 25.1538 5.26698 24.4663 4.4003 23.5997C3.53362 22.733 2.84613 21.7041 2.37708 20.5717C1.90804 19.4393 1.66663 18.2257 1.66663 17C1.66663 15.4627 2.24396 13.9413 2.99996 13C2.99996 13.8841 3.35115 14.7319 3.97627 15.357C4.60139 15.9821 5.44924 16.3333 6.33329 16.3333Z" | ||
fill="#FF9575" | ||
stroke="#1B1B1B" | ||
strokeWidth="2" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
/> | ||
</svg> | ||
) | ||
} | ||
|
||
export function BaseCategoryIcon({ width, height, isFavorites }: BaseCategoryProps) { | ||
return ( | ||
<svg width={width} height={height} viewBox="0 0 30 29" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||
<path | ||
d="M15 1.6665L19.12 10.0132L28.3333 11.3598L21.6666 17.8532L23.24 27.0265L15 22.6932L6.75996 27.0265L8.33329 17.8532L1.66663 11.3598L10.88 10.0132L15 1.6665Z" | ||
fill={`${isFavorites ? '#FF9575' : '#FFFFFF'}`} | ||
stroke="#1B1B1B" | ||
strokeWidth="2" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
/> | ||
</svg> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { create } from 'zustand' | ||
|
||
interface CategoryStore { | ||
category: string | ||
setCategory: (name: string) => void | ||
} | ||
|
||
export const categoryStore = create<CategoryStore>(set => ({ | ||
category: '', | ||
setCategory: category => { | ||
set({ category }) | ||
}, | ||
})) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './category' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
export interface IEvent { | ||
_id: string | ||
name: string | ||
address: string | ||
location: { | ||
type: string | ||
coordinates: number[] | ||
} | ||
startDate: Date | ||
endDate: Date | ||
description: string | ||
photo: string[] | ||
cost: number | ||
likeCount: number | ||
commentCount: number | ||
category: string[] | ||
targetAudience: string[] | ||
createdAt: Date | ||
} | ||
|
||
export interface IGetEventsByCategoryResponse { | ||
page: { | ||
totalDocs: number | ||
totalPages: number | ||
hasNextPage: boolean | ||
hasPrevPage: boolean | ||
page: number | ||
limit: number | ||
} | ||
events: IEvent[] | ||
} |
Oops, something went wrong.