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
+23 -13
View File
@@ -1,4 +1,4 @@
"use client";
"use client";
import React, { useState, useEffect, useRef, useCallback } from "react";
import { useI18n } from "@/lib/i18n";
import { ContextMenu, MenuItem } from "@/components/ui/ContextMenu";
@@ -97,15 +97,18 @@ function RecursiveTaskItem({
}
}, [addingSubtask]);
const formatDate = (d: string | null) => {
// 마감일 상태 반환: label + 색상
const getDueDateInfo = (d: string | null): { label: string; color: string } | null => {
if (!d) return null;
const date = new Date(d);
const now = new Date();
const isToday = date.toDateString() === now.toDateString();
const isTomorrow = date.toDateString() === new Date(now.getTime() + 86400000).toDateString();
if (isToday) return t("today");
if (isTomorrow) return t("tomorrow");
return date.toLocaleDateString(lang === "ko" ? "ko-KR" : lang === "ja" ? "ja-JP" : "en-US", { month: "short", day: "numeric" });
const isPast = date < now && !isToday;
if (isToday) return { label: t("today"), color: "#EF4444" };
if (isTomorrow) return { label: t("tomorrow"), color: "#F97316" };
if (isPast) return { label: date.toLocaleDateString(lang === "ko" ? "ko-KR" : lang === "ja" ? "ja-JP" : "en-US", { month: "short", day: "numeric" }), color: "#DC2626" };
return { label: date.toLocaleDateString(lang === "ko" ? "ko-KR" : lang === "ja" ? "ja-JP" : "en-US", { month: "short", day: "numeric" }), color: "var(--text-tertiary)" };
};
const priorityLabels = [t("priorityNone"), t("priorityLow"), t("priorityMedium"), t("priorityHigh")];
@@ -261,14 +264,21 @@ function RecursiveTaskItem({
title={priorityLabels[task.priority]}
/>
)}
{task.dueDate && !isTrashMode && (
<span className={`task-due${isOverdue(task.dueDate) && !task.completed ? " overdue" : ""}`}>
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<rect x="3" y="4" width="18" height="18" rx="2" /><line x1="16" y1="2" x2="16" y2="6" /><line x1="8" y1="2" x2="8" y2="6" /><line x1="3" y1="10" x2="21" y2="10" />
</svg>
{formatDate(task.dueDate)}
</span>
)}
{task.dueDate && !isTrashMode && (() => {
const dueDateInfo = getDueDateInfo(task.dueDate);
if (!dueDateInfo) return null;
return (
<span
className="task-due"
style={{ color: task.completed ? undefined : dueDateInfo.color, fontWeight: (dueDateInfo.color !== "var(--text-tertiary)" && !task.completed) ? 600 : undefined }}
>
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<rect x="3" y="4" width="18" height="18" rx="2" /><line x1="16" y1="2" x2="16" y2="6" /><line x1="8" y1="2" x2="8" y2="6" /><line x1="3" y1="10" x2="21" y2="10" />
</svg>
{dueDateInfo.label}
</span>
);
})()}
{totalChildren > 0 && !isTrashMode && (
<span
className="task-sub-count"