마이페이지, 리포트, 메일인증
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
LayoutDashboard,
|
||||
FolderKanban,
|
||||
SunMoon,
|
||||
BarChart2,
|
||||
} from "lucide-react";
|
||||
import { useAppLocale } from "@/features/i18n/LocaleProvider";
|
||||
import { getDashboardMessages } from "@/features/i18n/messages/dashboard";
|
||||
@@ -39,6 +40,7 @@ export default function ProjectsPage() {
|
||||
const [selectedSlugs, setSelectedSlugs] = useState<string[]>([]);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const [emailVerified, setEmailVerified] = useState<boolean | null>(null);
|
||||
const pageSize = 10;
|
||||
|
||||
useEffect(() => {
|
||||
@@ -85,6 +87,31 @@ export default function ProjectsPage() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 이메일 인증 여부를 확인해 퍼블릭 페이지 링크 노출 여부를 결정한다.
|
||||
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;
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const totalPages = Math.max(1, Math.ceil(projects.length / pageSize));
|
||||
setCurrentPage((prev) => Math.min(prev, totalPages));
|
||||
@@ -241,6 +268,13 @@ export default function ProjectsPage() {
|
||||
<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"
|
||||
@@ -262,6 +296,16 @@ export default function ProjectsPage() {
|
||||
</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"
|
||||
@@ -403,6 +447,15 @@ export default function ProjectsPage() {
|
||||
<Eye className="w-3 h-3" aria-hidden="true" />
|
||||
<span>{p.actionPreview}</span>
|
||||
</Link>
|
||||
{emailVerified !== false && (
|
||||
<Link
|
||||
href={`/p/${encodeURIComponent(project.slug)}`}
|
||||
className="inline-flex items-center gap-1 text-sky-700 hover:text-sky-900 underline-offset-2 hover:underline dark:text-sky-300 dark:hover:text-sky-200"
|
||||
>
|
||||
<Eye className="w-3 h-3" aria-hidden="true" />
|
||||
<span>{t.projectOpenPublicPageLink}</span>
|
||||
</Link>
|
||||
)}
|
||||
<Link
|
||||
href={`/projects/${encodeURIComponent(project.slug)}/submissions`}
|
||||
className="inline-flex items-center gap-1 text-emerald-600 hover:text-emerald-700 underline-offset-2 hover:underline dark:text-emerald-300 dark:hover:text-emerald-100"
|
||||
|
||||
Reference in New Issue
Block a user