Skip to content

Commit e1e853a

Browse files
Merge pull request #663 from code-zero-to-one/fix/qa
QA 수정 — 보안/버그/디자인 개선
2 parents 6694dd5 + aed17bf commit e1e853a

3 files changed

Lines changed: 105 additions & 85 deletions

File tree

e2e/class/lesson-review.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,15 @@ function makeDrawer(): { content: CourseDrawerResponse } {
7070
status: 'IN_PROGRESS',
7171
isCurrentLesson: true,
7272
},
73+
{
74+
lessonId: LESSON_ID + 1,
75+
order: 2,
76+
title: '다음 레슨',
77+
isFree: true,
78+
isLocked: true,
79+
status: 'LOCKED',
80+
isCurrentLesson: false,
81+
},
7382
],
7483
},
7584
],

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

Lines changed: 90 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,16 @@ export default function LessonPage({
4747
const reviewRef = useRef<HTMLDivElement>(null);
4848
const contentRef = useRef<HTMLDivElement>(null);
4949
const topBarRef = useRef<HTMLDivElement>(null);
50+
const leftColRef = useRef<HTMLDivElement>(null);
5051

5152
function scrollToRef(ref: RefObject<HTMLDivElement | null>) {
52-
if (!ref.current) return;
53-
const headerHeight = topBarRef.current?.offsetHeight ?? 64;
53+
if (!ref.current || !leftColRef.current) return;
54+
const container = leftColRef.current;
5455
const top =
55-
ref.current.getBoundingClientRect().top +
56-
window.scrollY -
57-
headerHeight -
58-
16;
59-
window.scrollTo({ top, behavior: 'smooth' });
56+
container.scrollTop +
57+
ref.current.getBoundingClientRect().top -
58+
container.getBoundingClientRect().top;
59+
container.scrollTo({ top, behavior: 'smooth' });
6060
}
6161

6262
function handleTabChange(next: LessonTabValue) {
@@ -200,93 +200,101 @@ export default function LessonPage({
200200
</div>
201201

202202
<div className="mx-auto w-full max-w-[1236px] px-300">
203-
<div className="grid grid-cols-content-sidebar-360 items-start gap-250 pt-500">
203+
<div className="flex items-start gap-250">
204204
{/* LEFT */}
205-
<div className="min-w-0">
206-
<Link
207-
href={`/class/${lesson?.courseSlug ?? slug}/home`}
208-
className="inline-flex items-center gap-125 rounded-full border border-gray-200 bg-background-default px-200 py-100"
209-
>
210-
<ArrowLeft className="h-250 w-250 text-gray-800" />
211-
<span className="font-designer-14m text-gray-1000">
212-
학습 여정 맵 돌아가기
213-
</span>
214-
</Link>
215-
216-
<div className="mt-300 flex items-center justify-between">
217-
<div className="flex items-center gap-200">
218-
<span className="rounded-100 bg-rose-200 px-125 py-25 font-designer-14m text-rose-400">
219-
Lesson {String(lessonId).padStart(2, '0')}
205+
<div className="min-w-0 flex-1 flex flex-col h-[calc(100vh-var(--spacing-800))]">
206+
{/* Fixed header: back link, title, description, tabs — never scrolls */}
207+
<div className="shrink-0 pt-500">
208+
<Link
209+
href={`/class/${lesson?.courseSlug ?? slug}/home`}
210+
className="inline-flex items-center gap-125 rounded-full border border-gray-200 bg-background-default px-200 py-100"
211+
>
212+
<ArrowLeft className="h-250 w-250 text-gray-800" />
213+
<span className="font-designer-14m text-gray-1000">
214+
학습 여정 맵 돌아가기
220215
</span>
221-
<h1 className="font-designer-32b text-gray-800">
222-
{lesson?.title ?? 'AI 처음 만나는 날'}
223-
</h1>
216+
</Link>
217+
218+
<div className="mt-300 flex items-center justify-between">
219+
<div className="flex items-center gap-200">
220+
<span className="rounded-100 bg-rose-200 px-125 py-25 font-designer-14m text-rose-400">
221+
Lesson {String(lessonId).padStart(2, '0')}
222+
</span>
223+
<h1 className="font-designer-32b text-gray-800">
224+
{lesson?.title ?? 'AI 처음 만나는 날'}
225+
</h1>
226+
</div>
227+
<p className="font-designer-16m text-gray-500">
228+
{lesson?.estimatedMinutes
229+
? `약 ${lesson.estimatedMinutes}분 소요`
230+
: ''}
231+
</p>
224232
</div>
225-
<p className="font-designer-16m text-gray-500">
226-
{lesson?.estimatedMinutes
227-
? `약 ${lesson.estimatedMinutes}분 소요`
228-
: ''}
229-
</p>
230-
</div>
231233

232-
{lesson?.description ? (
233-
<p className="mt-150 whitespace-pre-line font-designer-16r text-gray-700">
234-
{lesson.description}
235-
</p>
236-
) : null}
234+
{lesson?.description ? (
235+
<p className="mt-150 whitespace-pre-line font-designer-16r text-gray-700">
236+
{lesson.description}
237+
</p>
238+
) : null}
237239

238-
<div className="sticky top-800 z-20 mt-300 bg-gray-100">
239-
<LessonTabs value={tab} onChange={handleTabChange} />
240+
<div className="mt-300 bg-gray-100">
241+
<LessonTabs value={tab} onChange={handleTabChange} />
242+
</div>
240243
</div>
241244

242-
<div
243-
ref={contentRef}
244-
className="mt-300 min-h-[964px] rounded-150 bg-background-default p-500"
245-
>
246-
{lesson?.contentMarkdown ? (
247-
<MarkdownContentCore content={lesson.contentMarkdown} />
248-
) : (
249-
<p className="font-designer-16r text-gray-500">
250-
본문이 준비 중입니다.
251-
</p>
252-
)}
253-
</div>
245+
{/* Scroll area: content + review form only */}
246+
<div ref={leftColRef} className="flex-1 overflow-y-auto">
247+
<div
248+
ref={contentRef}
249+
className="mt-300 min-h-[964px] rounded-150 bg-background-default p-500"
250+
>
251+
{lesson?.contentMarkdown ? (
252+
<MarkdownContentCore content={lesson.contentMarkdown} />
253+
) : (
254+
<p className="font-designer-16r text-gray-500">
255+
본문이 준비 중입니다.
256+
</p>
257+
)}
258+
</div>
254259

255-
<div ref={reviewRef} />
256-
<hr className="my-500 border-gray-300" />
260+
<div ref={reviewRef} />
261+
<hr className="my-500 border-gray-300" />
257262

258-
{tab === 'review' || tab === 'follow' ? (
259-
<LessonReviewForm
260-
key={lessonId}
261-
retrospectivePurpose={
262-
lesson?.retrospectivePurpose ?? 'ARTIFACT_SHARE'
263-
}
264-
retrospectivePrompt={lesson?.retrospectivePrompt}
265-
artifactSubmissionRequired={
266-
lesson?.artifactSubmissionRequired ?? false
267-
}
268-
alreadySubmitted={alreadySubmitted}
269-
submitting={submitRetrospective.isPending}
270-
isLastLesson={isLastLesson}
271-
onSubmit={handleSubmit}
272-
/>
273-
) : null}
263+
{tab === 'review' || tab === 'follow' ? (
264+
<LessonReviewForm
265+
key={lessonId}
266+
retrospectivePurpose={
267+
lesson?.retrospectivePurpose ?? 'ARTIFACT_SHARE'
268+
}
269+
retrospectivePrompt={lesson?.retrospectivePrompt}
270+
artifactSubmissionRequired={
271+
lesson?.artifactSubmissionRequired ?? false
272+
}
273+
alreadySubmitted={alreadySubmitted}
274+
submitting={submitRetrospective.isPending}
275+
isLastLesson={isLastLesson}
276+
onSubmit={handleSubmit}
277+
/>
278+
) : null}
274279

275-
<div className="h-1000" />
280+
<div className="h-1000" />
281+
</div>
276282
</div>
277283

278-
{/* RIGHT sticky sidebar */}
279-
<div className="sticky top-800 z-10 flex flex-col gap-250">
280-
<LessonQnaCard
281-
myQnas={qnaSidebar?.qnas ?? []}
282-
builderQnas={qnaSidebar?.builderQnas ?? []}
283-
onAskClick={() => setSubmissionModalOpen(true)}
284-
onSelectQna={setSelectedQnaId}
285-
/>
286-
<LessonBuilderFeedCard
287-
feeds={feedPreview?.feeds ?? []}
288-
onSelectFeed={setSelectedFeedId}
289-
/>
284+
{/* RIGHT sidebar — stays put while left column scrolls */}
285+
<div className="w-4500 shrink-0 pt-500">
286+
<div className="flex flex-col gap-250">
287+
<LessonQnaCard
288+
myQnas={qnaSidebar?.qnas ?? []}
289+
builderQnas={qnaSidebar?.builderQnas ?? []}
290+
onAskClick={() => setSubmissionModalOpen(true)}
291+
onSelectQna={setSelectedQnaId}
292+
/>
293+
<LessonBuilderFeedCard
294+
feeds={feedPreview?.feeds ?? []}
295+
onSelectFeed={setSelectedFeedId}
296+
/>
297+
</div>
290298
</div>
291299
</div>
292300
</div>

src/components/pages/class/roadmap-tab.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ import { useToastStore } from '@/stores/use-toast-store';
2222
import type { CourseCurriculumChapterResponse } from '@/types/api/course.types';
2323
import { ChapterHeader } from './chapter-header';
2424
import {
25-
FALLBACK_CHAPTERS,
2625
buildLessonMap,
27-
mergeLessons,
26+
FALLBACK_CHAPTERS,
2827
type LessonDisplayInfo,
28+
mergeLessons,
2929
} from './home-constants';
3030
import { LessonPreviewModal } from './lesson-preview-modal';
3131
import { LessonStamp } from './lesson-stamp';
@@ -233,7 +233,10 @@ export function RoadmapTab({ slug }: { slug: string }) {
233233
</div>
234234

235235
{rows.map((row, ri) => (
236-
<div key={ri} className="flex w-full flex-col items-center">
236+
<div
237+
key={row[0].lessonId}
238+
className="flex w-full flex-col items-center"
239+
>
237240
<div
238241
className={cn(
239242
'relative flex items-center justify-center',

0 commit comments

Comments
 (0)