오..
This commit is contained in:
@@ -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">
|
||||
|
||||
9
src/app/[lessonCode]/review/page.tsx
Normal file
9
src/app/[lessonCode]/review/page.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import FigmaSelectedLessonPage from "../../menu/courses/lessons/FigmaSelectedLessonPage";
|
||||
|
||||
export default function SimpleReviewPage() {
|
||||
return <FigmaSelectedLessonPage />;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
17
src/app/components/FooterVisibility.tsx
Normal file
17
src/app/components/FooterVisibility.tsx
Normal 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 />;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 />;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -69,7 +69,9 @@ export default function AccountPage() {
|
||||
onSubmit={() => {
|
||||
// TODO: integrate API
|
||||
}}
|
||||
devVerificationState={verificationState}
|
||||
devVerificationState={
|
||||
verificationState === 'changed' ? 'verified' : verificationState
|
||||
}
|
||||
/>
|
||||
|
||||
<MenuAccountOption
|
||||
|
||||
@@ -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 ? "이어서 수강하기" : "수강하기"}
|
||||
|
||||
168
src/app/menu/courses/lessons/FigmaSelectedLessonPage.tsx
Normal file
168
src/app/menu/courses/lessons/FigmaSelectedLessonPage.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
import FigmaSelectedLessonPage from '../../FigmaSelectedLessonPage';
|
||||
|
||||
export default function ContinueLessonPage() {
|
||||
return <FigmaSelectedLessonPage />;
|
||||
}
|
||||
|
||||
|
||||
7
src/app/menu/courses/lessons/[lessonId]/review/page.tsx
Normal file
7
src/app/menu/courses/lessons/[lessonId]/review/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export default function ReviewLessonPage({ params }: { params: { lessonId: string } }) {
|
||||
redirect(`/${params.lessonId}/review`);
|
||||
}
|
||||
|
||||
|
||||
8
src/app/menu/courses/lessons/[lessonId]/start/page.tsx
Normal file
8
src/app/menu/courses/lessons/[lessonId]/start/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
import FigmaSelectedLessonPage from '../../FigmaSelectedLessonPage';
|
||||
|
||||
export default function StartLessonPage() {
|
||||
return <FigmaSelectedLessonPage />;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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
69
src/app/pages/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user