This commit is contained in:
mota
2025-11-18 10:42:48 +09:00
parent 4dc8304a1d
commit f4196167c7
42 changed files with 614 additions and 104 deletions

View File

@@ -17,6 +17,7 @@ export default function NavBar() {
const [isUserMenuOpen, setIsUserMenuOpen] = useState(false);
const userMenuRef = useRef<HTMLDivElement | null>(null);
const userButtonRef = useRef<HTMLButtonElement | null>(null);
const hideCenterNav = /^\/[^/]+\/review$/.test(pathname);
useEffect(() => {
if (!isUserMenuOpen) return;
@@ -50,21 +51,21 @@ export default function NavBar() {
<MainLogoSvg width={46.703} height={36} />
<span className="text-2xl font-extrabold leading-[1.45] text-white">XR LMS</span>
</Link>
<nav className="flex h-full items-center">
{NAV_ITEMS.map((item) => {
return (
<Link
key={item.href}
href={item.href}
className={[
"px-4 py-2 text-[16px] font-semibold text-white",
].join(" ")}
>
{item.label}
</Link>
);
})}
</nav>
{!hideCenterNav && (
<nav className="flex h-full items-center">
{NAV_ITEMS.map((item) => {
return (
<Link
key={item.href}
href={item.href}
className={["px-4 py-2 text-[16px] font-semibold text-white"].join(" ")}
>
{item.label}
</Link>
);
})}
</nav>
)}
</div>
<div className="relative flex items-center gap-2">
<Link href="/menu/courses" className="px-4 py-2 text-[16px] font-semibold text-white">

View File

@@ -0,0 +1,9 @@
'use client';
import FigmaSelectedLessonPage from "../../menu/courses/lessons/FigmaSelectedLessonPage";
export default function SimpleReviewPage() {
return <FigmaSelectedLessonPage />;
}

View File

@@ -39,7 +39,7 @@ export default function Footer() {
<img
src="/imgs/talk.png"
alt="talk"
className="self-end ml-auto mr-[40px] mb-[40px]"
className="self-end ml-auto mr-[40px] mb-[0px]"
/>
</div>
</div>

View File

@@ -0,0 +1,17 @@
'use client';
import { usePathname } from "next/navigation";
import Footer from "./Footer";
const HIDE_FOOTER_PREFIXES = ["/pages"];
export default function FooterVisibility() {
const pathname = usePathname();
const shouldHide = HIDE_FOOTER_PREFIXES.some(
(prefix) => pathname === prefix || pathname.startsWith(prefix + "/")
);
if (shouldHide) return null;
return <Footer />;
}

View File

@@ -3,11 +3,12 @@
import { usePathname } from "next/navigation";
import NavBar from "../NavBar";
const HIDE_HEADER_PREFIXES = ["/login", "/register", "/reset-password", "/find-id"];
const HIDE_HEADER_PREFIXES = ["/login", "/register", "/reset-password", "/find-id", "/pages"];
export default function HeaderVisibility() {
const pathname = usePathname();
const shouldHide = HIDE_HEADER_PREFIXES.some((prefix) => pathname === prefix || pathname.startsWith(prefix + "/"));
const shouldHide =
HIDE_HEADER_PREFIXES.some((prefix) => pathname === prefix || pathname.startsWith(prefix + "/"));
if (shouldHide) return null;
return <NavBar />;
}

View File

@@ -1,58 +1,153 @@
'use client';
import { useMemo, useState } from 'react';
import ChevronDownSvg from '../svgs/chevrondownsvg';
// 피그마 선택 컴포넌트의 구조/스타일(타이포/여백/색상)을 반영한 리스트 UI
// - 프로젝트는 Tailwind v4(@import "tailwindcss")를 사용하므로 클래스 그대로 적용
// - 헤더/푸터는 레이아웃에서 처리되므로 본 페이지에는 포함하지 않음
// - 카드 구성: 섬네일(rounded 8px) + 상태 태그 + 제목 + 메타(재생 아이콘 + 텍스트)
const imgPlay = '/imgs/play.svg'; // public/imgs/play.svg
const imgThumbA = '/imgs/thumb-a.png'; // public/imgs/thumb-a.png
const imgThumbB = '/imgs/thumb-b.png'; // public/imgs/thumb-b.png
const imgThumbC = '/imgs/thumb-c.png'; // public/imgs/thumb-c.png
const imgThumbD = '/imgs/thumb-d.png'; // public/imgs/thumb-d.png
function ColorfulTag({ text }: { text: string }) {
return (
<div className="bg-[#e5f5ec] box-border flex h-[20px] items-center justify-center px-[4px] rounded-[4px]">
<p className="font-['Pretendard:SemiBold',sans-serif] text-[#0c9d61] text-[13px] leading-[1.4]">{text}</p>
</div>
);
}
type Course = { id: string; title: string; image: string; inProgress?: boolean };
export default function CourseListPage() {
const courses = [
{
id: "p1",
title: "원자로 운전 및 계통",
description:
"원자로 운전 원리와 주요 계통의 구조 및 기능을 이해하고, 실제 운전 상황을 가상 환경에서 체험합니다.",
},
{
id: "p2",
title: "확률론",
description:
"확률과 통계의 핵심 개념을 직관적으로 이해하고 문제 해결력을 기릅니다.",
},
{
id: "p3",
title: "부서간 협업",
description:
"협업 절차, 기록 기준, 리스크 관리까지 실제 사례 기반으로 배우는 협업 가이드.",
},
{
id: "p4",
title: "방사선의 이해",
description:
"방사선 안전 기준과 보호 장비 선택법을 배우는 실무형 과정.",
},
const ITEMS_PER_PAGE = 20;
const [page, setPage] = useState(1);
const base: Omit<Course, 'id'>[] = [
{ title: '원자로 운전 및 계통', image: imgThumbA, inProgress: true },
{ title: '핵연료', image: imgThumbB },
{ title: '방사선 안전', image: imgThumbC },
{ title: '방사선 폐기물', image: imgThumbD },
{ title: '원자로 운전 및 계통', image: imgThumbA },
{ title: '핵연료', image: imgThumbB },
{ title: '방사선 안전', image: imgThumbC },
];
const courses: Course[] = Array.from({ length: 28 }, (_, i) => {
const item = base[i % base.length];
return {
id: `p${i + 1}`,
title: item.title,
image: item.image,
inProgress: item.inProgress ?? (i % 7 === 0),
};
});
const totalPages = Math.ceil(courses.length / ITEMS_PER_PAGE);
const pagedCourses = useMemo(
() => courses.slice((page - 1) * ITEMS_PER_PAGE, page * ITEMS_PER_PAGE),
[courses, page]
);
return (
<main className="flex w-full flex-col">
<div className="flex h-[100px] items-center px-8">
<h1 className="text-[24px] font-bold leading-[1.5] text-white"> </h1>
<main className="flex w-full flex-col items-center">
{/* 상단 타이틀 영역 */}
<div className="flex h-[100px] w-full max-w-[1440px] items-center px-8">
<h1 className="text-[24px] font-bold leading-normal text-[#1b2027]"> </h1>
</div>
<section className="px-8 pb-16">
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
{courses.map((c) => (
<article
key={c.id}
className="rounded-xl border border-[#ecf0ff] bg-white p-4 shadow-[0_2px_8px_rgba(0,0,0,0.02)]"
>
<h2 className="truncate text-[18px] font-bold leading-[1.5] text-[#1b2027]">{c.title}</h2>
<p className="mt-1 line-clamp-3 text-[14px] leading-[1.5] text-[#4c5561]">{c.description}</p>
<div className="mt-3">
<button
type="button"
className="h-9 rounded-md border border-[#dee1e6] px-3 text-[14px] font-medium leading-[1.5] text-[#4c5561] hover:bg-[#f9fafb]"
>
</button>
</div>
</article>
))}
{/* 콘텐츠 래퍼: Figma 기준 1440 컨테이너, 내부 1376 그리드 폭 */}
<section className="w-full max-w-[1440px] px-8 pt-8 pb-20">
{/* 상단 카운트/정렬 영역 */}
<div className="flex items-center justify-between">
<p className="text-[15px] font-medium leading-normal text-[#333c47]">
<span className="text-[#384fbf]">{courses.length}</span>
</p>
<div className="h-[40px] w-[114px]" />
</div>
{/* 카드 그리드(고정 5열, gap 32px) */}
<div className="mt-4 flex flex-col items-center">
<div className="w-[1376px] grid grid-cols-5 gap-[32px]">
{pagedCourses.map((c) => (
<article
key={c.id}
className="flex h-[260px] w-[249.6px] flex-col gap-[16px] rounded-[8px] bg-white"
>
{/* 섬네일 */}
<div className="relative h-[166.4px] w-full overflow-clip rounded-[8px]">
<img
src={c.image}
alt=""
className="absolute inset-0 size-full object-cover"
/>
</div>
{/* 본문 텍스트 블록 */}
<div className="flex w-full flex-col gap-[4px]">
<div className="flex flex-col gap-[4px]">
{c.inProgress && <ColorfulTag text="수강 중" />}
<h2 className="text-[18px] font-semibold leading-normal text-[#333c47]">
{c.title}
</h2>
</div>
<div className="flex items-center gap-[4px]">
<img src={imgPlay} alt="" className="size-[16px]" />
<p className="text-[13px] font-medium leading-[1.4] text-[#8c95a1]">
VOD · 6 · 4 20
</p>
</div>
</div>
</article>
))}
</div>
</div>
{/* 페이지네이션 (피그마 스타일 반영) */}
<div className="mt-8 flex items-center justify-center gap-[8px]">
{/* Prev */}
<button
type="button"
onClick={() => setPage((p) => Math.max(1, p - 1))}
aria-label="이전 페이지"
className="flex items-center justify-center rounded-[1000px] p-[8.615px] size-[32px] text-[#333c47] disabled:opacity-40"
disabled={page === 1}
>
<ChevronDownSvg width={14.8} height={14.8} className="rotate-90" />
</button>
{/* Numbers */}
{Array.from({ length: totalPages }, (_, i) => i + 1).map((n) => {
const active = n === page;
return (
<button
key={n}
type="button"
onClick={() => setPage(n)}
aria-current={active ? 'page' : undefined}
className={[
'flex items-center justify-center rounded-[1000px] size-[32px]',
active ? 'bg-[#ecf0ff]' : 'bg-white',
].join(' ')}
>
<span className="text-[16px] leading-[1.4] text-[#333c47]">{n}</span>
</button>
);
})}
{/* Next */}
<button
type="button"
onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
aria-label="다음 페이지"
className="flex items-center justify-center rounded-[1000px] p-[8.615px] size-[32px] text-[#333c47] disabled:opacity-40"
disabled={page === totalPages}
>
<ChevronDownSvg width={14.8} height={14.8} className="-rotate-90" />
</button>
</div>
</section>
</main>

View File

@@ -2,7 +2,7 @@ import type { Metadata } from "next";
import "./globals.css";
import { pretendard } from "./fonts";
import HeaderVisibility from "./components/HeaderVisibility";
import Footer from "./components/Footer";
import FooterVisibility from "./components/FooterVisibility";
export const metadata: Metadata = {
title: "XRLMS",
@@ -19,7 +19,7 @@ export default function RootLayout({
<main className="flex-1 min-h-0">
{children}
</main>
<Footer />
<FooterVisibility />
</body>
</html>
);

View File

@@ -69,7 +69,9 @@ export default function AccountPage() {
onSubmit={() => {
// TODO: integrate API
}}
devVerificationState={verificationState}
devVerificationState={
verificationState === 'changed' ? 'verified' : verificationState
}
/>
<MenuAccountOption

View File

@@ -1,6 +1,7 @@
'use client';
import Image from "next/image";
import { useRouter } from "next/navigation";
import { useState } from "react";
import ChevronDownSvg from "../../svgs/chevrondownsvg";
@@ -36,14 +37,15 @@ function ProgressBar({ value }: { value: number }) {
export default function CourseCard({ course, defaultOpen = false }: { course: Course; defaultOpen?: boolean }) {
const [open, setOpen] = useState(defaultOpen);
const router = useRouter();
const totalMinutes = course.lessons.reduce((sum, l) => sum + (l.durationMin || 0), 0);
const totalHours = Math.floor(totalMinutes / 60);
const restMinutes = totalMinutes % 60;
const firstIncomplete = course.lessons.find((l) => !l.isCompleted)?.id;
const cardClassName = [
"rounded-xl border bg-white shadow-[0_2px_8px_rgba(0,0,0,0.02)]",
open ? "border-[#384fbf]" : "border-[#ecf0ff]",
"rounded-xl bg-white shadow-[0_2px_8px_rgba(0,0,0,0.02)]",
open ? "border-[3px] border-[#384fbf]" : "border border-[#ecf0ff]",
].join(" ");
const formatDuration = (m: number) => {
@@ -54,33 +56,33 @@ export default function CourseCard({ course, defaultOpen = false }: { course: Co
return (
<article className={cardClassName}>
<header className="flex items-center gap-4 p-4">
<div className="relative h-[76px] w-[120px] overflow-hidden rounded-md bg-[#f1f3f5]">
<header className="flex items-center gap-6 px-8 py-6">
<div className="relative h-[120px] w-[180px] overflow-hidden rounded-[8px] bg-[#f1f3f5]">
<Image
src={`https://picsum.photos/seed/${encodeURIComponent(course.id)}/240/152`}
alt=""
fill
sizes="120px"
sizes="180px"
unoptimized
className="object-cover"
/>
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<span className="rounded bg-[#e5f5ec] px-2 py-0.5 text-[12px] font-semibold leading-[1.4] text-[#0c9d61]">
<span className="flex h-[20px] items-center justify-center rounded-[4px] bg-[#e5f5ec] px-[4px] text-[13px] font-semibold leading-[1.4] text-[#0c9d61]">
{course.status}
</span>
<h2 className="truncate text-[18px] font-bold leading-[1.5] text-[#1b2027]">
<h2 className="truncate text-[18px] font-semibold leading-normal text-[#333c47]">
{course.title}
</h2>
</div>
<p className="mt-1 line-clamp-2 text-[14px] leading-[1.5] text-[#4c5561]">{course.description}</p>
<p className="mt-1 line-clamp-2 text-[14px] leading-normal text-basic-text">{course.description}</p>
<div className="mt-2 flex items-center justify-between gap-3">
<p className="text-[13px] leading-[1.4] text-[#8c95a1]">
VOD · {course.lessons.length} · {totalHours} {restMinutes}
</p>
<p className="text-[13px] font-semibold leading-[1.4] text-[#384fbf]">
{course.progressPct}%
: {course.progressPct}%
</p>
</div>
<div className="mt-2">
@@ -105,20 +107,15 @@ export default function CourseCard({ course, defaultOpen = false }: { course: Co
</header>
{open ? (
<div className="px-4 pb-4">
<div className="px-6 pb-6">
<ul className="flex flex-col gap-2">
{course.lessons.map((lesson, idx) => (
<li key={lesson.id} className="rounded-lg border border-[#ecf0ff] bg-white">
<div className="flex items-center justify-between gap-4 px-4 py-3">
<li key={lesson.id} className="rounded-lg border border-[#dee1e6] bg-white">
<div className="flex items-center justify-between gap-4 px-[24px] py-[16px]">
<div className="min-w-0 flex-1">
<div className="flex items-center gap-3">
<span className="w-[20px] text-[13px] font-semibold leading-[1.4] text-[#8c95a1]">
{idx + 1}.
</span>
<p className="truncate text-[14px] font-medium leading-[1.5] text-[#333c47]">
{lesson.title}
</p>
</div>
<p className="truncate text-[16px] font-semibold leading-normal text-[#333c47]">
{`${idx + 1}. ${lesson.title}`}
</p>
<div className="mt-2 flex items-center gap-3">
<span className="text-[13px] leading-[1.4] text-[#8c95a1]">
{formatDuration(lesson.durationMin)}
@@ -129,29 +126,59 @@ export default function CourseCard({ course, defaultOpen = false }: { course: Co
<button
type="button"
className={[
"rounded-md px-3 py-2 text-[14px] font-medium leading-[1.5]",
"inline-flex h-[32px] w-[140px] items-center justify-center gap-[6px] rounded-[6px] px-4 text-center whitespace-nowrap cursor-pointer",
lesson.isCompleted
? "text-[#384fbf] border border-transparent bg-white"
: "text-[#4c5561] border border-[#8c95a1] bg-white",
? "bg-white text-[13px] font-medium leading-[1.4] text-[#384fbf]"
: "bg-white text-[14px] font-medium leading-normal text-basic-text border border-[#8c95a1]",
].join(" ")}
>
{lesson.isCompleted ? "학습 제출 완료" : "학습 제출 하기"}
{lesson.isCompleted ? (
<>
<svg
xmlns="http://www.w3.org/2000/svg"
width="10"
height="7"
viewBox="0 0 10 7"
fill="none"
aria-hidden
>
<path
d="M8.75 0.75L3.25 6.25L0.75 3.75"
stroke="#384FBF"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<span> </span>
</>
) : (
"학습 제출 하기"
)}
</button>
{lesson.isCompleted ? (
<button
type="button"
className="rounded-md border border-[#dee1e6] px-3 py-2 text-[14px] font-medium leading-[1.5] text-[#4c5561] hover:bg-[#f9fafb]"
onClick={() => router.push(`/menu/courses/lessons/${lesson.id}/review`)}
className="inline-flex h-[32px] w-[140px] items-center justify-center rounded-[6px] bg-[#f1f3f5] px-4 text-center text-[14px] font-medium leading-normal text-basic-text whitespace-nowrap cursor-pointer"
>
</button>
) : (
<button
type="button"
onClick={() =>
router.push(
lesson.id === firstIncomplete
? `/menu/courses/lessons/${lesson.id}/continue`
: `/menu/courses/lessons/${lesson.id}/start`,
)
}
className={[
"rounded-md px-3 py-2 text-[14px] font-medium leading-[1.5]",
"inline-flex h-[32px] w-[140px] items-center justify-center rounded-[6px] px-4 text-center text-[14px] font-medium leading-normal whitespace-nowrap cursor-pointer",
lesson.id === firstIncomplete
? "bg-[#ecf0ff] text-[#384fbf]"
: "border border-[#dee1e6] text-[#4c5561] hover:bg-[#f9fafb]",
: "border border-[#dee1e6] text-basic-text",
].join(" ")}
>
{lesson.id === firstIncomplete ? "이어서 수강하기" : "수강하기"}

View File

@@ -0,0 +1,168 @@
'use client';
const imgImage2 = "/imgs/image-2.png";
const imgLine58 = "/imgs/line-58.svg";
const img = "/imgs/asset-base.svg";
const imgArrowsDiagramsArrow = "/imgs/arrows-diagrams-arrow.svg";
const imgIcon = "/imgs/icon.svg";
const imgGroup = "/imgs/group.svg";
const imgEllipse2 = "/imgs/ellipse-2.svg";
const imgMusicAudioPlay = "/imgs/music-audio-play.svg";
const imgIcon1 = "/imgs/icon-1.svg";
const imgIcon2 = "/imgs/icon-2.svg";
const imgIcon3 = "/imgs/icon-3.svg";
const imgIcon4 = "/imgs/icon-4.svg";
export default function FigmaSelectedLessonPage() {
return (
<div className="bg-white content-stretch flex flex-col items-center relative size-full">
<div className="content-stretch flex flex-col items-start max-w-[1440px] relative shrink-0 w-[1440px]">
<div className="box-border content-stretch flex gap-[10px] h-[100px] items-center px-[32px] py-0 relative shrink-0 w-full">
<div className="basis-0 content-stretch flex gap-[12px] grow items-center min-h-px min-w-px relative shrink-0">
<div className="relative shrink-0 size-[32px]">
<img alt="" className="block max-w-none size-full" src={imgArrowsDiagramsArrow} />
</div>
<div className="basis-0 content-stretch flex flex-col grow items-start justify-center leading-[1.5] min-h-px min-w-px not-italic relative shrink-0">
<p className="font-['Pretendard:SemiBold',sans-serif] relative shrink-0 text-[#6c7682] text-[16px] w-full">
</p>
<p className="font-['Pretendard:Bold',sans-serif] relative shrink-0 text-[#1b2027] text-[24px] w-full">
6. ,
</p>
</div>
<div className="content-stretch flex gap-[20px] h-[81px] items-center justify-center relative shrink-0">
<div className="relative shrink-0 w-[52px]">
<div className="bg-clip-padding border-0 border-[transparent] border-solid box-border content-stretch flex flex-col gap-[4px] items-center relative w-[52px]">
<div className="bg-[#384fbf] relative rounded-[2.23696e+07px] shrink-0 size-[32px]">
<div className="bg-clip-padding border-0 border-[transparent] border-solid box-border content-stretch flex items-center justify-center relative size-[32px]">
<p className="font-['Pretendard:Bold',sans-serif] leading-[18px] not-italic relative shrink-0 text-[14px] text-nowrap text-white whitespace-pre">
1
</p>
</div>
</div>
<p className="font-['Pretendard:SemiBold',sans-serif] leading-[1.5] not-italic relative shrink-0 text-[#384fbf] text-[14px] text-nowrap whitespace-pre">
</p>
</div>
</div>
<div className="relative shrink-0 size-[24px]">
<img alt="" className="block max-w-none size-full" src={imgIcon} />
</div>
<div className="relative shrink-0 w-[52px]">
<div className="bg-clip-padding border-0 border-[transparent] border-solid box-border content-stretch flex flex-col gap-[4px] items-center relative w-[52px]">
<div className="bg-[#dee1e6] relative rounded-[2.23696e+07px] shrink-0 size-[32px]">
<div className="bg-clip-padding border-0 border-[transparent] border-solid box-border content-stretch flex items-center justify-center relative size-[32px]">
<p className="font-['Pretendard:Bold',sans-serif] leading-[18px] not-italic relative shrink-0 text-[#6c7682] text-[14px] text-nowrap whitespace-pre">
2
</p>
</div>
</div>
<p className="font-['Pretendard:SemiBold',sans-serif] leading-[1.5] not-italic relative shrink-0 text-[#6c7682] text-[14px] text-nowrap whitespace-pre">
XR
</p>
</div>
</div>
<div className="relative shrink-0 size-[24px]">
<img alt="" className="block max-w-none size-full" src={imgIcon} />
</div>
<div className="relative shrink-0 w-[52px]">
<div className="bg-clip-padding border-0 border-[transparent] border-solid box-border content-stretch flex flex-col gap-[4px] items-center relative w-[52px]">
<div className="bg-[#dee1e6] relative rounded-[2.23696e+07px] shrink-0 size-[32px]">
<div className="bg-clip-padding border-0 border-[transparent] border-solid box-border content-stretch flex items-center justify-center relative size-[32px]">
<p className="font-['Pretendard:Bold',sans-serif] leading-[18px] not-italic relative shrink-0 text-[#6c7682] text-[14px] text-nowrap whitespace-pre">
3
</p>
</div>
</div>
<p className="font-['Pretendard:SemiBold',sans-serif] leading-[1.5] not-italic relative shrink-0 text-[#6c7682] text-[14px] text-nowrap whitespace-pre">
</p>
</div>
</div>
</div>
</div>
</div>
<div className="box-border content-stretch flex flex-col gap-[24px] items-center overflow-clip pb-[80px] pt-[24px] px-8 relative rounded-[8px] shrink-0 w-full">
<div className="aspect-[1920/1080] bg-black overflow-clip relative rounded-[8px] shrink-0 w-full">
<div className="absolute left-1/2 size-[120px] top-1/2 translate-x-[-50%] translate-y-[-50%]">
<div className="absolute contents inset-0">
<img alt="" className="block max-w-none size-full" src={imgGroup} />
</div>
</div>
<div className="absolute bg-gradient-to-t bottom-0 box-border content-stretch flex flex-col from-[rgba(0,0,0,0.8)] gap-[20px] items-center justify-center left-[-0.5px] px-[16px] py-[24px] to-[rgba(0,0,0,0)] w-[1376px]">
<div className="bg-[#333c47] h-[4px] relative rounded-[3.35544e+07px] shrink-0 w-full">
<div className="absolute left-0 size-[12px] top-1/2 translate-y-[-50%]">
<img alt="" className="block max-w-none size-full" src={imgEllipse2} />
</div>
</div>
<div className="content-stretch flex h-[32px] items-center justify-between relative shrink-0 w-full">
<div className="relative shrink-0">
<div className="bg-clip-padding border-0 border-[transparent] border-solid box-border content-stretch flex gap-[8px] items-center relative">
<div className="content-stretch flex gap-[16px] items-center relative shrink-0">
<div className="relative shrink-0 size-[32px]">
<img alt="" className="block max-w-none size-full" src={imgMusicAudioPlay} />
</div>
<div className="content-stretch flex gap-[8px] h-[32px] items-center relative shrink-0 w-[120px]">
<div className="relative rounded-[4px] shrink-0 size-[32px]">
<div className="bg-clip-padding border-0 border-[transparent] border-solid box-border content-stretch flex items-center justify-center relative size-[32px]">
<div className="relative shrink-0 size-[18px]">
<img alt="" className="block max-w-none size-full" src={imgIcon1} />
</div>
</div>
</div>
<div className="basis-0 grow h-[4px] min-h-px min-w-px relative rounded-[3.35544e+07px] shrink-0">
<div className="bg-clip-padding border-0 border-[transparent] border-solid box-border h-[4px] w-full" />
</div>
</div>
<p className="font-['Pretendard:Medium',sans-serif] leading-[19.5px] not-italic relative shrink-0 text-[13px] text-nowrap text-white whitespace-pre">
0:00 / 12:26
</p>
</div>
</div>
</div>
<div className="relative shrink-0">
<div className="bg-clip-padding border-0 border-[transparent] border-solid box-border content-stretch flex gap-[12px] items-center relative">
<div className="bg-[#333c47] box-border content-stretch flex gap-[4px] h-[32px] items-center justify-center px-[16px] py-[3px] relative rounded-[6px] shrink-0 w-[112px]">
<div className="relative shrink-0 size-[16px]">
<img alt="" className="block max-w-none size-full" src={imgIcon2} />
</div>
<p className="font-['Pretendard:Medium',sans-serif] leading-[1.5] not-italic relative shrink-0 text-[14px] text-center text-nowrap text-white whitespace-pre">
</p>
</div>
<div className="bg-[#333c47] box-border content-stretch flex gap-[4px] h-[32px] items-center justify-center px-[16px] py-[3px] relative rounded-[6px] shrink-0 w-[112px]">
<p className="font-['Pretendard:Medium',sans-serif] leading-[1.5] not-italic relative shrink-0 text-[14px] text-center text-nowrap text-white whitespace-pre">
</p>
<div className="flex items-center justify-center relative shrink-0">
<div className="flex-none rotate-[180deg] scale-y-[-100%]">
<div className="relative size-[16px]">
<img alt="" className="block max-w-none size-full" src={imgIcon3} />
</div>
</div>
</div>
</div>
<div className="content-stretch flex items-center justify-center relative rounded-[4px] shrink-0 size-[32px]">
<div className="relative shrink-0 size-[18px]">
<img alt="" className="block max-w-none size-full" src={imgIcon4} />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="bg-[#fff9ee] border border-[#ffdd82] border-solid box-border content-stretch flex flex-col h-[55px] items-start pb-px pt-[17px] px-[17px] relative rounded-[8px] shrink-0 w-full">
<ul className="[white-space-collapse:collapse] block font-['Pretendard:Medium',sans-serif] leading-[0] not-italic relative shrink-0 text-[#333c47] text-[14px] text-nowrap">
<li className="ms-[21px]">
<span className="leading-[1.5]"> .</span>
</li>
</ul>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,8 @@
'use client';
import FigmaSelectedLessonPage from '../../FigmaSelectedLessonPage';
export default function ContinueLessonPage() {
return <FigmaSelectedLessonPage />;
}

View File

@@ -0,0 +1,7 @@
import { redirect } from "next/navigation";
export default function ReviewLessonPage({ params }: { params: { lessonId: string } }) {
redirect(`/${params.lessonId}/review`);
}

View File

@@ -0,0 +1,8 @@
'use client';
import FigmaSelectedLessonPage from '../../FigmaSelectedLessonPage';
export default function StartLessonPage() {
return <FigmaSelectedLessonPage />;
}

View File

@@ -7,10 +7,10 @@ type Props = {
onClose: () => void;
};
const imgImage1 = "http://localhost:3845/assets/89fda8e949171025b1232bae70fc9d442e4e70c8.png";
const imgContainer = "http://localhost:3845/assets/d04df6bb7fe1bd29946d04be9442029bca1503b0.png";
const img = "http://localhost:3845/assets/7adf9a5e43b6c9e5f9bee6adfee64e85eabac44a.svg";
const img1 = "http://localhost:3845/assets/9e3b52939dbaa99088659a82db437772ef1ad40e.svg";
const imgImage1 = "/imgs/image-1.png";
const imgContainer = "/imgs/certificate-container.png";
const img = "/imgs/certificate-asset.svg";
const img1 = "/imgs/certificate-asset-1.svg";
export default function FigmaCertificateContent({ onClose }: Props) {
return (

View File

@@ -10,8 +10,8 @@ type Props = {
scoreText?: string;
};
const img = "http://localhost:3845/assets/7adf9a5e43b6c9e5f9bee6adfee64e85eabac44a.svg";
const img1 = "http://localhost:3845/assets/498f1d9877c6da3dadf581f98114a7f15bfc6769.svg";
const img = "/imgs/feedback-asset.svg";
const img1 = "/imgs/feedback-asset-1.svg";
export default function FigmaFeedbackContent({
onClose,

69
src/app/pages/page.tsx Normal file
View File

@@ -0,0 +1,69 @@
import Link from "next/link";
type RouteItem = { label: string; href: string };
const STATIC_ROUTES: RouteItem[] = [
{ label: "홈", href: "/" },
{ label: "교육 과정 목록", href: "/course-list" },
{ label: "학습 자료실 목록", href: "/resources" },
{ label: "공지사항 목록", href: "/notices" },
{ label: "내 강좌실 - 강좌 목록", href: "/menu/courses" },
{ label: "내 강좌실 - 학습 결과", href: "/menu/results" },
{ label: "내 정보 수정", href: "/menu/account" },
{ label: "로그인", href: "/login" },
{ label: "회원가입", href: "/register" },
{ label: "아이디 찾기", href: "/find-id" },
{ label: "비밀번호 재설정", href: "/reset-password" },
];
// 예시가 필요한 동적 라우트
const DYNAMIC_EXAMPLES: RouteItem[] = [
{ label: "공지사항 상세(예시)", href: "/notices/1" },
{ label: "자료실 상세(예시)", href: "/resources/1" },
{ label: "강좌 상세(예시)", href: "/menu/courses/abc123" },
{ label: "레슨 시작(예시)", href: "/menu/courses/lessons/c1l1/start" },
{ label: "레슨 이어서(예시)", href: "/menu/courses/lessons/c1l1/continue" },
{ label: "레슨 리뷰(새 경로 예시)", href: "/c1l1/review" },
];
export default function AllPages() {
return (
<main className="mx-auto max-w-[960px] p-8">
<h1 className="mb-6 text-[24px] font-bold leading-[1.5] text-[#1b2027]">
</h1>
<section className="mb-10">
<h2 className="mb-3 text-[18px] font-semibold leading-[1.5] text-[#333c47]">
/
</h2>
<ul className="list-disc pl-6">
{STATIC_ROUTES.map((r) => (
<li key={r.href} className="mb-1">
<Link href={r.href} className="text-[#1f2b91] underline">
{r.label}
</Link>
</li>
))}
</ul>
</section>
<section>
<h2 className="mb-3 text-[18px] font-semibold leading-[1.5] text-[#333c47]">
( )
</h2>
<ul className="list-disc pl-6">
{DYNAMIC_EXAMPLES.map((r) => (
<li key={r.href} className="mb-1">
<Link href={r.href} className="text-[#1f2b91] underline">
{r.label}
</Link>
</li>
))}
</ul>
</section>
</main>
);
}

View File

@@ -1,10 +1,12 @@
import type { ReactElement } from "react";
export default function BackCircleSvg(
{
width = 32,
height = 32,
className = '',
}: { width?: number | string; height?: number | string; className?: string }
): JSX.Element {
): ReactElement {
return (
<svg
xmlns="http://www.w3.org/2000/svg"

View File

@@ -1,10 +1,12 @@
import type { ReactElement } from "react";
export default function ChevronDownSvg(
{
width = 24,
height = 24,
className = '',
}: { width?: number | string; height?: number | string; className?: string }
): JSX.Element {
): ReactElement {
return (
<svg
xmlns="http://www.w3.org/2000/svg"

View File

@@ -1,10 +1,12 @@
import type { ReactElement } from "react";
export default function PaperClipSvg(
{
width = 16,
height = 16,
className = '',
}: { width?: number | string; height?: number | string; className?: string }
): JSX.Element {
): ReactElement {
return (
<svg
xmlns="http://www.w3.org/2000/svg"