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
22 changes: 8 additions & 14 deletions app/(main)/home/[id]/quiz/result/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"use client";

import { Suspense, useEffect, useState } from "react";
import { Suspense } from "react";
import { useParams, useSearchParams, useRouter } from "next/navigation";
import Link from "next/link";
import { ArrowLeft, Loader2, AlertCircle } from "lucide-react";
import { useQuery } from "@tanstack/react-query";
import { QuizResult } from "@/components/features/home/quiz/QuizResult";
import { fetchMyQuizHistoryDetail } from "@/lib/mock/my-page-wrong-quizzes";
import type { QuizHistoryDetail } from "@/types/myPage";
import { getQuizHistoryDetail } from "@/lib/api/endpoints/quizzes";

function QuizResultContent() {
const params = useParams<{ id: string }>();
Expand All @@ -16,17 +16,11 @@ function QuizResultContent() {
const contentId = params.id;
const attemptId = searchParams.get("attemptId");

const [detail, setDetail] = useState<QuizHistoryDetail | null>(null);
const [isLoading, setIsLoading] = useState(!!attemptId);
const [isError, setIsError] = useState(!attemptId);

useEffect(() => {
if (!attemptId) return;
fetchMyQuizHistoryDetail(attemptId)
.then(setDetail)
.catch(() => setIsError(true))
.finally(() => setIsLoading(false));
}, [attemptId]);
const { data: detail, isLoading, isError } = useQuery({
queryKey: ["quizHistoryDetail", attemptId],
queryFn: () => getQuizHistoryDetail(attemptId!),
enabled: !!attemptId,
});

if (isLoading) {
return (
Expand Down
57 changes: 21 additions & 36 deletions components/features/my-page/quizzes/WrongQuizListWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,60 +1,37 @@
"use client";

import { useEffect, useState } from "react";
import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { WrongQuizList } from "./WrongQuizList";
import { WrongQuizListItemSkeleton } from "./WrongQuizListItemSkeleton";
import { fetchMyQuizHistory } from "@/lib/mock/my-page-wrong-quizzes";
import type { MyPageQuizHistoryResponse } from "@/types/myPage";
import { getMyQuizHistory } from "@/lib/api/endpoints/users";

type SortOrder = "newest" | "oldest";

export function WrongQuizListWrapper() {
const [data, setData] = useState<MyPageQuizHistoryResponse | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
const [sort, setSort] = useState<SortOrder>("newest");
const [page, setPage] = useState(0);

useEffect(() => {
let cancelled = false;

fetchMyQuizHistory({ sort, page, size: 10, wrongOnly: true })
.then((res) => {
if (!cancelled) setData(res);
})
.catch(() => {
if (!cancelled) setIsError(true);
})
.finally(() => {
if (!cancelled) setIsLoading(false);
});

return () => {
cancelled = true;
};
}, [sort, page]);
const { data, isLoading, isError } = useQuery({
queryKey: ["myQuizHistory", sort, page],
queryFn: () =>
getMyQuizHistory({
passed: false,
sort,
page,
size: 10,
}),
});

const handleSortChange = (value: SortOrder) => {
setSort(value);
setPage(0);
setIsLoading(true);
setIsError(false);
};

const handlePageChange = (nextPage: number) => {
setPage(nextPage - 1);
setIsLoading(true);
setIsError(false);
};

if (isError) {
return (
<p className="text-sm text-muted-foreground">
불러오는 중 오류가 발생했습니다.
</p>
);
}

if (isLoading) {
return (
<div className="divide-y divide-border">
Expand All @@ -65,6 +42,14 @@ export function WrongQuizListWrapper() {
);
}

if (isError) {
return (
<p className="py-10 text-center text-sm text-muted-foreground">
불러오는 중 오류가 발생했습니다.
</p>
);
}

return (
<WrongQuizList
quizzes={data?.content ?? []}
Expand Down
41 changes: 18 additions & 23 deletions components/features/my-page/quizzes/WrongQuizSection.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,25 @@
"use client";

import { useEffect, useState } from "react";
import Link from "next/link";
import { ArrowRight } from "lucide-react";
import { Skeleton } from "@/components/ui/skeleton";
import { useQuery } from "@tanstack/react-query";
import { WrongQuizCard } from "./WrongQuizCard";
import { fetchMyWrongQuizzesPreview } from "@/lib/mock/my-page-wrong-quizzes";
import type { MyPageQuizHistory } from "@/types/myPage";
import { getMyQuizHistory } from "@/lib/api/endpoints/users";

export function WrongQuizSection() {
const [quizzes, setQuizzes] = useState<MyPageQuizHistory[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
const { data, isLoading, isError } = useQuery({
queryKey: ["myQuizHistoryPreview"],
queryFn: () =>
getMyQuizHistory({
passed: false,
page: 0,
size: 4,
sort: "newest",
}),
});

useEffect(() => {
fetchMyWrongQuizzesPreview(4)
.then((data) => {
setQuizzes(data);
})
.catch(() => {
setIsError(true);
})
.finally(() => {
setIsLoading(false);
});
}, []);
const quizzes = data?.content ?? [];

return (
<section>
Expand All @@ -39,11 +34,7 @@ export function WrongQuizSection() {
</Link>
</div>

{isError ? (
<p className="text-sm text-muted-foreground">
불러오는 중 오류가 발생했습니다.
</p>
) : isLoading ? (
{isLoading ? (
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4">
{Array.from({ length: 4 }).map((_, i) => (
<div
Expand All @@ -59,6 +50,10 @@ export function WrongQuizSection() {
</div>
))}
</div>
) : isError ? (
<p className="text-sm text-muted-foreground">
불러오는 중 오류가 발생했습니다.
</p>
) : quizzes.length === 0 ? (
<p className="text-sm text-muted-foreground">틀린 퀴즈가 없습니다.</p>
) : (
Expand Down
43 changes: 13 additions & 30 deletions components/features/my-page/scraps/ScrappedPostsList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useEffect, useState } from "react";
import { useState } from "react";
import { ChevronDown, Search } from "lucide-react";
import { Skeleton } from "@/components/ui/skeleton";
import {
Expand All @@ -10,10 +10,10 @@ import {
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useQuery } from "@tanstack/react-query";
import { ScrappedPostListItem } from "./ScrappedPostListItem";
import { MyPagePagination } from "../MyPagePagination";
import { fetchMyScraps } from "@/lib/mock/my-page-scraps";
import type { MyPageScrapResponse } from "@/types/myPage";
import { getMyScraps } from "@/lib/api/endpoints/users";

type SortOrder = "newest" | "oldest";

Expand All @@ -32,50 +32,33 @@ function ListItemSkeleton() {
}

export function ScrappedPostsList() {
const [data, setData] = useState<MyPageScrapResponse | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
const [query, setQuery] = useState("");
const [sort, setSort] = useState<SortOrder>("newest");
const [page, setPage] = useState(0);

useEffect(() => {
let cancelled = false;

fetchMyScraps({ q: query || undefined, sort, page, size: 10 })
.then((res) => {
if (!cancelled) setData(res);
})
.catch(() => {
if (!cancelled) setIsError(true);
})
.finally(() => {
if (!cancelled) setIsLoading(false);
});

return () => {
cancelled = true;
};
}, [query, sort, page]);
const { data, isLoading, isError } = useQuery({
queryKey: ["myScraps", query.trim(), sort, page],
queryFn: () =>
getMyScraps({
q: query.trim() || undefined,
sort,
page,
size: 10,
}),
});

const handleQueryChange = (value: string) => {
setQuery(value);
setPage(0);
setIsLoading(true);
setIsError(false);
};

const handleSortChange = (value: string) => {
setSort(value as SortOrder);
setPage(0);
setIsLoading(true);
setIsError(false);
};

const handlePageChange = (nextPage: number) => {
setPage(nextPage - 1);
setIsLoading(true);
setIsError(false);
};

if (isLoading) {
Expand Down
25 changes: 14 additions & 11 deletions components/features/my-page/scraps/ScrappedPostsSection.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
"use client";

import { useEffect, useState } from "react";
import Link from "next/link";
import { ArrowRight } from "lucide-react";
import { Skeleton } from "@/components/ui/skeleton";
import { useQuery } from "@tanstack/react-query";
import { ScrappedPostCard } from "./ScrappedPostCard";
import { fetchMyScrapsPreview } from "@/lib/mock/my-page-scraps";
import type { MyPageScrap } from "@/types/myPage";
import { getMyScraps } from "@/lib/api/endpoints/users";

export function ScrappedPostsSection() {
const [scraps, setScraps] = useState<MyPageScrap[]>([]);
const [isLoading, setIsLoading] = useState(true);
const { data, isLoading, isError } = useQuery({
queryKey: ["myScrapsPreview"],
queryFn: () =>
getMyScraps({
page: 0,
size: 4,
sort: "newest",
}),
});

useEffect(() => {
fetchMyScrapsPreview(4).then((res) => {
setScraps(res.content);
setIsLoading(false);
});
}, []);
const scraps = data?.content ?? [];

return (
<section>
Expand Down Expand Up @@ -51,6 +52,8 @@ export function ScrappedPostsSection() {
</div>
))}
</div>
) : isError ? (
<p className="text-sm text-muted-foreground">불러오는 중 오류가 발생했습니다.</p>
) : scraps.length === 0 ? (
<p className="text-sm text-muted-foreground">스크랩한 글이 없습니다.</p>
) : (
Expand Down
8 changes: 8 additions & 0 deletions lib/api/endpoints/quizzes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
QuizSubmitResponse,
QuizLevel,
} from "@/types/quiz";
import type { QuizHistoryDetail } from "@/types/myPage";

export const quizzesEndpoints = {
/**
Expand Down Expand Up @@ -49,3 +50,10 @@ export const quizzesEndpoints = {
.then((r) => r.data);
},
};

export async function getQuizHistoryDetail(
attemptId: string,
): Promise<QuizHistoryDetail> {
const res = await apiClient.get(`/quiz-history/${attemptId}`);
return res.data.data;
}
21 changes: 21 additions & 0 deletions lib/api/endpoints/users.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { apiClient } from "../client";
import type { ApiResponse } from "@/types/api";
import type { UserProfileResponse } from "@/types/userProfile";
import type { MyPageScrapResponse, MyPageQuizHistoryResponse } from "@/types/myPage";

export interface UpdateMeRequest {
nickname?: string;
Expand Down Expand Up @@ -47,3 +48,23 @@ export const usersEndpoints = {
.then((r) => r.data);
},
};

export async function getMyQuizHistory(params?: {
sort?: "newest" | "oldest";
page?: number;
size?: number;
passed?: boolean;
}): Promise<MyPageQuizHistoryResponse> {
const res = await apiClient.get("/users/me/quiz-history", { params });
return res.data.data;
}

export async function getMyScraps(params?: {
q?: string;
sort?: "newest" | "oldest";
page?: number;
size?: number;
}): Promise<MyPageScrapResponse> {
const res = await apiClient.get("/users/me/scraps", { params });
return res.data.data;
}
Loading
Loading