Security+UX: Add DOMPurify XSS protection, security headers, admin auth gate, import file size limit, due date color coding, swipe/scroll conflict prevention

This commit is contained in:
2026-08-20 22:58:47 +09:00
parent 36c8d690d9
commit 8212f16d28
8 changed files with 136 additions and 22 deletions
+25 -1
View File
@@ -1,6 +1,8 @@
"use client";
"use client";
import React, { useState, useEffect } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useSession } from "next-auth/react";
import { useI18n } from "@/lib/i18n";
import { getDemoStore } from "@/lib/mockData";
@@ -46,17 +48,39 @@ const INITIAL_ADMIN_USERS: AdminUser[] = [
export default function AdminPage() {
const { t } = useI18n();
const router = useRouter();
const { data: session, status } = useSession();
const [users, setUsers] = useState<AdminUser[]>(INITIAL_ADMIN_USERS);
const [search, setSearch] = useState("");
const [totalTasks, setTotalTasks] = useState(67);
const [totalLists, setTotalLists] = useState(9);
// 인증 게이트: 비로그인 시 /login으로 리디렉션
useEffect(() => {
if (status === "unauthenticated") {
router.replace("/login");
}
}, [status, router]);
useEffect(() => {
const store = getDemoStore();
if (store.tasks) setTotalTasks(store.tasks.length);
if (store.lists) setTotalLists(store.lists.length);
}, []);
// 로딩 중 스피너
if (status === "loading" || status === "unauthenticated") {
return (
<div style={{ minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center", background: "var(--bg-primary)" }}>
<div style={{ textAlign: "center" }}>
<div style={{ fontSize: 32, marginBottom: 12 }}>🔒</div>
<p style={{ color: "var(--text-secondary)" }}>Checking access...</p>
</div>
</div>
);
}
const toggleRole = (id: string) => {
setUsers((prev) =>
prev.map((u) => (u.id === id ? { ...u, role: u.role === "ADMIN" ? "USER" : "ADMIN" } : u))