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:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user