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
2 changes: 2 additions & 0 deletions services/one-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@
},
"dependencies": {
"@faker-js/faker": "^9.0.3",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@svgr/webpack": "^8.1.0",
"@tanstack/react-query": "^5.59.16",
"axios": "^1.7.7",
"clsx": "^2.1.1",
"js-cookie": "^3.0.5",
"next": "14.2.16",
"nuqs": "^2.2.2",
"query-string": "^9.1.1",
"react": "^18",
"react-dom": "^18",
Expand Down
6 changes: 5 additions & 1 deletion services/one-app/src/app/(site)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { Metadata } from 'next';
import { NuqsAdapter } from 'nuqs/adapters/next/app';

import '../globals.css';
import { RQProvider } from '../_components/RQProvider';
import { MSWComponent } from '../_components/MSWComponent';
Expand All @@ -19,7 +21,9 @@ export default function RootLayout({
<html lang="ko">
<body className={cn('font-sans antialiased', Pretendard.variable)}>
<MSWComponent />
<RQProvider>{children}</RQProvider>
<RQProvider>
<NuqsAdapter>{children}</NuqsAdapter>
</RQProvider>
</body>
</html>
);
Expand Down
12 changes: 8 additions & 4 deletions services/one-app/src/app/(site)/lost-found/[lostId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import Link from 'next/link';

import LostFoundPostDetail from '../_components/postDetail/PostDetail';
import PlusIcon from '@/common/assets/icons/plus';
import { SuspenseQueryBoundary } from '@/common/components';
import LostFoundDetail from '../_components/LostFoundDetail';
import { SuspenseQueryBoundary } from '@/common/components/SuspenseQueryBoundary/SuspenseQueryBoundary';

type Props = {
params: {
Expand All @@ -18,8 +19,11 @@ export default function LostFoundDetailPage({ params }: Props) {
<Link href={'/lost-found/1'}>
<PlusIcon />
</Link>
<SuspenseQueryBoundary suspenseFallback={<div>loading</div>}>
<LostFoundDetail lostId={lostId} />
<SuspenseQueryBoundary
errorFallback={<div>error</div>}
suspenseFallback={<div>loading</div>}
>
<LostFoundPostDetail lostId={lostId} />
</SuspenseQueryBoundary>
</div>
</main>
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use client';

import React from 'react';
import { useGetLostFoundDetail } from '../../_lib/get';

type Props = {
lostId: string;
};

const LostFoundPostDetail = ({ lostId }: Props) => {
const { data } = useGetLostFoundDetail(lostId);

return <div>{data.title}</div>;
};

export default LostFoundPostDetail;
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react';
import { FilterState } from '@/store/filter';
import { LostFoundFilters, LostFoundType } from '@/model/LostFound';
import { DropdownFilter } from '@/common/components/Filters/DropdownFilter';
import { subwayLineIdOptions } from '@/common/constants/subway';
import ResetFilter from '@/common/components/Filters/ResetFilter';
import SearchFilter from '@/common/components/Filters/SearchFilter';

const lostTypeOptions = {
[LostFoundType.LOST]: '분실물',
[LostFoundType.ACQUIRE]: '습득물',
};

interface LostFoundFilterListProps
extends Omit<FilterState<LostFoundFilters>, 'loaded'> {}

const LostFoundFilterList = ({
filters,
activedCount,
handleSelect,
handleReset,
}: LostFoundFilterListProps) => {
return (
<section className=" flex flex-col gap-4">
<SearchFilter />
<div>
<ResetFilter activedCount={activedCount} handleReset={handleReset} />
<DropdownFilter
name="lostType"
filters={filters}
options={lostTypeOptions}
onSelect={handleSelect}
/>
<DropdownFilter
name="subwayLineId"
filters={filters}
options={subwayLineIdOptions}
onSelect={handleSelect}
/>
</div>
</section>
);
};

export default LostFoundFilterList;
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use client';

import React, { useMemo } from 'react';
import Link from 'next/link';

import type { LostFoundFilters } from '@/model/LostFound';
import { flattenInfinityList } from '@/common/utils/react-query';
import { gneratetAccurateSubwayLineId } from '@/common/utils/subway';
import { useIntersectionObserver } from '@/common/hooks/useIntersectionObserver';
import { useGetLostFoundList } from '@/app/(site)/lost-found/_lib/get';

interface Props {
keyword: string | null;
filters: LostFoundFilters;
}

const LostFoundSearchedList = ({ keyword, filters }: Props) => {
const temporaryUserFavoriteLineId = 1; // TODO: 추후 유저가 즐겨찾는 역 설장하는 피쳐 개발 후 수정하기
const subwayLineId = useMemo(
() =>
gneratetAccurateSubwayLineId(
filters.subwayLineId,
temporaryUserFavoriteLineId,
),
[filters.subwayLineId],
);

const { data, hasNextPage, isFetchingNextPage, fetchNextPage } =
useGetLostFoundList({
pageSize: 40,
keyword,
subwayLineId,
lostType: filters.lostType,
});

const lostArticles = flattenInfinityList(data);
const intersectCallback = () => !isFetchingNextPage && fetchNextPage();
const { ref: loadMoreRef } = useIntersectionObserver({
callback: intersectCallback,
});

return (
<>
{lostArticles.map((item, idx) => (
// item.id로만 하면 key 중복이 발생하고 있음.
// 해결 필요
<Link key={`${item.id}-${idx}`} href={`/lost-found/${item.id}`}>
{item.title}
</Link>
))}
{hasNextPage && (
<span ref={loadMoreRef} className="visuallyHidden">
더 보기
</span>
)}
</>
);
};

export default LostFoundSearchedList;
4 changes: 2 additions & 2 deletions services/one-app/src/app/(site)/lost-found/_lib/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import type {
LostFoundPostDetail,
LostFoundListParams,
} from '@/model/LostFound';
import { TIMESTAMP } from '@/constants/time';
import { TIMESTAMP } from '@/common/constants/time';
import { generateQueryKey } from '@/common/utils/react-query';
import { objectToQueryString } from '@/common/utils/object';

const getLostFoundList = (params: LostFoundListParams) =>
apiClient.get<IResponse<ListResponseWithPagination<LostFoundPost>>>(
`/lost-posts?${objectToQueryString(params)}`,
`/lost-posts?${objectToQueryString(params, { removeZero: true })}`,
);

export const useGetLostFoundList = (params: LostFoundListParams) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use client';

import { useQueryState } from 'nuqs';

import {
APP_UNIQUE_FILTER_ID_LIST,
FilterState,
createFilterStoreWithPersist,
} from '@/store/filter';
import { SubwayLineFilterOptions } from '@/model/Subway';
import { LostFoundType, type LostFoundFilters } from '@/model/LostFound';

const LOST_FOUND_FILTER_DEFAULT_VALUES: LostFoundFilters = {
lostType: LostFoundType.LOST,
subwayLineId: SubwayLineFilterOptions.ALL_LINES,
} as const;

export const useLostFoundFilters = () => {
const [keyword] = useQueryState('keyword');
const { filters, loaded, activedCount, handleSelect, handleReset } =
createFilterStoreWithPersist<LostFoundFilters>(
LOST_FOUND_FILTER_DEFAULT_VALUES,
APP_UNIQUE_FILTER_ID_LIST.LOST_FOUND,
)();

const boundaryKeys = [...Object.values(filters), keyword];

const getFilterProps = (): Omit<FilterState<LostFoundFilters>, 'loaded'> => ({
filters,
activedCount,
handleSelect,
handleReset,
});

return {
loaded,
filters,
keyword,
boundaryKeys,
getFilterProps,
};
};
43 changes: 32 additions & 11 deletions services/one-app/src/app/(site)/lost-found/page.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,40 @@
import Link from 'next/link';
import PlusIcon from '@/common/assets/icons/plus';
import LostFoundList from './_components/LostFoundList';
import { SuspenseQueryBoundary } from '@/common/components';
'use client';
import { Suspense } from 'react';

export default function LostFoundPage() {
return (
import LostFoundFilterList from './_components/searchResults/filterList/FilterList';
import LostFoundSearchedList from './_components/searchResults/searchedList/SearchedList';
import { useLostFoundFilters } from './_lib/useLostFoundFilterStore';
import { SuspenseQueryBoundary } from '@/common/components/SuspenseQueryBoundary/SuspenseQueryBoundary';

const LoadingPage = () => <div>Loading...</div>;

function LostFound() {
const { loaded, keyword, filters, boundaryKeys, getFilterProps } =
useLostFoundFilters();

return loaded ? (
<main className="flex min-h-screen flex-col text-black bg-white ">
<div className="flex flex-col gap-3">
<Link href={'/lost-found/1'}>
<PlusIcon />
</Link>
<SuspenseQueryBoundary suspenseFallback={<div>loading</div>}>
<LostFoundList />
<LostFoundFilterList {...getFilterProps()} />
<SuspenseQueryBoundary
keys={boundaryKeys}
resetError={() => {}}
errorFallback={<div>error</div>}
suspenseFallback={<div>loading</div>}
>
<LostFoundSearchedList keyword={keyword} filters={filters} />
</SuspenseQueryBoundary>
</div>
</main>
) : (
<LoadingPage />
);
}

export default function LostFoundPage() {
return (
<Suspense fallback={<LoadingPage />}>
<LostFound />
</Suspense>
);
}
34 changes: 34 additions & 0 deletions services/one-app/src/common/assets/icons/chevron-down.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as React from 'react';

function ChevronDownIcon() {
return (
<svg
width="16"
height="17"
viewBox="0 0 16 17"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clipPath="url(#clip0_6028_20274)">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8.47136 10.9713C8.34634 11.0963 8.1768 11.1665 8.00002 11.1665C7.82325 11.1665 7.65371 11.0963 7.52869 10.9713L3.75736 7.19999C3.69369 7.1385 3.6429 7.06493 3.60796 6.9836C3.57302 6.90226 3.55463 6.81478 3.55386 6.72626C3.55309 6.63774 3.56996 6.54996 3.60348 6.46803C3.637 6.38609 3.6865 6.31166 3.7491 6.24906C3.81169 6.18647 3.88612 6.13697 3.96806 6.10345C4.04999 6.06993 4.13777 6.05306 4.22629 6.05383C4.31481 6.0546 4.40229 6.07299 4.48363 6.10793C4.56496 6.14287 4.63853 6.19365 4.70003 6.25733L8.00002 9.55733L11.3 6.25733C11.4258 6.13589 11.5942 6.06869 11.769 6.07021C11.9438 6.07173 12.111 6.14184 12.2346 6.26545C12.3582 6.38905 12.4283 6.55626 12.4298 6.73106C12.4313 6.90586 12.3641 7.07426 12.2427 7.19999L8.47136 10.9713Z"
fill="#95979F"
/>
</g>
<defs>
<clipPath id="clip0_6028_20274">
<rect
width="16"
height="16"
fill="white"
transform="translate(0 0.5)"
/>
</clipPath>
</defs>
</svg>
);
}

export default ChevronDownIcon;
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ interface Props {
* 선언적으로 간편한 조건부 렌더링을 제공합니다.
* @param props
*/
export const ConditionalRender = (props: Props) => {
const { children, isRender } = props;
export const ConditionalRender = ({ isRender, children }: Props) => {
return <>{isRender ? children : null}</>;
};
Loading
Loading