내 강좌실 생성11
This commit is contained in:
367
app/mylecture/page.tsx
Normal file
367
app/mylecture/page.tsx
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import LoginPage from '../login/page';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import logo from '../logo.svg';
|
||||||
|
import RadioOff from '../../public/svg/radio_off';
|
||||||
|
import RadioOn from '../../public/svg/radio_on';
|
||||||
|
|
||||||
|
const imgImage2 = "http://localhost:3845/assets/89fda8e949171025b1232bae70fc9d442e4e70c8.png";
|
||||||
|
const imgLine2 = "http://localhost:3845/assets/6ee8cf4ebb6bc2adb14aab8c9940b3002c20af35.svg";
|
||||||
|
|
||||||
|
type CourseStatus = 'all' | 'completed' | 'in-progress';
|
||||||
|
|
||||||
|
interface Course {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
progress: number;
|
||||||
|
score: number;
|
||||||
|
maxScore: number;
|
||||||
|
status: 'in-progress' | 'completed' | 'not-started';
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MyLecturePage() {
|
||||||
|
const router = useRouter();
|
||||||
|
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [filter, setFilter] = useState<CourseStatus>('all');
|
||||||
|
|
||||||
|
// 샘플 강좌 데이터
|
||||||
|
const [courses] = useState<Course[]>([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: '원자로 기본 원리와 주료 계동 원리',
|
||||||
|
progress: 10,
|
||||||
|
score: 0,
|
||||||
|
maxScore: 100,
|
||||||
|
status: 'in-progress',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: '핵연료 제조 공정 및 특성',
|
||||||
|
progress: 10,
|
||||||
|
score: 0,
|
||||||
|
maxScore: 100,
|
||||||
|
status: 'in-progress',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
title: '핵분열과 핵연료 주기 이해',
|
||||||
|
progress: 10,
|
||||||
|
score: 0,
|
||||||
|
maxScore: 100,
|
||||||
|
status: 'completed',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
title: '핵연료 성능 평가와 열수력 해석',
|
||||||
|
progress: 10,
|
||||||
|
score: 0,
|
||||||
|
maxScore: 100,
|
||||||
|
status: 'not-started',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 로그인 상태 확인
|
||||||
|
const loginStatus = localStorage.getItem('isLoggedIn') === 'true';
|
||||||
|
setIsLoggedIn(loginStatus);
|
||||||
|
setIsLoading(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return null; // 로딩 중
|
||||||
|
}
|
||||||
|
|
||||||
|
// 로그인되지 않았으면 로그인 페이지 표시
|
||||||
|
if (!isLoggedIn) {
|
||||||
|
return <LoginPage />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 필터에 따른 강좌 필터링
|
||||||
|
const filteredCourses = courses.filter((course) => {
|
||||||
|
if (filter === 'all') return true;
|
||||||
|
if (filter === 'completed') return course.status === 'completed';
|
||||||
|
if (filter === 'in-progress') return course.status === 'in-progress';
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-white relative min-h-screen w-full">
|
||||||
|
{/* 헤더 */}
|
||||||
|
<header className="absolute content-stretch flex items-center justify-between left-[calc(12.5%+91px)] top-[43px] w-[1332px]">
|
||||||
|
<div className="content-stretch flex gap-[99px] items-center relative shrink-0">
|
||||||
|
{/* 로고 */}
|
||||||
|
<div className="h-[74px] relative shrink-0 w-[72px]">
|
||||||
|
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||||
|
<img alt="" className="absolute h-[291.74%] left-[-100%] max-w-none top-[-95.73%] w-[301.18%]" src={imgImage2} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* 메뉴 */}
|
||||||
|
<div className="content-stretch flex gap-[24px] items-center relative shrink-0">
|
||||||
|
<div className="content-stretch flex gap-[150px] items-center relative shrink-0">
|
||||||
|
<div className="box-border content-stretch flex gap-[10px] items-center justify-center p-[10px] relative shrink-0">
|
||||||
|
<p className="font-bold leading-[normal] not-italic relative shrink-0 text-[#515151] text-[24px] text-nowrap whitespace-pre">
|
||||||
|
교육 과정 목록
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="content-stretch flex gap-[150px] items-center relative shrink-0">
|
||||||
|
<div className="box-border content-stretch flex gap-[10px] items-center justify-center p-[10px] relative shrink-0">
|
||||||
|
<p className="font-bold leading-[normal] not-italic relative shrink-0 text-[#515151] text-[24px] text-nowrap whitespace-pre">
|
||||||
|
학습 자료실
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="content-stretch flex gap-[150px] items-center relative shrink-0">
|
||||||
|
<div className="box-border content-stretch flex gap-[10px] items-center justify-center p-[10px] relative shrink-0">
|
||||||
|
<p className="font-bold leading-[normal] not-italic relative shrink-0 text-[#515151] text-[24px] text-nowrap whitespace-pre">
|
||||||
|
공지사항
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* 사용자 메뉴 */}
|
||||||
|
<div className="content-stretch flex gap-[20px] items-center relative shrink-0">
|
||||||
|
<div className="box-border content-stretch flex gap-[10px] items-center justify-center p-[10px] relative shrink-0">
|
||||||
|
<p className="font-medium leading-[normal] not-italic relative shrink-0 text-[#1669ca] text-[18px] text-nowrap whitespace-pre">
|
||||||
|
내 강좌실
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
localStorage.removeItem('isLoggedIn');
|
||||||
|
setIsLoggedIn(false);
|
||||||
|
router.push('/');
|
||||||
|
}}
|
||||||
|
className="box-border content-stretch flex gap-[10px] items-center justify-center p-[10px] relative shrink-0 cursor-pointer group transition-colors"
|
||||||
|
>
|
||||||
|
<p className="font-medium leading-[normal] not-italic relative shrink-0 text-[#515151] text-[18px] text-nowrap whitespace-pre group-hover:text-blue-500 transition-colors">
|
||||||
|
로그아웃
|
||||||
|
</p>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* 구분선 */}
|
||||||
|
<div className="absolute h-0 left-1/2 top-[150px] translate-x-[-50%] w-full">
|
||||||
|
<div className="absolute bottom-0 left-0 right-0 top-[-1px]">
|
||||||
|
<img alt="" className="block max-w-none size-full" src={imgLine2} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 사이드바 */}
|
||||||
|
<div className="absolute box-border content-stretch flex flex-col items-center left-[37px] pb-0 pt-4 px-0 top-[192px] w-[250px]">
|
||||||
|
<div className="box-border content-stretch flex flex-col gap-2 items-start p-3 relative shrink-0 w-full">
|
||||||
|
<button
|
||||||
|
onClick={() => router.push('/myinfo')}
|
||||||
|
className="box-border content-stretch flex gap-2 h-[34px] items-center pl-2 pr-2 py-1 relative rounded-lg shrink-0 w-[226px] cursor-pointer"
|
||||||
|
>
|
||||||
|
<div className="basis-0 content-stretch flex gap-2 grow items-center min-h-px min-w-px relative shrink-0 pl-2">
|
||||||
|
<p className="[white-space-collapse:collapse] basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[14px] text-[#404040] text-left text-nowrap">
|
||||||
|
내 정보 수정
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => router.push('/mylecture')}
|
||||||
|
className="bg-[#f7f7f7] box-border content-stretch flex gap-2 h-[34px] items-center pl-2 pr-2 py-1 relative rounded-lg shrink-0 w-[226px] cursor-pointer"
|
||||||
|
>
|
||||||
|
<div className="basis-0 content-stretch flex gap-2 grow items-center min-h-px min-w-px relative shrink-0 pl-2">
|
||||||
|
<p className="[white-space-collapse:collapse] basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[14px] text-[#404040] text-left text-nowrap">
|
||||||
|
내 강좌실
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button className="box-border content-stretch flex gap-2 h-[34px] items-center pl-2 pr-2 py-1 relative rounded-lg shrink-0 w-[226px] cursor-pointer">
|
||||||
|
<div className="basis-0 content-stretch flex gap-2 grow items-center min-h-px min-w-px relative shrink-0 pl-2">
|
||||||
|
<p className="[white-space-collapse:collapse] basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[14px] text-[#404040] text-left text-nowrap">
|
||||||
|
학습 결과
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button className="box-border content-stretch flex gap-2 h-[34px] items-center pl-2 pr-2 py-1 relative rounded-lg shrink-0 w-[226px] cursor-pointer">
|
||||||
|
<div className="basis-0 content-stretch flex gap-2 grow items-center min-h-px min-w-px relative shrink-0 pl-2">
|
||||||
|
<p className="[white-space-collapse:collapse] basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[14px] text-[#404040] text-left text-nowrap">
|
||||||
|
수강 캘린더
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 메인 콘텐츠 - 내 강좌실 */}
|
||||||
|
<div className="absolute content-stretch flex flex-col gap-[32px] items-start justify-end left-[calc(12.5%+101px)] top-[196px]">
|
||||||
|
{/* 필터 섹션 */}
|
||||||
|
<div className="content-stretch flex gap-[20px] items-center relative shrink-0">
|
||||||
|
<div className="content-stretch flex flex-col gap-[20px] items-start relative shrink-0 w-[717px]">
|
||||||
|
<div className="box-border content-stretch flex gap-[10px] items-center justify-center p-[10px] relative shrink-0">
|
||||||
|
<p className="font-bold leading-[normal] not-italic relative shrink-0 text-[#515151] text-[28px] text-nowrap whitespace-pre">
|
||||||
|
수강 중인 강좌
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="content-stretch flex gap-[20px] items-center relative shrink-0">
|
||||||
|
<button
|
||||||
|
onClick={() => setFilter('all')}
|
||||||
|
className="content-stretch flex gap-[10px] items-center relative shrink-0 cursor-pointer"
|
||||||
|
>
|
||||||
|
<div className="relative shrink-0 size-[25px]">
|
||||||
|
{filter === 'all' ? <RadioOn /> : <RadioOff />}
|
||||||
|
</div>
|
||||||
|
<p className="font-medium leading-[normal] not-italic relative shrink-0 text-[#515151] text-[18px] text-nowrap whitespace-pre">
|
||||||
|
전체 보기
|
||||||
|
</p>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setFilter('completed')}
|
||||||
|
className="content-stretch flex gap-[10px] items-center relative shrink-0 cursor-pointer"
|
||||||
|
>
|
||||||
|
<div className="relative shrink-0 size-[24px]">
|
||||||
|
{filter === 'completed' ? <RadioOn /> : <RadioOff />}
|
||||||
|
</div>
|
||||||
|
<p className="font-medium leading-[normal] not-italic relative shrink-0 text-[#515151] text-[18px] text-nowrap whitespace-pre">
|
||||||
|
수강 완료한 강좌
|
||||||
|
</p>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setFilter('in-progress')}
|
||||||
|
className="content-stretch flex gap-[10px] items-center relative shrink-0 cursor-pointer"
|
||||||
|
>
|
||||||
|
<div className="relative shrink-0 size-[24px]">
|
||||||
|
{filter === 'in-progress' ? <RadioOn /> : <RadioOff />}
|
||||||
|
</div>
|
||||||
|
<p className="font-medium leading-[normal] not-italic relative shrink-0 text-[#515151] text-[18px] text-nowrap whitespace-pre">
|
||||||
|
수강 중인 강좌
|
||||||
|
</p>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 강좌 리스트 */}
|
||||||
|
<div className="border border-[#b9b9b9] border-solid box-border content-stretch flex flex-col gap-[30px] items-start p-[20px] relative rounded-[10px] shrink-0 w-[1347px]">
|
||||||
|
{filteredCourses.map((course, index) => (
|
||||||
|
<div key={course.id}>
|
||||||
|
<div className="content-stretch flex gap-[26px] items-center relative shrink-0 w-full">
|
||||||
|
{/* 썸네일 */}
|
||||||
|
<div className="bg-[#f7f7f7] box-border content-stretch flex gap-[10px] h-[165px] items-center justify-center px-[20px] py-[10px] relative rounded-[10px] shrink-0 w-[165px]">
|
||||||
|
<p className="font-bold leading-[normal] not-italic relative shrink-0 text-[#b9b9b9] text-[24px] text-nowrap whitespace-pre">
|
||||||
|
강좌 관련 썸네일
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/* 강좌 정보 */}
|
||||||
|
<div className="basis-0 content-stretch flex flex-col grow items-start justify-center min-h-px min-w-px relative shrink-0">
|
||||||
|
<div className="box-border content-stretch flex gap-[133px] items-center justify-center p-[10px] relative shrink-0 w-full">
|
||||||
|
<div className="content-stretch flex flex-col gap-[10px] items-start leading-[normal] not-italic relative shrink-0 w-[392px]">
|
||||||
|
<p className="font-bold relative shrink-0 text-[#515151] text-[20px] w-full">
|
||||||
|
{course.title}
|
||||||
|
</p>
|
||||||
|
<p className="font-medium relative shrink-0 text-[#b9b9b9] text-[18px] w-full">
|
||||||
|
진도율: {course.progress}%
|
||||||
|
</p>
|
||||||
|
<p className="font-medium relative shrink-0 text-[#b9b9b9] text-[18px] w-full">
|
||||||
|
점수: {course.score}점 / {course.maxScore}점
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/* 액션 버튼들 */}
|
||||||
|
<div className="content-stretch flex gap-[10px] items-center justify-end relative shrink-0">
|
||||||
|
{/* 상태 표시 */}
|
||||||
|
<div className="box-border content-stretch flex gap-[10px] h-[50px] items-center justify-center p-[10px] relative rounded-[10px] shrink-0 w-[264px]">
|
||||||
|
<p className="font-bold leading-[normal] not-italic relative shrink-0 text-[#f25200] text-[18px] text-nowrap whitespace-pre">
|
||||||
|
{course.status === 'in-progress' && '수강중'}
|
||||||
|
{course.status === 'completed' && '수강 완료'}
|
||||||
|
{course.status === 'not-started' && '수강 전'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/* 수강 취소 버튼 */}
|
||||||
|
{course.status !== 'completed' && (
|
||||||
|
<button className="border border-[#2b82e8] border-solid box-border content-stretch flex gap-[10px] h-[50px] items-center justify-center p-[10px] relative rounded-[10px] shrink-0 w-[124px] cursor-pointer hover:bg-blue-50 transition-colors">
|
||||||
|
<p className="font-medium leading-[normal] not-italic relative shrink-0 text-[#515151] text-[18px] text-nowrap whitespace-pre">
|
||||||
|
수강 취소
|
||||||
|
</p>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{/* 이어서 수강 / 수강하기 버튼 */}
|
||||||
|
{course.status === 'in-progress' && (
|
||||||
|
<button className="bg-[#599ded] box-border content-stretch flex gap-[10px] h-[50px] items-center justify-center p-[10px] relative rounded-[10px] shrink-0 w-[124px] cursor-pointer hover:bg-[#4a8ddc] transition-colors">
|
||||||
|
<p className="font-medium leading-[normal] not-italic relative shrink-0 text-[18px] text-nowrap text-white whitespace-pre">
|
||||||
|
이어서 수강
|
||||||
|
</p>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{course.status === 'not-started' && (
|
||||||
|
<button className="bg-[#599ded] box-border content-stretch flex gap-[10px] h-[50px] items-center justify-center p-[10px] relative rounded-[10px] shrink-0 w-[124px] cursor-pointer hover:bg-[#4a8ddc] transition-colors">
|
||||||
|
<p className="font-medium leading-[normal] not-italic relative shrink-0 text-[18px] text-nowrap text-white whitespace-pre">
|
||||||
|
수강하기
|
||||||
|
</p>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{course.status === 'completed' && (
|
||||||
|
<button className="bg-[#599ded] box-border content-stretch flex gap-[10px] h-[50px] items-center justify-center p-[10px] relative rounded-[10px] shrink-0 w-[124px] cursor-pointer hover:bg-[#4a8ddc] transition-colors">
|
||||||
|
<p className="font-medium leading-[normal] not-italic relative shrink-0 text-[18px] text-nowrap text-white whitespace-pre">
|
||||||
|
다시보기
|
||||||
|
</p>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* 구분선 */}
|
||||||
|
{index < filteredCourses.length - 1 && (
|
||||||
|
<div className="bg-[#eeeeee] h-px shrink-0 w-full mt-[30px]" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 푸터 */}
|
||||||
|
<footer className="absolute bg-[#f7f7f7] box-border content-stretch flex flex-col gap-[10px] h-[225px] items-start left-0 px-[243px] py-[39px] top-[1226px] w-full">
|
||||||
|
<div className="content-stretch flex gap-[49px] items-center relative shrink-0">
|
||||||
|
{/* 로고 */}
|
||||||
|
<div className="h-[74px] relative shrink-0 w-[72px]">
|
||||||
|
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||||
|
<img alt="" className="absolute h-[291.74%] left-[-100%] max-w-none top-[-95.73%] w-[301.18%]" src={imgImage2} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* 푸터 정보 */}
|
||||||
|
<div className="content-stretch flex flex-col gap-[5px] items-start relative shrink-0 w-[479px]">
|
||||||
|
<div className="content-stretch flex gap-[27px] items-center relative shrink-0 w-full">
|
||||||
|
<div className="box-border content-stretch flex gap-[10px] items-center justify-center p-[10px] relative shrink-0">
|
||||||
|
<p className="font-bold leading-[normal] not-italic relative shrink-0 text-[#515151] text-[18px] text-nowrap whitespace-pre">
|
||||||
|
이용 약관
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="box-border content-stretch flex gap-[10px] items-center justify-center p-[10px] relative shrink-0">
|
||||||
|
<p className="font-bold leading-[normal] not-italic relative shrink-0 text-[#515151] text-[18px] text-nowrap whitespace-pre">
|
||||||
|
개인정보처리방침
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="box-border content-stretch flex gap-[10px] items-center justify-center p-[10px] relative shrink-0">
|
||||||
|
<p className="font-bold leading-[normal] not-italic relative shrink-0 text-[#515151] text-[18px] text-nowrap whitespace-pre">
|
||||||
|
고객센터
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="box-border content-stretch flex flex-col font-medium gap-[10px] items-center justify-center leading-[normal] not-italic p-[10px] relative shrink-0 text-[#515151] text-[16px]">
|
||||||
|
<p className="relative shrink-0 w-[400px]">
|
||||||
|
(12345) 서울특별시 광진구 구의동 123-12(구의타워1)
|
||||||
|
</p>
|
||||||
|
<p className="relative shrink-0 w-[400px]">
|
||||||
|
문의: 1234-1234 (평일 09:00 ~ 18:00)
|
||||||
|
</p>
|
||||||
|
<p className="relative shrink-0 w-[400px]">
|
||||||
|
이메일: qwer1234@go.or.kr
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -7,6 +7,7 @@ import RadioOff from '../../public/svg/radio_off';
|
|||||||
import RadioOn from '../../public/svg/radio_on';
|
import RadioOn from '../../public/svg/radio_on';
|
||||||
import CheckOff from '../../public/svg/check_off';
|
import CheckOff from '../../public/svg/check_off';
|
||||||
import CheckOn from '../../public/svg/check_on';
|
import CheckOn from '../../public/svg/check_on';
|
||||||
|
import ChevronSmall from '../../public/svg/chevron_small';
|
||||||
|
|
||||||
const imgRiCheckboxCircleLine = "http://localhost:3845/assets/e4c498605e2559d2764a3112ae9a9019e6ad798e.svg";
|
const imgRiCheckboxCircleLine = "http://localhost:3845/assets/e4c498605e2559d2764a3112ae9a9019e6ad798e.svg";
|
||||||
const imgFormkitRadio = "http://localhost:3845/assets/ea30a9a80d95ced4bfb1174d3a8475a4a1dbbabb.svg";
|
const imgFormkitRadio = "http://localhost:3845/assets/ea30a9a80d95ced4bfb1174d3a8475a4a1dbbabb.svg";
|
||||||
@@ -46,6 +47,7 @@ export default function RegisterPage() {
|
|||||||
const [isVerificationSent, setIsVerificationSent] = useState(false);
|
const [isVerificationSent, setIsVerificationSent] = useState(false);
|
||||||
const [isVerificationComplete, setIsVerificationComplete] = useState(false);
|
const [isVerificationComplete, setIsVerificationComplete] = useState(false);
|
||||||
const [verificationError, setVerificationError] = useState('');
|
const [verificationError, setVerificationError] = useState('');
|
||||||
|
const [activeSelect, setActiveSelect] = useState<string | null>(null);
|
||||||
|
|
||||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
@@ -66,6 +68,32 @@ export default function RegisterPage() {
|
|||||||
setVerificationCode('');
|
setVerificationCode('');
|
||||||
setIsVerificationComplete(false);
|
setIsVerificationComplete(false);
|
||||||
}
|
}
|
||||||
|
// 비밀번호 확인 실시간 검증
|
||||||
|
if (name === 'passwordConfirm' && value && formData.password && value !== formData.password) {
|
||||||
|
setErrors((prev) => ({
|
||||||
|
...prev,
|
||||||
|
passwordConfirm: '비밀번호와 일치하지 않아요.',
|
||||||
|
}));
|
||||||
|
} else if (name === 'passwordConfirm' && value && formData.password && value === formData.password) {
|
||||||
|
setErrors((prev) => ({
|
||||||
|
...prev,
|
||||||
|
passwordConfirm: '',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
// 비밀번호가 변경되면 비밀번호 확인도 다시 검증
|
||||||
|
if (name === 'password' && formData.passwordConfirm) {
|
||||||
|
if (formData.passwordConfirm !== value) {
|
||||||
|
setErrors((prev) => ({
|
||||||
|
...prev,
|
||||||
|
passwordConfirm: '비밀번호와 일치하지 않아요.',
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
setErrors((prev) => ({
|
||||||
|
...prev,
|
||||||
|
passwordConfirm: '',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePhoneChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handlePhoneChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
@@ -258,7 +286,7 @@ export default function RegisterPage() {
|
|||||||
name="name"
|
name="name"
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className={`flex-1 h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border border-[#b9b9b9] placeholder:text-[#b9b9b9] bg-white ${errors.name ? 'border-[#E85D5D] text-[#E85D5D]' : 'text-[#515151]'}`}
|
className={`flex-1 h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border placeholder:text-[#b9b9b9] bg-white focus:border-[#1669CA] focus:outline-none ${errors.name ? 'border-[#E85D5D] text-[#E85D5D]' : 'border-[#b9b9b9] text-[#515151]'}`}
|
||||||
placeholder="이름을 입력해 주세요."
|
placeholder="이름을 입력해 주세요."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -279,7 +307,7 @@ export default function RegisterPage() {
|
|||||||
value={formData.phone}
|
value={formData.phone}
|
||||||
onChange={handlePhoneChange}
|
onChange={handlePhoneChange}
|
||||||
maxLength={11}
|
maxLength={11}
|
||||||
className={`flex-1 h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border border-[#b9b9b9] placeholder:text-[#b9b9b9] bg-white ${errors.phone ? 'border-[#E85D5D] text-[#E85D5D]' : 'text-[#515151]'}`}
|
className={`flex-1 h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border placeholder:text-[#b9b9b9] bg-white focus:border-[#1669CA] focus:outline-none ${errors.phone ? 'border-[#E85D5D] text-[#E85D5D]' : 'border-[#b9b9b9] text-[#515151]'}`}
|
||||||
placeholder="-없이 입력해 주세요."
|
placeholder="-없이 입력해 주세요."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -299,7 +327,7 @@ export default function RegisterPage() {
|
|||||||
name="email"
|
name="email"
|
||||||
value={formData.email}
|
value={formData.email}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className={`h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border border-[#b9b9b9] placeholder:text-[#b9b9b9] bg-white w-[401px] ${errors.email ? 'border-[#E85D5D] text-[#E85D5D]' : 'text-[#515151]'}`}
|
className={`h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border placeholder:text-[#b9b9b9] bg-white w-[401px] focus:border-[#1669CA] focus:outline-none ${errors.email ? 'border-[#E85D5D] text-[#E85D5D]' : 'border-[#b9b9b9] text-[#515151]'}`}
|
||||||
placeholder="이메일을 입력해 주세요."
|
placeholder="이메일을 입력해 주세요."
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
@@ -325,7 +353,7 @@ export default function RegisterPage() {
|
|||||||
type="text"
|
type="text"
|
||||||
value={verificationCode}
|
value={verificationCode}
|
||||||
onChange={handleVerificationCodeChange}
|
onChange={handleVerificationCodeChange}
|
||||||
className="h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border border-[#1669ca] text-[#515151] bg-white w-[401px]"
|
className="h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border border-[#1669ca] text-[#515151] bg-white w-[401px] focus:border-[#1669CA] focus:outline-none"
|
||||||
placeholder="인증번호를 입력해 주세요."
|
placeholder="인증번호를 입력해 주세요."
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
@@ -372,7 +400,7 @@ export default function RegisterPage() {
|
|||||||
name="password"
|
name="password"
|
||||||
value={formData.password}
|
value={formData.password}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className={`flex-1 h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border border-[#b9b9b9] placeholder:text-[#b9b9b9] bg-white ${errors.password ? 'border-[#E85D5D] text-[#E85D5D]' : 'text-[#515151]'}`}
|
className={`flex-1 h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border placeholder:text-[#b9b9b9] bg-white focus:border-[#1669CA] focus:outline-none ${errors.password ? 'border-[#E85D5D] text-[#E85D5D]' : 'border-[#b9b9b9] text-[#515151]'}`}
|
||||||
placeholder="8~16자의 영문/숫자/특수문자를 조합해서 입력해 주세요."
|
placeholder="8~16자의 영문/숫자/특수문자를 조합해서 입력해 주세요."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -392,11 +420,19 @@ export default function RegisterPage() {
|
|||||||
name="passwordConfirm"
|
name="passwordConfirm"
|
||||||
value={formData.passwordConfirm}
|
value={formData.passwordConfirm}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className={`flex-1 h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border border-[#b9b9b9] placeholder:text-[#b9b9b9] bg-white ${errors.passwordConfirm ? 'border-[#E85D5D] text-[#E85D5D]' : 'text-[#515151]'}`}
|
className={`flex-1 h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border placeholder:text-[#b9b9b9] bg-white text-[#000000] focus:outline-none ${formData.passwordConfirm && formData.password && formData.passwordConfirm !== formData.password
|
||||||
|
? 'border-[#E61A1A] focus:border-[#E61A1A]'
|
||||||
|
: errors.passwordConfirm
|
||||||
|
? 'border-[#E85D5D] focus:border-[#1669CA]'
|
||||||
|
: 'border-[#b9b9b9] focus:border-[#1669CA]'
|
||||||
|
}`}
|
||||||
placeholder="비밀번호를 다시 입력해 주세요."
|
placeholder="비밀번호를 다시 입력해 주세요."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{errors.passwordConfirm && (
|
{formData.passwordConfirm && formData.password && formData.passwordConfirm !== formData.password && (
|
||||||
|
<p className="text-[13px] text-[#E61A1A] ml-[193px] mt-[16px]">비밀번호와 일치하지 않아요.</p>
|
||||||
|
)}
|
||||||
|
{errors.passwordConfirm && formData.passwordConfirm === formData.password && (
|
||||||
<p className="text-[13px] text-[#E85D5D] ml-[193px] mt-[16px]">{errors.passwordConfirm}</p>
|
<p className="text-[13px] text-[#E85D5D] ml-[193px] mt-[16px]">{errors.passwordConfirm}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -441,55 +477,58 @@ export default function RegisterPage() {
|
|||||||
name="birthYear"
|
name="birthYear"
|
||||||
value={formData.birthYear}
|
value={formData.birthYear}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full h-[42px] px-[10px] text-[18px] font-medium border border-[#b9b9b9] rounded-[8px] bg-white text-[#515151] appearance-none pr-[30px]"
|
onFocus={() => setActiveSelect('birthYear')}
|
||||||
|
onBlur={() => setActiveSelect(null)}
|
||||||
|
className="w-full h-[42px] px-[10px] pr-[30px] text-[18px] font-medium border border-[#b9b9b9] rounded-[8px] bg-white text-[#515151] appearance-none focus:border-[#1669CA] focus:outline-none"
|
||||||
|
style={{ color: formData.birthYear ? '#515151' : '#b9b9b9' }}
|
||||||
>
|
>
|
||||||
<option value="" className="text-[#b9b9b9]">년도</option>
|
<option value="" style={{ color: '#b9b9b9' }}>년도</option>
|
||||||
{[...Array(100)].map((_, idx) => {
|
{[...Array(100)].map((_, idx) => {
|
||||||
const year = new Date().getFullYear() - idx;
|
const year = new Date().getFullYear() - idx;
|
||||||
return <option key={year} value={year}>{year}</option>
|
return <option key={year} value={year} style={{ color: '#515151' }}>{year}</option>
|
||||||
})}
|
})}
|
||||||
</select>
|
</select>
|
||||||
<img
|
<div className={`absolute right-[10px] top-1/2 -translate-y-1/2 pointer-events-none transition-transform ${activeSelect === 'birthYear' ? 'rotate-180' : ''}`}>
|
||||||
src={imgLsiconDownFilled}
|
<ChevronSmall />
|
||||||
alt=""
|
</div>
|
||||||
className="absolute right-[10px] top-1/2 -translate-y-1/2 w-[16px] h-[16px] pointer-events-none"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="relative flex-1">
|
<div className="relative flex-1">
|
||||||
<select
|
<select
|
||||||
name="birthMonth"
|
name="birthMonth"
|
||||||
value={formData.birthMonth}
|
value={formData.birthMonth}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full h-[42px] px-[10px] text-[18px] font-medium border border-[#b9b9b9] rounded-[8px] bg-white text-[#515151] appearance-none pr-[30px]"
|
onFocus={() => setActiveSelect('birthMonth')}
|
||||||
|
onBlur={() => setActiveSelect(null)}
|
||||||
|
className="w-full h-[42px] px-[10px] pr-[30px] text-[18px] font-medium border border-[#b9b9b9] rounded-[8px] bg-white text-[#515151] appearance-none focus:border-[#1669CA] focus:outline-none"
|
||||||
|
style={{ color: formData.birthMonth ? '#515151' : '#b9b9b9' }}
|
||||||
>
|
>
|
||||||
<option value="" className="text-[#b9b9b9]">월</option>
|
<option value="" style={{ color: '#b9b9b9' }}>월</option>
|
||||||
{[...Array(12)].map((_, idx) => (
|
{[...Array(12)].map((_, idx) => (
|
||||||
<option key={idx + 1} value={idx + 1}>{idx + 1}</option>
|
<option key={idx + 1} value={idx + 1} style={{ color: '#515151' }}>{idx + 1}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
<img
|
<div className={`absolute right-[10px] top-1/2 -translate-y-1/2 pointer-events-none transition-transform ${activeSelect === 'birthMonth' ? 'rotate-180' : ''}`}>
|
||||||
src={imgLsiconDownFilled}
|
<ChevronSmall />
|
||||||
alt=""
|
</div>
|
||||||
className="absolute right-[10px] top-1/2 -translate-y-1/2 w-[16px] h-[16px] pointer-events-none"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="relative flex-1">
|
<div className="relative flex-1">
|
||||||
<select
|
<select
|
||||||
name="birthDay"
|
name="birthDay"
|
||||||
value={formData.birthDay}
|
value={formData.birthDay}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full h-[42px] px-[10px] text-[18px] font-medium border border-[#b9b9b9] rounded-[8px] bg-white text-[#515151] appearance-none pr-[30px]"
|
onFocus={() => setActiveSelect('birthDay')}
|
||||||
|
onBlur={() => setActiveSelect(null)}
|
||||||
|
className="w-full h-[42px] px-[10px] pr-[30px] text-[18px] font-medium border border-[#b9b9b9] rounded-[8px] bg-white text-[#515151] appearance-none focus:border-[#1669CA] focus:outline-none"
|
||||||
|
style={{ color: formData.birthDay ? '#515151' : '#b9b9b9' }}
|
||||||
>
|
>
|
||||||
<option value="" className="text-[#b9b9b9]">일</option>
|
<option value="" style={{ color: '#b9b9b9' }}>일</option>
|
||||||
{[...Array(31)].map((_, idx) => (
|
{[...Array(31)].map((_, idx) => (
|
||||||
<option key={idx + 1} value={idx + 1}>{idx + 1}</option>
|
<option key={idx + 1} value={idx + 1} style={{ color: '#515151' }}>{idx + 1}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
<img
|
<div className={`absolute right-[10px] top-1/2 -translate-y-1/2 pointer-events-none transition-transform ${activeSelect === 'birthDay' ? 'rotate-180' : ''}`}>
|
||||||
src={imgLsiconDownFilled}
|
<ChevronSmall />
|
||||||
alt=""
|
</div>
|
||||||
className="absolute right-[10px] top-1/2 -translate-y-1/2 w-[16px] h-[16px] pointer-events-none"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,81 +4,33 @@ import Link from 'next/link';
|
|||||||
|
|
||||||
export default function RegisterCompletePage() {
|
export default function RegisterCompletePage() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex items-center justify-center bg-white">
|
<div className="relative min-h-screen flex flex-col items-center bg-white">
|
||||||
<div className="w-full max-w-2xl px-4">
|
{/* 메인 콘텐츠 영역 - 카피라이트와 최상단 사이 중앙 */}
|
||||||
{/* 회원가입 완료 카드 */}
|
<div className="flex-1 flex flex-col justify-center items-center w-full">
|
||||||
<div className="bg-white rounded-lg shadow-lg p-8">
|
{/* 완료 메시지 */}
|
||||||
{/* 제목 */}
|
<div className="flex flex-col gap-[33px] items-center">
|
||||||
<h1 className="text-2xl font-bold text-center mb-8">회원가입</h1>
|
<p className="font-bold text-[#515151] text-[32px] leading-normal text-center">
|
||||||
|
회원가입을 완료했어요.
|
||||||
{/* 단계 표시 */}
|
</p>
|
||||||
<div className="flex items-center justify-center mb-8 text-sm">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<span className="px-3 py-1 text-gray-500">01 약관 동의</span>
|
|
||||||
<span className="mx-2 text-gray-400">></span>
|
|
||||||
<span className="px-3 py-1 text-gray-500">02 회원정보입력</span>
|
|
||||||
<span className="mx-2 text-gray-400">></span>
|
|
||||||
<span className="px-3 py-1 bg-blue-500 text-white rounded-md font-semibold">
|
|
||||||
03 회원가입완료
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 완료 메시지 */}
|
|
||||||
<div className="text-center py-12">
|
|
||||||
{/* 체크 아이콘 */}
|
|
||||||
<div className="mb-6 flex justify-center">
|
|
||||||
<div className="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center">
|
|
||||||
<svg
|
|
||||||
className="w-12 h-12 text-green-500"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth={2}
|
|
||||||
d="M5 13l4 4L19 7"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 완료 메시지 */}
|
|
||||||
<h2 className="text-2xl font-bold mb-4 text-gray-800">
|
|
||||||
회원가입이 완료되었습니다!
|
|
||||||
</h2>
|
|
||||||
<p className="text-gray-600 mb-8">
|
|
||||||
XL LMS에 오신 것을 환영합니다.
|
|
||||||
<br />
|
|
||||||
이제 다양한 강의를 수강하실 수 있습니다.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{/* 버튼 영역 */}
|
|
||||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
|
||||||
<Link
|
|
||||||
href="/login"
|
|
||||||
className="px-8 py-3 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition font-medium text-center"
|
|
||||||
>
|
|
||||||
로그인하기
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
href="/"
|
|
||||||
className="px-8 py-3 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition font-medium text-center"
|
|
||||||
>
|
|
||||||
홈으로 가기
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 카피라이트 */}
|
{/* 로그인 버튼 */}
|
||||||
<div className="mt-8 text-center text-sm text-gray-500">
|
<div className="mt-[46px]">
|
||||||
Copyright © 2025 XL LMS. All rights reserved
|
<Link
|
||||||
|
href="/login"
|
||||||
|
className="bg-[#2B82E8] rounded-[10px] h-[55px] w-[334px] flex items-center justify-center font-medium text-white text-[18px] hover:bg-[#1669ca] transition"
|
||||||
|
>
|
||||||
|
로그인
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 카피라이트 */}
|
||||||
|
<footer className="absolute bottom-[51.5px] left-1/2 -translate-x-1/2 flex flex-col items-center">
|
||||||
|
<p className="text-[16px] text-[rgba(0,0,0,0.55)] leading-[1.45] font-medium tracking-[-0.08px]">
|
||||||
|
Copyright ⓒ 2025 XL LMS. All rights reserved
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default function ChevronSmall() {
|
export default function ChevronSmall() {
|
||||||
return (
|
return (
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 10.2075L11.854 6.35448L11.147 5.64648L8 8.79348L4.854 5.64648L4.146 6.35448L8 10.2075Z" fill="#2B82E8" />
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 10.2075L11.854 6.35448L11.147 5.64648L8 8.79348L4.854 5.64648L4.146 6.35448L8 10.2075Z" fill="#000000" />
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user