마이페이지, 리포트, 메일인증
CI / test (push) Failing after 13m23s
CI / e2e (push) Has been cancelled
CI / pr_and_merge (push) Has been cancelled

This commit is contained in:
2025-12-15 21:59:43 +09:00
parent a8e1a4a960
commit 87cdc1868b
30 changed files with 5488 additions and 16 deletions
+50 -1
View File
@@ -3,7 +3,7 @@
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";
import { FolderKanban, LayoutDashboard, ListChecks, SunMoon } from "lucide-react";
import { BarChart2, FolderKanban, LayoutDashboard, ListChecks, SunMoon } from "lucide-react";
import { useAppLocale } from "@/features/i18n/LocaleProvider";
import { getDashboardMessages } from "@/features/i18n/messages/dashboard";
@@ -46,6 +46,7 @@ export default function DashboardPage() {
const [status, setStatus] = useState<PageStatus>("idle");
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [emailVerified, setEmailVerified] = useState<boolean | null>(null);
// 마운트 시 대시보드 개요 데이터를 불러온다.
useEffect(() => {
@@ -103,6 +104,31 @@ export default function DashboardPage() {
};
}, [router]);
// 마운트 시 현재 사용자 이메일 인증 여부를 확인한다.
useEffect(() => {
let cancelled = false;
const loadMe = async () => {
try {
const res = await fetch("/api/auth/me");
if (cancelled || !res.ok) return;
const data = (await res.json().catch(() => null)) as any;
if (!cancelled && data) {
setEmailVerified(Boolean(data.emailVerified));
}
} catch {
// ignore – 인증 여부를 알 수 없으면 배너만 표시하지 않는다.
}
};
void loadMe();
return () => {
cancelled = true;
};
}, []);
const safeSummary: SummaryStats =
summary ?? {
totalProjects: 0,
@@ -170,6 +196,13 @@ export default function DashboardPage() {
<ListChecks className="w-4 h-4" aria-hidden="true" />
<span>{t.navSubmissions}</span>
</Link>
<Link
href="/reports"
className="inline-flex items-center gap-1 px-3 py-1.5 rounded-full text-sm font-medium text-slate-700 hover:bg-slate-100 hover:text-sky-700 dark:text-slate-200 dark:hover:bg-slate-800 dark:hover:text-sky-200"
>
<BarChart2 className="w-4 h-4" aria-hidden="true" />
<span>{t.navReports ?? "Reports"}</span>
</Link>
</nav>
<button
type="button"
@@ -191,6 +224,16 @@ export default function DashboardPage() {
</button>
{isMenuOpen && (
<div className="absolute right-0 mt-1 w-32 rounded border border-slate-200 bg-white shadow-lg z-20 dark:border-slate-700 dark:bg-slate-900/95">
<button
type="button"
className="w-full px-3 py-2 text-left text-xs text-slate-700 hover:bg-slate-100 dark:text-slate-200 dark:hover:bg-slate-800"
onClick={() => {
setIsMenuOpen(false);
router.push("/mypage");
}}
>
{t.myPageLabel}
</button>
<button
type="button"
className="w-full px-3 py-2 text-left text-xs text-slate-700 hover:bg-slate-100 dark:text-slate-200 dark:hover:bg-slate-800"
@@ -212,6 +255,12 @@ export default function DashboardPage() {
<p className="text-xs text-red-500 dark:text-red-300">{errorMessage}</p>
)}
{emailVerified === false && (
<p className="text-xs text-amber-700 mb-1 dark:text-amber-300">
{t.emailNotVerifiedBanner}
</p>
)}
{status === "loading" && (
<p className="text-xs text-slate-400">{t.loadingText}</p>
)}