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:
Wonhee Han
2026-08-20 22:58:47 +09:00
parent 765f670ef0
commit dd01a8ec69
8 changed files with 136 additions and 22 deletions
+22 -3
View File
@@ -46,6 +46,8 @@ export function TaskDetail({
// Mobile Bottom-Sheet: swipe down to close
const touchStartY = useRef<number | null>(null);
const touchStartX = useRef<number | null>(null);
const gestureDirection = useRef<"vertical" | "horizontal" | null>(null);
const [panelTranslateY, setPanelTranslateY] = useState(0);
const [isMobile, setIsMobile] = useState(false);
@@ -306,15 +308,30 @@ export function TaskDetail({
[isDemo, onDemoUpdateTask, onUpdate, subtasks, task]
);
// Touch handlers: 모바일에서 아래로 스와이프 → 패널 닫기
// Touch handlers: 모바일에서 아래로 스와이프 → 패널 닫기 (스크롤과 충돌 방지)
const handleTouchStart = (e: React.TouchEvent) => {
// 스와이프 핸들 또는 detail-header 영역에서만 드래그 닫기 허용
const target = e.target as HTMLElement;
const isHandle = target.closest(".mobile-swipe-handle") || target.closest(".detail-header");
if (!isHandle || !isMobile) return;
touchStartY.current = e.touches[0].clientY;
touchStartX.current = e.touches[0].clientX;
gestureDirection.current = null;
};
const handleTouchMove = (e: React.TouchEvent) => {
if (touchStartY.current === null) return;
if (touchStartY.current === null || touchStartX.current === null) return;
const deltaY = e.touches[0].clientY - touchStartY.current;
if (deltaY > 0) {
const deltaX = e.touches[0].clientX - touchStartX.current;
// 초기 제스처 방향 결정 (첫 10px 이동 기준)
if (gestureDirection.current === null && (Math.abs(deltaX) > 10 || Math.abs(deltaY) > 10)) {
gestureDirection.current = Math.abs(deltaY) > Math.abs(deltaX) ? "vertical" : "horizontal";
}
// 수직 드래그일 때만 패널 이동
if (gestureDirection.current === "vertical" && deltaY > 0) {
e.preventDefault();
setPanelTranslateY(deltaY);
}
};
@@ -325,6 +342,8 @@ export function TaskDetail({
}
setPanelTranslateY(0);
touchStartY.current = null;
touchStartX.current = null;
gestureDirection.current = null;
};
const completedCount = subtasks.filter((s) => s.completed).length;