오..
11
public/imgs/arrows-diagrams-arrow.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Group">
|
||||
<g id="Group_2">
|
||||
<path id="Path" fill-rule="evenodd" clip-rule="evenodd" d="M16 4V4C22.628 4 28 9.372 28 16V16C28 22.628 22.628 28 16 28V28C9.372 28 4 22.628 4 16V16C4 9.372 9.372 4 16 4Z" fill="var(--fill-0, #8C95A1)" stroke="var(--stroke-0, #8C95A1)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="Path_2" d="M10.6667 16H21.3333" stroke="var(--stroke-0, white)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="Path_3" d="M14.6667 20L10.6667 16L14.6667 12" stroke="var(--stroke-0, white)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g id="Path_4">
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 836 B |
3
public/imgs/asset-base.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 8 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="Icon (Stroke)" fill-rule="evenodd" clip-rule="evenodd" d="M0.292893 0.292893C0.683417 -0.0976311 1.31658 -0.0976311 1.70711 0.292893L7.70711 6.29289C8.09763 6.68342 8.09763 7.31658 7.70711 7.70711L1.70711 13.7071C1.31658 14.0976 0.683417 14.0976 0.292893 13.7071C-0.0976311 13.3166 -0.0976311 12.6834 0.292893 12.2929L5.58579 7L0.292893 1.70711C-0.0976311 1.31658 -0.0976311 0.683418 0.292893 0.292893Z" fill="var(--fill-0, white)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 620 B |
9
public/imgs/certificate-asset-1.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Group">
|
||||
<path id="Path" d="M12.9684 9.26294L10.0038 12.2275L7.03923 9.26294" stroke="var(--stroke-0, #4C5561)" stroke-width="1.5625" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="Path_2" d="M10.0041 3.33439V12.2273" stroke="var(--stroke-0, #4C5561)" stroke-width="1.5625" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="Path_3" d="M16.6737 13.7087C16.6737 15.346 15.3464 16.6733 13.7091 16.6733H6.29937C4.66208 16.6733 3.3348 15.346 3.3348 13.7087" stroke="var(--stroke-0, #4C5561)" stroke-width="1.5625" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<g id="Rectangle">
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 791 B |
3
public/imgs/certificate-asset.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="Icon (Stroke)" fill-rule="evenodd" clip-rule="evenodd" d="M0.317301 0.317301C0.740369 -0.105767 1.4263 -0.105767 1.84937 0.317301L7.58333 6.05127L13.3173 0.317301C13.7404 -0.105767 14.4263 -0.105767 14.8494 0.317301C15.2724 0.740369 15.2724 1.4263 14.8494 1.84937L9.1154 7.58333L14.8494 13.3173C15.2724 13.7404 15.2724 14.4263 14.8494 14.8494C14.4263 15.2724 13.7404 15.2724 13.3173 14.8494L7.58333 9.1154L1.84937 14.8494C1.4263 15.2724 0.740369 15.2724 0.317301 14.8494C-0.105767 14.4263 -0.105767 13.7404 0.317301 13.3173L6.05127 7.58333L0.317301 1.84937C-0.105767 1.4263 -0.105767 0.740369 0.317301 0.317301Z" fill="var(--fill-0, #333C47)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 832 B |
BIN
public/imgs/certificate-container.png
Normal file
|
After Width: | Height: | Size: 713 B |
3
public/imgs/ellipse-2.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle id="Ellipse 2" cx="6" cy="6" r="6" fill="var(--fill-0, white)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 250 B |
4
public/imgs/feedback-asset-1.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id=" Min Width">
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 202 B |
3
public/imgs/feedback-asset.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="Icon (Stroke)" fill-rule="evenodd" clip-rule="evenodd" d="M0.317301 0.317301C0.740369 -0.105767 1.4263 -0.105767 1.84937 0.317301L7.58333 6.05127L13.3173 0.317301C13.7404 -0.105767 14.4263 -0.105767 14.8494 0.317301C15.2724 0.740369 15.2724 1.4263 14.8494 1.84937L9.1154 7.58333L14.8494 13.3173C15.2724 13.7404 15.2724 14.4263 14.8494 14.8494C14.4263 15.2724 13.7404 15.2724 13.3173 14.8494L7.58333 9.1154L1.84937 14.8494C1.4263 15.2724 0.740369 15.2724 0.317301 14.8494C-0.105767 14.4263 -0.105767 13.7404 0.317301 13.3173L6.05127 7.58333L0.317301 1.84937C-0.105767 1.4263 -0.105767 0.740369 0.317301 0.317301Z" fill="var(--fill-0, #333C47)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 832 B |
8
public/imgs/group.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Group">
|
||||
<g id="Path">
|
||||
</g>
|
||||
<path id="Path_2" fill-rule="evenodd" clip-rule="evenodd" d="M105 60V60C105 84.855 84.855 105 60 105V105C35.145 105 15 84.855 15 60V60C15 35.145 35.145 15 60 15V15C84.855 15 105 35.145 105 60Z" stroke="var(--stroke-0, white)" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="Path_3" fill-rule="evenodd" clip-rule="evenodd" d="M54.703 45.2904L74.113 56.7703C76.568 58.2203 76.568 61.7754 74.113 63.2254L54.703 74.7054C52.203 76.1854 49.043 74.3804 49.043 71.4754V48.5204C49.043 45.6154 52.203 43.8104 54.703 45.2904V45.2904Z" fill="var(--fill-0, white)" stroke="var(--stroke-0, white)" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 896 B |
7
public/imgs/icon-1.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Icon">
|
||||
<path id="Vector" d="M8.25 3.52652C8.24985 3.42206 8.21876 3.31998 8.16065 3.23316C8.10254 3.14635 8.02001 3.0787 7.92349 3.03874C7.82697 2.99878 7.72077 2.98831 7.61831 3.00865C7.51584 3.02899 7.42169 3.07923 7.34775 3.15302L4.80975 5.69027C4.7118 5.7888 4.59528 5.86692 4.46692 5.92009C4.33856 5.97326 4.20093 6.00043 4.062 6.00002H2.25C2.05109 6.00002 1.86032 6.07904 1.71967 6.21969C1.57902 6.36034 1.5 6.55111 1.5 6.75002V11.25C1.5 11.4489 1.57902 11.6397 1.71967 11.7804C1.86032 11.921 2.05109 12 2.25 12H4.062C4.20093 11.9996 4.33856 12.0268 4.46692 12.08C4.59528 12.1331 4.7118 12.2112 4.80975 12.3098L7.347 14.8478C7.42095 14.9219 7.51523 14.9723 7.61789 14.9928C7.72056 15.0133 7.82699 15.0028 7.92369 14.9627C8.0204 14.9226 8.10303 14.8548 8.16112 14.7677C8.21921 14.6806 8.25015 14.5782 8.25 14.4735V3.52652Z" stroke="var(--stroke-0, white)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="Vector_2" d="M12 6.75C12.4868 7.39911 12.75 8.18861 12.75 9C12.75 9.81139 12.4868 10.6009 12 11.25" stroke="var(--stroke-0, white)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="Vector_3" d="M14.5234 13.7731C15.1502 13.1463 15.6475 12.4021 15.9867 11.5832C16.3259 10.7642 16.5005 9.88648 16.5005 9.00005C16.5005 8.11362 16.3259 7.23587 15.9867 6.41692C15.6475 5.59797 15.1502 4.85385 14.5234 4.22705" stroke="var(--stroke-0, white)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
5
public/imgs/icon-2.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Icon">
|
||||
<path id="Vector" d="M10 12L6 8L10 4" stroke="var(--stroke-0, white)" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 338 B |
5
public/imgs/icon-3.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Icon">
|
||||
<path id="Vector" d="M10 12L6 8L10 4" stroke="var(--stroke-0, white)" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 338 B |
8
public/imgs/icon-4.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Icon">
|
||||
<path id="Vector" d="M11.25 2.25H15.75V6.75" stroke="var(--stroke-0, white)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="Vector_2" d="M15.75 2.25L10.5 7.5" stroke="var(--stroke-0, white)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="Vector_3" d="M2.25 15.75L7.5 10.5" stroke="var(--stroke-0, white)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="Vector_4" d="M6.75 15.75H2.25V11.25" stroke="var(--stroke-0, white)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 778 B |
5
public/imgs/icon.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Icon">
|
||||
<path id="Vector" d="M9 18L15 12L9 6" stroke="var(--stroke-0, #B1B8C0)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 336 B |
BIN
public/imgs/image-1.png
Normal file
|
After Width: | Height: | Size: 276 KiB |
BIN
public/imgs/image-2.png
Normal file
|
After Width: | Height: | Size: 276 KiB |
3
public/imgs/line-58.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 16 1" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<line id="Line 58" y1="0.25" x2="16" y2="0.25" stroke="var(--stroke-0, #B1B8C0)" stroke-width="0.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 278 B |
7
public/imgs/music-audio-play.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Group">
|
||||
<g id="Path">
|
||||
</g>
|
||||
<path id="Path_2" fill-rule="evenodd" clip-rule="evenodd" d="M12.5735 8.27696L22.7652 14.3048C24.0543 15.0662 24.0543 16.9328 22.7652 17.6942L12.5735 23.7221C11.2608 24.4992 9.60156 23.5514 9.60156 22.0261V9.97295C9.60156 8.4476 11.2608 7.49984 12.5735 8.27696V8.27696Z" fill="var(--fill-0, white)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 517 B |
8
public/imgs/play.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Group">
|
||||
<g id="Path">
|
||||
</g>
|
||||
<path id="Path_2" fill-rule="evenodd" clip-rule="evenodd" d="M14 8V8C14 11.314 11.314 14 8 14V14C4.686 14 2 11.314 2 8V8C2 4.686 4.686 2 8 2V2C11.314 2 14 4.686 14 8Z" fill="var(--fill-0, #B1B8C0)" stroke="var(--stroke-0, #B1B8C0)" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="Path_3" fill-rule="evenodd" clip-rule="evenodd" d="M7.29373 6.03871L9.88173 7.56938C10.2091 7.76271 10.2091 8.23671 9.88173 8.43005L7.29373 9.96071C6.9604 10.158 6.53906 9.91738 6.53906 9.53005V6.46938C6.53906 6.08205 6.9604 5.84138 7.29373 6.03871V6.03871Z" fill="var(--fill-0, white)" stroke="var(--stroke-0, white)" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 875 B |
BIN
public/imgs/thumb-a.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
public/imgs/thumb-b.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
public/imgs/thumb-c.png
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
public/imgs/thumb-d.png
Normal file
|
After Width: | Height: | Size: 98 KiB |
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||
|
||||