Skip to content
Closed
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
137 changes: 137 additions & 0 deletions app/(afterLogin)/mypage/MypageActivity.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
"use client";

import { useRouter, useSearchParams } from "next/navigation";

import {
LikedArticleCard,
MyArticleCard,
} from "@/components/mypage/articleCard";
import { PaginationButton } from "@/components/pagination-button/pagination-button";
import { toasts } from "@/components/shared/toast";
import { useLikedPrompts, useUserPrompts } from "@/hooks/use-mypage";
import { cn } from "@/lib/utils";

export default function MypageActivitySection() {
const router = useRouter();
const searchParams = useSearchParams();

// 1. 현재 탭 및 페이지 상태 관리
const rawSub = searchParams.get("sub");
const activeSub: "liked" | "posted" =
rawSub === "posted" ? "posted" : "liked";
const currentPage = Number(searchParams.get("page")) || 0;
const userId = 1; // 실제로는 인증 정보나 프로필 훅에서 가져온 ID 사용

const { data: likedData, isLoading: isLikedLoading } = useLikedPrompts(
userId,
currentPage
);
const { data: postedData, isLoading: isPostedLoading } = useUserPrompts({
userId,
page: currentPage,
});

const handleSubTabChange = (sub: "liked" | "posted") => {
const params = new URLSearchParams(searchParams.toString());
params.set("sub", sub);
params.set("page", "0");
router.push(`/mypage?${params.toString()}`, { scroll: false });
};

const handleCopy = async (e: React.MouseEvent, content: string) => {
e.stopPropagation();
try {
await navigator.clipboard.writeText(content);
toasts.success("프롬프트가 클립보드에 복사되었습니다!");
} catch {
alert("복사에 실패했습니다.");
}
};
const handlePageChange = (page: number) => {
const params = new URLSearchParams(searchParams.toString());
params.set("page", page.toString());
router.push(`/mypage?${params.toString()}`, { scroll: false });
};

const handleCardClick = (id: number) => {
router.push(`/prompts/${id}`);
};

// 현재 활성화된 데이터와 로딩 상태 결정
const currentData = activeSub === "liked" ? likedData : postedData;
const isLoading = activeSub === "liked" ? isLikedLoading : isPostedLoading;

return (
<div className="flex flex-col gap-6 w-full">
{/* 서브 탭 메뉴 */}
<div className="flex w-full bg-gray-0 border border-gray-100 rounded-[8px] overflow-hidden p-1 shadow-sm">
<button
onClick={() => handleSubTabChange("liked")}
className={cn(
"flex-1 py-2.5 label-medium !font-bold transition-all rounded-[6px]",
activeSub === "liked"
? "bg-blue-600 text-gray-0 shadow-sm"
: "text-gray-500 hover:bg-gray-50"
)}
>
좋아요한 프롬프트
</button>
<button
onClick={() => handleSubTabChange("posted")}
className={cn(
"flex-1 py-2.5 label-medium !font-bold transition-all rounded-[6px]",
activeSub === "posted"
? "bg-blue-600 text-gray-0 shadow-sm"
: "text-gray-500 hover:bg-gray-50"
)}
>
게시한 프롬프트
</button>
</div>

{/* 리스트 영역 */}
<div className="flex flex-col gap-4">
{isLoading ? (
<div className="py-20 text-center text-gray-400">로딩 중...</div>
) : currentData?.content.length === 0 ? (
<div className="py-20 text-center text-gray-400">
{activeSub === "liked"
? "좋아요한 프롬프트가 없습니다."
: "게시한 프롬프트가 없습니다."}
</div>
) : (
currentData?.content.map((prompt) =>
activeSub === "liked" ? (
<LikedArticleCard
key={prompt.promptId}
title={prompt.title}
content={prompt.description}
onCopy={(e) => handleCopy(e, prompt.description)}
onClick={() => handleCardClick(prompt.promptId)}
/>
) : (
<MyArticleCard
key={prompt.promptId}
title={prompt.title}
content={prompt.description}
createdAt={prompt.createdAt}
onClick={() => handleCardClick(prompt.promptId)}
/>
)
)
)}
</div>

{/* 단순 페이지네이션 (필요 시 추가) */}
{!isLoading && (currentData?.pageInfo.totalPages ?? 0) > 1 && (
<div className="flex justify-center gap-2 mt-4">
<PaginationButton
currentPage={currentPage}
totalPages={currentData?.pageInfo.totalPages ?? 1}
onPageChange={handlePageChange}
/>
</div>
)}
</div>
);
}
96 changes: 96 additions & 0 deletions app/(afterLogin)/mypage/MypageLeftSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"use client";
import { useQueryClient } from "@tanstack/react-query";
import Cookies from "js-cookie";
import { useRouter } from "next/navigation";

import UserProfile from "@/components/mypage/user-profile";
import { BaseButton } from "@/components/shared/button";
import { toasts } from "@/components/shared/toast";
import { useUserProfile } from "@/hooks/use-mypage";
import { deleteUserAccount, postLogout } from "@/queries/api/auth";

export default function MypageLeftSection() {
const router = useRouter();
const queryClient = useQueryClient();

const { data: userData, isLoading } = useUserProfile();

const handleLogout = async () => {
if (confirm("로그아웃 하시겠습니까?")) {
try {
await postLogout();

Cookies.remove("accessToken");
Cookies.remove("refreshToken");

queryClient.clear();
toasts.success("로그아웃 되었습니다.");
router.push("/");
router.refresh();
} catch (error) {
Cookies.remove("accessToken");
Cookies.remove("refreshToken");
console.error("Logout failed:", error);
//TODO: error 컴포넌트가 생기면 사용자 피드백 주기
//toasts.error("로그아웃 처리 중 오류가 발생했습니다.");
}
}
};

const handleWithdraw = async () => {
const isConfirmed = confirm(
"정말로 탈퇴하시겠습니까?\n탈퇴 시 모든 데이터가 삭제되며 복구할 수 없습니다."
);

if (!isConfirmed || !userData) return;

try {
await deleteUserAccount(userData.basicInfo.uid);

Cookies.remove("refreshToken");
Cookies.remove("accessToken");
queryClient.clear();

toasts.success("회원 탈퇴가 완료되었습니다. 이용해 주셔서 감사합니다.");
router.push("/");
router.refresh();
} catch (error) {
console.error("Withdrawal failed:", error);
//toasts.error("탈퇴 처리 중 오류가 발생했습니다. 고객센터에 문의해주세요.");
}
};

// 로딩 및 에러 처리
if (isLoading) return <div className="p-10 text-center">로딩 중...</div>;
if (!userData)
return <div className="p-10 text-center">유저 정보가 없습니다.</div>;

const { basicInfo } = userData;

return (
<div className="flex flex-col gap-5">
<UserProfile
nickname={basicInfo.nickname}
email={basicInfo.email}
profileImage={""}
provider={basicInfo.provider}
introduction={basicInfo.introduction ?? "반갑습니다!"}
/>

<BaseButton
onClick={handleLogout}
variant={"secondary"}
className="bg-gray-0"
>
로그아웃
</BaseButton>

<button
onClick={handleWithdraw}
className="text-error label-small opacity-60 hover:opacity-100 transition-opacity"
>
계정 탈퇴
</button>
</div>
);
}
Loading