Skip to content

Commit eb21dac

Browse files
[qa] fix : QnA XSS sanitize 누락, 운영진 role 판별 오류, 필터 상태 불일치 수정
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 5c58fb5 commit eb21dac

6 files changed

Lines changed: 29 additions & 7 deletions

File tree

src/app/(class-lesson)/class/[slug]/lesson/[id]/_components/lesson-qna-detail-modal.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use client';
22

3+
import DOMPurify from 'dompurify';
34
import {
45
Heart,
56
HelpCircle,
@@ -79,11 +80,14 @@ function HtmlContent({ html }: { html: string }) {
7980
);
8081
}
8182

83+
const sanitized =
84+
typeof window !== 'undefined' ? DOMPurify.sanitize(html) : html;
8285
return (
8386
<div className="tiptap-editor">
8487
<div
8588
className="tiptap font-designer-16r leading-relaxed text-gray-800"
86-
dangerouslySetInnerHTML={{ __html: html }}
89+
// biome-ignore lint/security/noDangerouslySetInnerHtml: DOMPurify sanitized
90+
dangerouslySetInnerHTML={{ __html: sanitized }}
8791
/>
8892
</div>
8993
);

src/app/(class-lesson)/class/[slug]/lesson/[id]/page.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,11 @@ export default function LessonPage({
111111
const isLastLesson = useMemo(() => {
112112
const allLessons = drawerChapters.flatMap((c) => c.lessons);
113113
if (allLessons.length === 0) return false;
114-
return allLessons[allLessons.length - 1].lessonId === lessonId;
114+
const isLast = allLessons.at(-1)?.lessonId === lessonId;
115+
const allOthersCompleted = allLessons.every(
116+
(l) => l.lessonId === lessonId || l.status === 'COMPLETED',
117+
);
118+
return isLast && allOthersCompleted;
115119
}, [drawerChapters, lessonId]);
116120

117121
const alreadySubmitted = lesson?.retrospectiveSubmitted ?? false;

src/app/(landing)/class/[slug]/(learning)/_components/feed-tab.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { ChevronDown } from 'lucide-react';
44
import dynamic from 'next/dynamic';
55
import Link from 'next/link';
66
import { useParams } from 'next/navigation';
7-
import { useMemo, useState } from 'react';
7+
import { useEffect, useMemo, useState } from 'react';
88
import { cn } from '@/components/common/ui/(shadcn)/lib/utils';
99
import { PlanSelectionModal } from '@/components/pages/class/plan-selection-modal';
1010
import { useAuth } from '@/features/auth/model/use-auth';
@@ -70,6 +70,13 @@ export function FeedTab() {
7070
});
7171
const hasOperatorPick = (operatorPickProbe?.totalCount ?? 0) > 0;
7272

73+
useEffect(() => {
74+
if (!hasOperatorPick && filter === '운영자 PICK') {
75+
setFilter('전체');
76+
setCurrentPage(0);
77+
}
78+
}, [hasOperatorPick, filter]);
79+
7380
const filterOptions: FeedFilter[] = [
7481
'전체',
7582
...(hasOperatorPick ? (['운영자 PICK'] as FeedFilter[]) : []),

src/app/(landing)/class/[slug]/(learning)/_components/qna-tab.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ export function QnaTab() {
200200
className={cn(
201201
'flex items-center gap-50 rounded-full border px-250 py-125 font-designer-14m',
202202
q.answerStatus === 'ANSWERED'
203-
? 'border-[#02c76e] bg-[#dafbe7] text-[#02c76e]'
203+
? 'border-qna-answered bg-qna-answered-bg text-qna-answered'
204204
: 'border-border-subtle text-gray-500',
205205
)}
206206
>

src/app/(landing)/class/[slug]/(learning)/feed/[id]/page.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,8 @@ export default function FeedDetailPage({
432432
{/* Comments */}
433433
<div className="mt-400 space-y-300">
434434
{(commentsData?.comments ?? []).map((c) => {
435-
const isOperator = c.author.role === '운영진';
435+
const isOperator =
436+
c.author.role === 'MANAGER' || c.author.role === 'ADMIN';
436437
const isCommentAuthor =
437438
memberId !== undefined && c.author.memberId === memberId;
438439
const isMenuOpen = commentMenuOpenId === c.commentId;
@@ -560,7 +561,9 @@ export default function FeedDetailPage({
560561
{c.replies.length > 0 && (
561562
<div className="ml-575 mt-200 space-y-200">
562563
{c.replies.map((r) => {
563-
const isReplyOperator = r.author.role === '운영진';
564+
const isReplyOperator =
565+
r.author.role === 'MANAGER' ||
566+
r.author.role === 'ADMIN';
564567
const contentParts = r.content.startsWith('@')
565568
? (() => {
566569
const spaceIdx = r.content.indexOf(' ');

src/app/(landing)/class/[slug]/(learning)/qa/[id]/page.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use client';
22

3+
import DOMPurify from 'dompurify';
34
import {
45
ArrowLeft,
56
Heart,
@@ -56,11 +57,14 @@ function HtmlContent({ html }: { html: string }) {
5657
);
5758
}
5859

60+
const sanitized =
61+
typeof window !== 'undefined' ? DOMPurify.sanitize(html) : html;
5962
return (
6063
<div className="tiptap-editor">
6164
<div
6265
className="tiptap font-designer-16r leading-relaxed text-gray-800"
63-
dangerouslySetInnerHTML={{ __html: html }}
66+
// biome-ignore lint/security/noDangerouslySetInnerHtml: DOMPurify sanitized
67+
dangerouslySetInnerHTML={{ __html: sanitized }}
6468
/>
6569
</div>
6670
);

0 commit comments

Comments
 (0)