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
6 changes: 6 additions & 0 deletions src/app/(pages)/mypage/components/FilterBar/TabMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ export default function TabMenu() {
const createQueryString = (tab: string) => {
const params = new URLSearchParams(searchParams.toString());
params.set("tab", tab);

if (tab !== "scrap") {
params.delete("isPublic");
params.delete("isRecruiting");
}

return params.toString();
};

Expand Down
85 changes: 63 additions & 22 deletions src/app/(pages)/mypage/components/sections/ScrapsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,56 +4,97 @@ import React, { useEffect } from "react";
import { useInView } from "react-intersection-observer";
import { useMyScraps } from "@/hooks/queries/user/me/useMyScraps";
import { useSortStore } from "@/store/sortStore";
import { useFilterStore } from "@/store/filterStore";
import type { FormListType } from "@/types/response/form";
import FilterDropdown from "@/app/components/button/dropdown/FilterDropdown";
import { filterPublicOptions, filterRecruitingOptions } from "@/constants/filterOptions";
import { useRouter, usePathname, useSearchParams } from "next/navigation";

// 한 페이지당 스크랩 수
const SCRAPS_PER_PAGE = 10;

export default function ScrapsSection() {
// 정렬 상태 관리
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();

// URL 쿼리 파라미터에서 필터 상태 가져오기
const isPublic = searchParams.get("isPublic");
const isRecruiting = searchParams.get("isRecruiting");
const { orderBy } = useSortStore();
const { filterBy, setFilterBy } = useFilterStore();

// 초기 마운트 시 필터 값 설정
useEffect(() => {
const params = new URLSearchParams(searchParams);
let needsUpdate = false;

if (!params.has("isPublic")) {
params.set("isPublic", "true");
needsUpdate = true;
}
if (!params.has("isRecruiting")) {
params.set("isRecruiting", "true");
needsUpdate = true;
}
if (needsUpdate) {
params.set("tab", "scrap");
router.push(`${pathname}?${params.toString()}`);
}
}, []);

// 무한 스크롤을 위한 Intersection Observer 설정
const { ref, inView } = useInView({
threshold: 0.1, // 10% 정도 보이면 트리거
triggerOnce: true, // 한 번만 트리거 (불필요한 API 호출 방지)
rootMargin: "100px", // 하단 100px 전에 미리 로드
threshold: 0.1,
triggerOnce: true,
rootMargin: "100px",
});

// 내가 스크랩한 알바폼 목록 조회
// 내가 스크랩한 알폼 목록 조회
const { data, isLoading, error, hasNextPage, fetchNextPage, isFetchingNextPage } = useMyScraps({
limit: SCRAPS_PER_PAGE,
orderBy: orderBy.scrap,
isPublic: filterBy.isPublic,
isRecruiting: filterBy.isRecruiting,
isPublic: isPublic === "true" ? true : isPublic === "false" ? false : undefined,
isRecruiting: isRecruiting === "true" ? true : isRecruiting === "false" ? false : undefined,
});

// 공개 여부 필터 변경 함수
const handlePublicFilter = (selected: string) => {
const option = filterPublicOptions.find((opt) => opt.label === selected);
if (option) {
setFilterBy("isPublic", String(option.value));
const params = new URLSearchParams(searchParams);
if (selected === "전체") {
params.delete("isPublic");
} else {
params.set("isPublic", String(option.value));
}
params.set("tab", "scrap");
router.push(`${pathname}?${params.toString()}`);
}
};

// 모집 여부 필터 변경 함수
const handleRecruitingFilter = (selected: string) => {
const option = filterRecruitingOptions.find((opt) => opt.label === selected);
if (option) {
setFilterBy("isRecruiting", String(option.value));
const params = new URLSearchParams(searchParams);
if (selected === "전체") {
params.delete("isRecruiting");
} else {
params.set("isRecruiting", String(option.value));
}
params.set("tab", "scrap");
router.push(`${pathname}?${params.toString()}`);
}
};

// 현재 필터 상태에 따른 초기값 설정을 위한 함수들
const getInitialPublicValue = (isPublic: boolean) => {
const option = filterPublicOptions.find((opt) => opt.value === isPublic);
const getInitialPublicValue = (isPublic: string | null) => {
if (!isPublic) return "전체";
const option = filterPublicOptions.find((opt) => String(opt.value) === isPublic);
return option?.label || "전체";
};

const getInitialRecruitingValue = (isRecruiting: boolean) => {
const option = filterRecruitingOptions.find((opt) => opt.value === isRecruiting);
const getInitialRecruitingValue = (isRecruiting: string | null) => {
if (!isRecruiting) return "전체";
const option = filterRecruitingOptions.find((opt) => String(opt.value) === isRecruiting);
return option?.label || "전체";
};

Expand All @@ -64,7 +105,7 @@ export default function ScrapsSection() {
}
}, [inView, hasNextPage, fetchNextPage, isFetchingNextPage]);

// 에러 ��태 처리
// 에러 상태 처리
if (error) {
return (
<div className="flex h-[calc(100vh-200px)] items-center justify-center">
Expand All @@ -89,26 +130,26 @@ export default function ScrapsSection() {
<div className="flex items-center gap-2 py-4">
<FilterDropdown
options={filterPublicOptions.map((option) => option.label)}
initialValue={getInitialPublicValue(filterBy.isPublic)}
initialValue={getInitialPublicValue(isPublic)}
onChange={handlePublicFilter}
/>
<FilterDropdown
options={filterRecruitingOptions.map((option) => option.label)}
initialValue={getInitialRecruitingValue(filterBy.isRecruiting)}
initialValue={getInitialRecruitingValue(isRecruiting)}
onChange={handleRecruitingFilter}
/>
</div>
</div>

{/* 스크랩 목록 렌더링 */}
{!data?.pages[0]?.data?.length ? (
{!data?.pages?.[0]?.data?.length ? (
<div className="flex h-[calc(100vh-200px)] items-center justify-center">
<p className="text-grayscale-500">스크랩한 공고가 없습니다.</p>
</div>
) : (
<>
{data.pages.map((page, index) => (
<React.Fragment key={index}>
{data?.pages.map((page) => (
<React.Fragment key={page.nextCursor}>
{page.data.map((scrap: FormListType) => (
<div key={scrap.id} className="rounded-lg border p-4 transition-all hover:border-primary-orange-200">
<h3 className="font-bold">{scrap.title}</h3>
Expand Down
14 changes: 2 additions & 12 deletions src/app/api/forms/[formId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,10 @@ import { cookies } from "next/headers";
import { NextRequest, NextResponse } from "next/server";
import apiClient from "@/lib/apiClient";

// 알바폼 상세 조회
// 알바폼 상세 조회(로그인 안한 유저도 조회 가능)
export async function GET(req: NextRequest, { params }: { params: { formId: string } }) {
try {
const accessToken = cookies().get("accessToken")?.value;

if (!accessToken) {
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
}

const response = await apiClient.get(`/forms/${params.formId}`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
const response = await apiClient.get(`/forms/${params.formId}`);

return NextResponse.json(response.data);
} catch (error: unknown) {
Expand Down
14 changes: 9 additions & 5 deletions src/app/api/forms/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AxiosError } from "axios";
import { cookies } from "next/headers";
import { NextRequest, NextResponse } from "next/server";
import apiClient from "@/lib/apiClient";
import { cleanedParameters } from "@/utils/cleanedParameters";

// 알바폼 생성
export async function POST(req: NextRequest) {
Expand Down Expand Up @@ -44,15 +45,18 @@ export async function GET(req: NextRequest) {

const { searchParams } = new URL(req.url);
const params = {
page: searchParams.get("page"),
cursor: searchParams.get("cursor"),
limit: searchParams.get("limit"),
orderBy: searchParams.get("orderBy"),
keyword: searchParams.get("keyword"),
isRecruiting: searchParams.get("isRecruiting"),
};

// null, undefined, 빈 문자열을 가진 파라미터 제거
const cleanedParams = cleanedParameters(params);

const response = await apiClient.get("/forms", {
params,
headers: {
Authorization: `Bearer ${accessToken}`,
},
params: cleanedParams,
});

return NextResponse.json(response.data);
Expand Down
9 changes: 6 additions & 3 deletions src/app/api/posts/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AxiosError } from "axios";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
import apiClient from "@/lib/apiClient";
import { cleanedParameters } from "@/utils/cleanedParameters";

// 게시글 목록 조회 API
export async function GET(request: Request) {
Expand All @@ -11,14 +12,16 @@ export async function GET(request: Request) {
const params = {
cursor: searchParams.get("cursor"), // 페이지네이션 커서
limit: searchParams.get("limit"), // 한 페이지당 항목 수
keyword: searchParams.get("keyword"), // 검색 키워드
orderBy: searchParams.get("orderBy"), // 정렬 기준
category: searchParams.get("category"), // 카테고리
keyword: searchParams.get("keyword"), // 검색 키워드
};

// null, undefined, 빈 문자열을 가진 파라미터 제거
const cleanedParams = cleanedParameters(params);

// 게시글 목록 조회 요청
const response = await apiClient.get("/posts", {
params,
params: cleanedParams,
});

return NextResponse.json(response.data);
Expand Down
6 changes: 5 additions & 1 deletion src/app/api/users/me/applications/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AxiosError } from "axios";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
import apiClient from "@/lib/apiClient";
import { cleanedParameters } from "@/utils/cleanedParameters";

// 내가 지원한 알바폼 목록 조회 API
export async function GET(request: Request) {
Expand All @@ -22,12 +23,15 @@ export async function GET(request: Request) {
keyword: searchParams.get("keyword"), // 검색 키워드
};

// null, undefined, 빈 문자열을 가진 파라미터 제거
const cleanedParams = cleanedParameters(params);

// 지원 목록 조회 요청
const response = await apiClient.get("/users/me/applications", {
headers: {
Authorization: `Bearer ${accessToken}`,
},
params,
params: cleanedParams,
});

return NextResponse.json(response.data);
Expand Down
12 changes: 7 additions & 5 deletions src/app/api/users/me/comments/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AxiosError } from "axios";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
import apiClient from "@/lib/apiClient";
import { cleanedParameters } from "@/utils/cleanedParameters";

// 내가 작성한 댓글 목록 조회 API
export async function GET(request: Request) {
Expand All @@ -16,18 +17,19 @@ export async function GET(request: Request) {
// URL 쿼리 파라미터 파싱
const { searchParams } = new URL(request.url);
const params = {
cursor: searchParams.get("cursor"), // 페이지네이션 커서
limit: searchParams.get("limit"), // 한 페이지당 항목 수
keyword: searchParams.get("keyword"), // 검색 키워드
orderBy: searchParams.get("orderBy"), // 정렬 기준
page: searchParams.get("page"), // 페이지네이션 커서
pageSize: searchParams.get("pageSize"), // 한 페이지당 항목 수
};

// null, undefined, 빈 문자열을 가진 파라미터 제거
const cleanedParams = cleanedParameters(params);

// 내가 작성한 댓글 목록 조회 요청
const response = await apiClient.get("/users/me/comments", {
headers: {
Authorization: `Bearer ${accessToken}`,
},
params,
params: cleanedParams,
});

return NextResponse.json(response.data);
Expand Down
6 changes: 5 additions & 1 deletion src/app/api/users/me/forms/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AxiosError } from "axios";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
import apiClient from "@/lib/apiClient";
import { cleanedParameters } from "@/utils/cleanedParameters";

// 내가 생성한 알바폼 목록 조회 API
export async function GET(request: Request) {
Expand All @@ -24,12 +25,15 @@ export async function GET(request: Request) {
isRecruiting: searchParams.get("isRecruiting"), // 모집 중 여부
};

// null, undefined, 빈 문자열을 가진 파라미터 제거
const cleanedParams = cleanedParameters(params);

// 알바폼 목록 조회 요청
const response = await apiClient.get("/users/me/forms", {
headers: {
Authorization: `Bearer ${accessToken}`,
},
params,
params: cleanedParams,
});

return NextResponse.json(response.data);
Expand Down
10 changes: 6 additions & 4 deletions src/app/api/users/me/posts/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AxiosError } from "axios";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
import apiClient from "@/lib/apiClient";
import { cleanedParameters } from "@/utils/cleanedParameters";

// 내가 작성한 게시글 목록 조회 API
export async function GET(request: Request) {
Expand All @@ -18,17 +19,18 @@ export async function GET(request: Request) {
const params = {
cursor: searchParams.get("cursor"), // 페이지네이션 커서
limit: searchParams.get("limit"), // 한 페이지당 항목 수
keyword: searchParams.get("keyword"), // 검색 키워드
orderBy: searchParams.get("orderBy"), // 정렬 기준 (최신순, 좋아요순 등)
category: searchParams.get("category"), // 게시글 카테고리
orderBy: searchParams.get("orderBy"), // 정렬 기준 (최신순, 댓글수 순, 좋아요순 등)
};

// null, undefined, 빈 문자열을 가진 파라미터 제거
const cleanedParams = cleanedParameters(params);

// 내가 작성한 게시글 목록 조회 요청
const response = await apiClient.get("/users/me/posts", {
headers: {
Authorization: `Bearer ${accessToken}`,
},
params,
params: cleanedParams,
});

return NextResponse.json(response.data);
Expand Down
Loading
Loading