diff --git a/src/components/features/index.ts b/src/components/features/index.ts index 17b0d29..401c83d 100644 --- a/src/components/features/index.ts +++ b/src/components/features/index.ts @@ -1 +1 @@ -export { NoticeListSection, RecentNoticeList } from './noticeList'; +export { CustomNotice, NoticeEmpty, NoticeListSection, RecentNoticeList } from './noticeList'; diff --git a/src/components/features/noticeList/customNotice.tsx b/src/components/features/noticeList/customNotice.tsx new file mode 100644 index 0000000..bc5f093 --- /dev/null +++ b/src/components/features/noticeList/customNotice.tsx @@ -0,0 +1,42 @@ +import { Container, HorizontalScroll } from '@/components/layout'; +import { Post, SkeletonUI } from '@/components/ui'; +import useAuth from '@/hooks/useAuth'; +import useCustomNotices from './hooks/useCustomNotices'; + +const CustomNoticeList = () => { + const { user } = useAuth(); + const { notices, isLoading, error } = useCustomNotices(user?.address); + + if (error) { + return
{error}
; + } + + return ( + <> + {isLoading ? ( + + ) : ( + + {notices.map(notice => ( +
  • + +
  • + ))} +
    + )} + + ); +}; + +const CustomNotice = () => ( +
    + +

    맞춤공고

    + +
    +
    +); +export default CustomNotice; diff --git a/src/components/features/noticeList/hooks/useCustomNotices.ts b/src/components/features/noticeList/hooks/useCustomNotices.ts new file mode 100644 index 0000000..c83ef17 --- /dev/null +++ b/src/components/features/noticeList/hooks/useCustomNotices.ts @@ -0,0 +1,55 @@ +import useAsync from '@/hooks/useAsync'; +import axiosInstance from '@/lib/axios'; +import { paramsSerializer } from '@/lib/utils/paramsSerializer'; +import { toPostCard } from '@/lib/utils/parse'; +import { NoticeQuery } from '@/types/api'; +import { PostCard } from '@/types/notice'; +import { useCallback, useEffect } from 'react'; + +const useCustomNotices = (address?: string) => { + const { data, isLoading, error, fetch } = useAsync(); + + const fetchCustom = useCallback(async () => { + const now = new Date(); + now.setSeconds(now.getSeconds() + 15); + + // 기본 쿼리 + const baseQuery: NoticeQuery = { + sort: 'time', + startsAtGte: now.toISOString(), + limit: 3, + }; + + const firstQuery: NoticeQuery = address ? { ...baseQuery, address: [address] } : baseQuery; + const getCustom = axiosInstance + .get('/notices', { + params: firstQuery, + paramsSerializer: { serialize: paramsSerializer }, + }) + .then(async res => { + const items = res.data.items.map(toPostCard); + if (items.length === 0) { + const fallbackRes = await axiosInstance.get('/notices', { + params: baseQuery, + paramsSerializer: { serialize: paramsSerializer }, + }); + return fallbackRes.data.items.map(toPostCard); + } + return items; + }); + + await fetch(getCustom); + }, [address, fetch]); + + useEffect(() => { + fetchCustom(); + }, []); + + return { + notices: data ?? [], + isLoading, + error, + fetchCustom, + }; +}; +export default useCustomNotices; diff --git a/src/components/features/noticeList/hooks/useNotices.ts b/src/components/features/noticeList/hooks/useNotices.ts new file mode 100644 index 0000000..7801941 --- /dev/null +++ b/src/components/features/noticeList/hooks/useNotices.ts @@ -0,0 +1,86 @@ +import useAsync from '@/hooks/useAsync'; +import axiosInstance from '@/lib/axios'; +import { paramsSerializer } from '@/lib/utils/paramsSerializer'; +import { toPostCard } from '@/lib/utils/parse'; +import { NoticeQuery, PaginatedResponse } from '@/types/api'; +import { PostCard } from '@/types/notice'; +import { useCallback, useState } from 'react'; + +const INIT_FILTER_DATA: NoticeQuery = { + sort: 'time', +}; + +const useNotices = (initialQuery: Partial = {}) => { + const { data, isLoading, isInitialized, error, fetch } = useAsync(); + const [filters, setFiltersState] = useState(INIT_FILTER_DATA); + const [pagination, setPagination] = useState({ + offset: 0, + limit: 6, + count: 0, + hasNext: false, + }); + + const changeTimeFilter = useCallback((q: Partial): Partial => { + const now = new Date(); + now.setSeconds(now.getSeconds() + 15); // 서버 시간 오차 대비 + + // startsAtGte가 없거나, 현재보다 과거면 현재 시각으로 보정 + if (!q.startsAtGte || new Date(q.startsAtGte) < now) { + return { ...q, startsAtGte: now.toISOString() }; + } + + return q; + }, []); + + const fetchNotices = useCallback( + async (query?: Partial) => { + // 검색 필터 업데이트 + const mergedFilter: NoticeQuery = { + ...filters, // 내부 초기값 + limit: pagination.limit, + offset: pagination.offset, + ...initialQuery, // 외부 초기값 + ...(query ?? {}), // fetchNotices 호출 시 추가 값 + }; + const queryUpdate = changeTimeFilter(mergedFilter) as NoticeQuery; + // 상태에도 반영하여 UI와 동기화 + + setFiltersState(prev => ({ ...prev, ...queryUpdate })); + // 필터기반 패치 + const getNotices = axiosInstance + .get('/notices', { + params: queryUpdate, + paramsSerializer: { serialize: paramsSerializer }, + }) + .then(res => { + setPagination({ + offset: res.data.offset, + limit: res.data.limit, + count: res.data.count, + hasNext: res.data.hasNext, + }); + return res.data.items.map(toPostCard); + }); + + await fetch(getNotices); + }, + [initialQuery, fetch, filters, changeTimeFilter] + ); + + const reset = useCallback(() => { + setFiltersState(INIT_FILTER_DATA); + fetchNotices(INIT_FILTER_DATA); + }, []); + + return { + notices: data ?? [], + pagination, + isLoading, + isInitialized, + error, + fetchNotices, + filters, + reset, + }; +}; +export default useNotices; diff --git a/src/components/features/noticeList/hooks/useRecentNotice.ts b/src/components/features/noticeList/hooks/useRecentNotice.ts index a508dbc..7fb2efc 100644 --- a/src/components/features/noticeList/hooks/useRecentNotice.ts +++ b/src/components/features/noticeList/hooks/useRecentNotice.ts @@ -6,8 +6,6 @@ const RECENT_KEY = 'thejulge_recent'; // 최근 본 공고 저장 export const useRecentNotice = (notice: NoticeCard) => { const handleRecentNotice = useCallback(() => { - if (!notice) return; - const current: RecentNotice = { id: notice.id, shopId: notice.shopId, @@ -42,7 +40,7 @@ export const useRecentNotice = (notice: NoticeCard) => { }; // 최근 본 공고 불러오기 -export function useRecentNoticeList() { +export const useRecentNoticeList = () => { const [recentNotices, setRecentNotices] = useState([]); useEffect(() => { @@ -54,4 +52,4 @@ export function useRecentNoticeList() { }, []); return { recentNotices }; -} +}; diff --git a/src/components/features/noticeList/index.ts b/src/components/features/noticeList/index.ts index 3129276..7f6a357 100644 --- a/src/components/features/noticeList/index.ts +++ b/src/components/features/noticeList/index.ts @@ -1,3 +1,4 @@ -export { default as NoticeListSection } from './noticeListSection'; - +export { default as CustomNotice } from './customNotice'; export { default as RecentNoticeList } from './recentNoticeList'; +export { default as NoticeListSection } from './noticeListSection'; +export { default as NoticeEmpty } from './noticeEmpty'; diff --git a/src/components/features/noticeList/noticeEmpty.tsx b/src/components/features/noticeList/noticeEmpty.tsx index f0caac8..37fb264 100644 --- a/src/components/features/noticeList/noticeEmpty.tsx +++ b/src/components/features/noticeList/noticeEmpty.tsx @@ -25,7 +25,7 @@ const NoticeEmpty = ({ q, onReset }: NoticeEmptyProps) => { 홈으로 돌아가기 ) : ( - )} diff --git a/src/components/features/noticeList/noticeList.tsx b/src/components/features/noticeList/noticeList.tsx index a9d1acd..5294dc2 100644 --- a/src/components/features/noticeList/noticeList.tsx +++ b/src/components/features/noticeList/noticeList.tsx @@ -1,39 +1,33 @@ -import { Post } from '@/components/ui'; -import { Pagination } from '@/components/ui/pagination'; -import { useNotice } from '@/context/noticeProvider'; +import { Post, SkeletonUI } from '@/components/ui'; +import { ApiAsync } from '@/types/api'; +import { PostCard } from '@/types/notice'; +import NoticeEmpty from './noticeEmpty'; -interface NoticeProps { +interface NoticeProps extends ApiAsync { + notices: PostCard[]; q?: string; + reset: () => void; } -const NoticeList = ({ q }: NoticeProps) => { - const { notices, isLoading, error, pagination, fetchNotices } = useNotice(); +const NoticeList = ({ notices, q, isLoading, isInitialized, reset, error }: NoticeProps) => { if (error) { return
    {error}
    ; } + if (!isInitialized || isLoading) { + if (q) return; + ; + } + if (notices.length === 0) { - return
    {q && q + '에 대한 '}공고가 존재하지 않습니다
    ; + return reset()} />; } return ( - <> - {isLoading ? ( -
    로딩중 .. 스켈레톤 UI 삽입예정
    - ) : ( -
    - {notices.map(notice => ( - - ))} -
    - )} - fetchNotices({ offset: next })} - className='mt-8 tablet:mt-10' - /> - +
    + {notices.map(notice => ( + + ))} +
    ); }; export default NoticeList; diff --git a/src/components/features/noticeList/noticeListFilter.tsx b/src/components/features/noticeList/noticeListFilter.tsx index af815d8..88b1eeb 100644 --- a/src/components/features/noticeList/noticeListFilter.tsx +++ b/src/components/features/noticeList/noticeListFilter.tsx @@ -1,9 +1,8 @@ import { Dropdown, Filter } from '@/components/ui'; import { getActiveFilterCount } from '@/components/ui/filter/getActiveFilterCount'; import { SORT_CODE, type SortCode } from '@/constants/dropdown'; -import { useNotice } from '@/context/noticeProvider'; -import { FilterQuery, type sort } from '@/types/api'; -import { useState } from 'react'; +import { FilterQuery, NoticeQuery, type sort } from '@/types/api'; +import { useMemo } from 'react'; const SORT_TO_API: Record = { '마감 임박 순': 'time', @@ -12,20 +11,28 @@ const SORT_TO_API: Record = { '가나다 순': 'shop', }; -const NoticeListFilter = () => { - const { fetchNotices, updateFilters, filters } = useNotice(); - const [sort, setSort] = useState('마감 임박 순'); +interface NoticeListFilterProps { + filters: NoticeQuery; + onSortChange: (sort: sort) => void; + onFilterSubmit: (filter: FilterQuery) => void; +} + +const NoticeListFilter = ({ filters, onSortChange, onFilterSubmit }: NoticeListFilterProps) => { + const selectedLabel = useMemo(() => { + const currentSort = filters.sort ?? 'time'; + const entry = Object.entries(SORT_TO_API).find(([, v]) => v === currentSort); + return entry?.[0] as SortCode; + }, [filters.sort]); + const appliedCount = getActiveFilterCount(filters); - const handleSort = (label: SortCode) => { - const sort = SORT_TO_API[label]; - fetchNotices({ sort }); - setSort(label); + const handleSort = (next: SortCode) => { + const s = SORT_TO_API[next]; + onSortChange(s); }; - const handleFilter = (q: FilterQuery) => { - updateFilters(q); - fetchNotices(q); + const handleFilter = (filter: FilterQuery) => { + onFilterSubmit(filter); }; return ( @@ -35,7 +42,7 @@ const NoticeListFilter = () => { ariaLabel='공고 정렬 기준' size='sm' values={SORT_CODE} - selected={sort} + selected={selectedLabel} onChange={handleSort} /> diff --git a/src/components/features/noticeList/noticeListHeader.tsx b/src/components/features/noticeList/noticeListHeader.tsx new file mode 100644 index 0000000..12dd831 --- /dev/null +++ b/src/components/features/noticeList/noticeListHeader.tsx @@ -0,0 +1,15 @@ +const NoticeListHeader = ({ q }: { q?: string }) => { + return ( +

    + {q ? ( + <> + {q}에 대한 공고 목록 + + ) : ( + '전체공고' + )} +

    + ); +}; + +export default NoticeListHeader; diff --git a/src/components/features/noticeList/noticeListSection.tsx b/src/components/features/noticeList/noticeListSection.tsx index fc742cf..fa872f7 100644 --- a/src/components/features/noticeList/noticeListSection.tsx +++ b/src/components/features/noticeList/noticeListSection.tsx @@ -1,50 +1,54 @@ import NoticeList from '@/components/features/noticeList/noticeList'; import NoticeListFilter from '@/components/features/noticeList/noticeListFilter'; import { Container } from '@/components/layout'; -import { useNotice } from '@/context/noticeProvider'; +import { Pagination } from '@/components/ui'; import type { NoticeQuery } from '@/types/api'; import { useEffect } from 'react'; +import useNotices from './hooks/useNotices'; +import NoticeListHeader from './noticeListHeader'; interface NoticeListSectionProps { - title?: string; // 제목 q?: string; // 검색어 - showFilter?: boolean; // 우측 필터 표시 여부 initialFilters?: Partial; // 섹션 진입 시 적용할 초기 필터 } -const NoticeListSection = ({ - title = '전체 공고', - q, - showFilter, - initialFilters, -}: NoticeListSectionProps) => { - const { updateFilters, fetchNotices } = useNotice(); - // 섹션 진입 및 q/initialFilters 변경 시 초기 필터 반영하여 조회 +const NoticeListSection = ({ q, initialFilters }: NoticeListSectionProps) => { + const { notices, isLoading, isInitialized, error, pagination, fetchNotices,reset, filters } = + useNotices(); + useEffect(() => { - const hasInitial = Boolean(initialFilters && Object.keys(initialFilters).length > 0); - if (hasInitial) { - updateFilters(initialFilters!); - fetchNotices(initialFilters); - } else { - fetchNotices(); - } - - // fetchNotices와 updateFilters 함수는 initialFilters값이 변경될때 새로만들어짐 + // 새 검색어/필터로 진입 시 페이지를 1페이지로 리셋 + fetchNotices({ ...(initialFilters ?? {}), offset: 0 }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [q, initialFilters]); + return (
    - {q ? ( -

    - {q}에 대한 공고 목록 -

    - ) : ( -

    {title}

    - )} - {showFilter && } + + fetchNotices({ sort })} + onFilterSubmit={filter => fetchNotices(filter)} + />
    - + + {!isLoading && ( + fetchNotices({ offset: next })} + className='mt-8 tablet:mt-10' + /> + )}
    ); }; diff --git a/src/components/layout/horizontalScroll/horizontalScroll.tsx b/src/components/layout/horizontalScroll/horizontalScroll.tsx new file mode 100644 index 0000000..04dc308 --- /dev/null +++ b/src/components/layout/horizontalScroll/horizontalScroll.tsx @@ -0,0 +1,80 @@ +import { cn } from '@/lib/utils/cn'; +import { ElementType, ReactNode, useEffect, useRef } from 'react'; + +interface HorizontalScrollProps { + as?: ElementType; + scrollSpeed?: number; // 마우스 휠 감도 + touchSensitivity?: number; // 터치 이동 감도 (1 기본값) + className?: string; + children: ReactNode; +} + +// 마우스 휠 → 가로 스크롤 + 터치 스와이프 +const HorizontalScroll = ({ + as: Component = 'div', + scrollSpeed = 2, + touchSensitivity = 1, + className, + children, +}: HorizontalScrollProps) => { + const containerRef = useRef(null); + const startX = useRef(0); + const scrollStart = useRef(0); + const isDragging = useRef(false); + + useEffect(() => { + const el = containerRef.current; + if (!el) return; + + // 마우스 휠 세로 -> 가로 + const handleWheel = (e: WheelEvent) => { + const atLeftEnd = el.scrollLeft <= 0; + const atRightEnd = el.scrollLeft + el.offsetWidth >= el.scrollWidth; + const scrollingUp = e.deltaY < 0; + const scrollingDown = e.deltaY > 0; + + if ((atLeftEnd && scrollingUp) || (atRightEnd && scrollingDown)) return; + + e.preventDefault(); + el.scrollLeft += e.deltaY * scrollSpeed; + }; + + // 터치 스와이프 시작 + const handleTouchStart = (e: TouchEvent) => { + isDragging.current = true; + startX.current = e.touches[0].clientX; + scrollStart.current = el.scrollLeft; + }; + + // 터치 스와이프 + const handleTouchMove = (e: TouchEvent) => { + if (!isDragging.current) return; + const currentX = e.touches[0].clientX; + const deltaX = (startX.current - currentX) * touchSensitivity; + el.scrollLeft = scrollStart.current + deltaX; + }; + + const handleTouchEnd = () => { + isDragging.current = false; + }; + + el.addEventListener('wheel', handleWheel, { passive: false }); + el.addEventListener('touchstart', handleTouchStart, { passive: true }); + el.addEventListener('touchmove', handleTouchMove, { passive: true }); + el.addEventListener('touchend', handleTouchEnd); + + return () => { + el.removeEventListener('wheel', handleWheel); + el.removeEventListener('touchstart', handleTouchStart); + el.removeEventListener('touchmove', handleTouchMove); + el.removeEventListener('touchend', handleTouchEnd); + }; + }, [scrollSpeed, touchSensitivity]); + + return ( + + {children} + + ); +}; +export default HorizontalScroll; diff --git a/src/components/layout/horizontalScroll/index.ts b/src/components/layout/horizontalScroll/index.ts new file mode 100644 index 0000000..ed31bd8 --- /dev/null +++ b/src/components/layout/horizontalScroll/index.ts @@ -0,0 +1 @@ +export { default as HorizontalScroll } from './horizontalScroll'; diff --git a/src/components/layout/index.ts b/src/components/layout/index.ts index 0232f88..d693e4d 100644 --- a/src/components/layout/index.ts +++ b/src/components/layout/index.ts @@ -2,3 +2,4 @@ export { Container, Wrapper } from './container'; export { Footer } from './footer'; export { Frame } from './frame'; export { Header } from './header'; +export { HorizontalScroll } from './horizontalScroll'; diff --git a/src/components/ui/card/card.styles.ts b/src/components/ui/card/card.styles.ts index 0f3715a..1e227bc 100644 --- a/src/components/ui/card/card.styles.ts +++ b/src/components/ui/card/card.styles.ts @@ -31,7 +31,7 @@ const cardInfoText = cva('text-caption tablet:text-body-s', { defaultVariants: { status: 'open' }, }); -const cardInfoIcon = cva('', { +const cardInfoIcon = cva('shrink-0', { variants: { status: { open: 'bg-gray-700', diff --git a/src/context/noticeProvider.tsx b/src/context/noticeProvider.tsx deleted file mode 100644 index 01472a5..0000000 --- a/src/context/noticeProvider.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import axiosInstance from '@/lib/axios'; -import { paramsSerializer } from '@/lib/utils/paramsSerializer'; -import { toPostCard } from '@/lib/utils/parse'; -import { NoticeQuery, PaginatedResponse } from '@/types/api'; -import { PostCard } from '@/types/notice'; -import { createContext, ReactNode, useCallback, useContext, useMemo, useState } from 'react'; - -interface NoticeContextValue { - notices: PostCard[]; - pagination: PaginatedResponse; - isLoading: boolean; - error: string | null; - filters: NoticeQuery; - fetchNotices: (params?: Partial) => Promise; - updateFilters: (filters: Partial) => void; - reset: () => void; -} - -//현재 필터 상태(filters)의 초기값 -const INIT_FILTER_DATA: NoticeQuery = { - sort: 'time', -}; - -const NoticeContext = createContext(undefined); - -// 맞춤 공고, 전체 공고, 검색된 공고등 공고 조회 관리 -export const NoticeProvider = ({ children }: { children: ReactNode }) => { - const [notices, setNotices] = useState([]); // PostCard data - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - const [filters, setFiltersState] = useState(INIT_FILTER_DATA); // 현재 페이지에서 적용 중인 필터 조건 - const [pagination, setPagination] = useState({ - offset: 0, - limit: 10, - count: 0, - hasNext: false, - }); - - // sort=time일 때 startsAtGte를 항상 현재 시각으로 보정 - const changeTimeFilter = useCallback((q: Partial): Partial => { - if (q.sort !== 'time') return q; - const now = new Date(); - now.setSeconds(now.getSeconds() + 120); - const nowIso = now.toISOString(); - const startsAt = q.startsAtGte ? new Date(q.startsAtGte) : null; - if (!startsAt || isNaN(startsAt.getTime()) || startsAt.getTime() < now.getTime()) { - return { ...q, startsAtGte: nowIso }; - } - return q; - }, []); - - // 공고 데이터 요청 함수. 파라미터를 넣으면 검색/정렬 가능 - const fetchNotices: NoticeContextValue['fetchNotices'] = useCallback( - async params => { - try { - setIsLoading(true); - setError(null); - const merged = { ...filters, ...(params ?? {}) }; - const query = changeTimeFilter(merged); // time 정렬 보정 - // 뒤로가기 등으로 과거 시각이 남지 않도록 보정된 startsAtGte를 상태에도 반영 - if (query.startsAtGte !== filters.startsAtGte || query.sort !== filters.sort) { - setFiltersState(prev => ({ ...prev, ...query })); - } - const res = await axiosInstance.get('/notices', { - params: query, - paramsSerializer: { serialize: paramsSerializer }, - // 서버에서 원하는 형태로 직렬화 - }); - // 공고 목록 업데이트 - setNotices(res.data.items.map(toPostCard)); // toPostCard 컴포넌트에서 필요한 필드만 추출 - //페이지네이션 정보 업데이트 - setPagination({ - offset: res.data.offset, - limit: res.data.limit, - count: res.data.count, - hasNext: res.data.hasNext, - }); - } catch { - setError(`공고를 불러오는 중 오류가 발생했습니다.`); - setNotices([]); - setPagination(prev => ({ ...prev, count: 0, hasNext: false })); - setFiltersState(INIT_FILTER_DATA); - } finally { - setIsLoading(false); - } - }, - [filters] - ); - - // 기존 필터를 유지하면서 filters 상태만 부분 업데이트 하여 특정 조건만 수정 - // 사용자가 선택한 필터 조건을 Context 상태에 반영하는 함수 - const updateFilters: NoticeContextValue['updateFilters'] = useCallback( - partial => { - setFiltersState(prev => ({ ...prev, ...changeTimeFilter(partial) })); - }, - [changeTimeFilter] - ); - - // 공고와 필터 초기화 - const reset = useCallback(() => { - setNotices([]); - setFiltersState(INIT_FILTER_DATA); - }, []); - - const value = useMemo( - () => ({ - notices, - pagination, - isLoading, - error, - filters, - fetchNotices, - updateFilters, - reset, - }), - [notices, pagination, isLoading, error, filters, fetchNotices, updateFilters, reset] - ); - - return {children}; -}; - -export const useNotice = () => { - const context = useContext(NoticeContext); - if (!context) throw new Error('useContext는 NoticeContext 안에서 사용해야 합니다.'); - return context; -}; diff --git a/src/hooks/useAsync.ts b/src/hooks/useAsync.ts new file mode 100644 index 0000000..d48f1cd --- /dev/null +++ b/src/hooks/useAsync.ts @@ -0,0 +1,40 @@ +import { ApiAsync } from '@/types/api'; +import { useCallback, useState } from 'react'; + +interface UseAsyncState extends ApiAsync{ + data: T | null; + fetch: (promise: Promise) => Promise; + reset: () => void; +} + +const useAsync = (): UseAsyncState => { + const [data, setData] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [isInitialized, setIsInitialized] = useState(false); + + const fetch = useCallback(async (promise: Promise) => { + try { + setIsInitialized(true); + setIsLoading(true); + setError(null); + const result = await promise; + setData(result); + return result; + } catch (err) { + setError(`요청 중 오류가 발생했습니다 : ${err}`); + } finally { + setIsLoading(false); + } + }, []); + + const reset = useCallback(() => { + setData(null); + setError(null); + setIsLoading(false); + setIsInitialized(false); + }, []); + + return { data, isLoading, isInitialized, error, fetch, reset }; +}; +export default useAsync; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 327a1e7..ae469dc 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,25 +1,10 @@ -import { NoticeListSection } from '@/components/features'; -import { NoticeProvider } from '@/context/noticeProvider'; -import useAuth from '@/hooks/useAuth'; -import { useMemo } from 'react'; +import { CustomNotice, NoticeListSection } from '@/components/features'; export default function Main() { - const { user } = useAuth(); - const recommendedAddress = user?.address ?? ''; - const initialFilters = useMemo( - () => ({ limit: 3, address: [recommendedAddress] }), - [recommendedAddress] - ); return ( <> - -
    - -
    -
    - - ; - + + ; ); } diff --git a/src/pages/search.tsx b/src/pages/search.tsx index de20633..418e8a9 100644 --- a/src/pages/search.tsx +++ b/src/pages/search.tsx @@ -1,33 +1,12 @@ -import NoticeListSection from '@/components/features/noticeList/noticeListSection'; -import { NoticeProvider } from '@/context/noticeProvider'; -import axiosInstance from '@/lib/axios'; -import type { GetServerSideProps } from 'next'; +import { NoticeListSection } from '@/components/features'; +import { useRouter } from 'next/router'; import { useMemo } from 'react'; -type SearchProps = { - q: string; -}; - -export const getServerSideProps: GetServerSideProps = async ({ query }) => { - const q = query.q as string; - if (!q) { - return { props: { q: '' } }; - } - - try { - await axiosInstance.get('/notices', { params: { keyword: q } }); - return { props: { q } }; - } catch { - return { props: { q } }; - } -}; - -const Search = ({ q }: SearchProps) => { - const initialFilters = useMemo(() => ({ keyword: q }), [q]); - return ( - - - - ); +const Search = () => { + const router = useRouter(); + const q = typeof router.query.q === 'string' ? router.query.q : ''; + const initialFilters = useMemo(() => (q ? { keyword: q } : undefined), [q]); + + return ; }; export default Search; diff --git a/src/styles/globals.css b/src/styles/globals.css index c1568bd..438819f 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -182,7 +182,14 @@ body { background: transparent; } } - +.none-scroll-bar { + overflow: auto; + -ms-overflow-style: none; /* 인터넷 익스플로러 */ + scrollbar-width: none; /* 파이어폭스 */ + &:-webkit-scrollbar { + display: none; + } +} .icon-btn { line-height: 0; } diff --git a/src/types/api.ts b/src/types/api.ts index 802881f..69a50a1 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -50,3 +50,9 @@ export interface NoticeQuery extends FilterQuery { keyword?: string; sort?: sort; } + +export interface ApiAsync { + isLoading: boolean; + isInitialized: boolean; + error: string | null; +}