From b439d567442c9764a7731a3d698d024a54fea627 Mon Sep 17 00:00:00 2001 From: wallace Date: Thu, 6 Nov 2025 16:42:55 +0900 Subject: [PATCH] =?UTF-8?q?login=20page=20=EB=A7=88=EB=AC=B4=EB=A6=AC?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/login/page.tsx | 249 +++++++++++++++++++++++++++++++++++++-------- app/logo.svg | 9 ++ app/page.tsx | 187 +++++++--------------------------- next.config.ts | 4 +- public/Divider.svg | 3 + public/logo.png | Bin 2440 -> 0 bytes public/logo.svg | 9 ++ 7 files changed, 268 insertions(+), 193 deletions(-) create mode 100644 app/logo.svg create mode 100644 public/Divider.svg delete mode 100644 public/logo.png create mode 100644 public/logo.svg diff --git a/app/login/page.tsx b/app/login/page.tsx index 715ed38..0956d4e 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -1,84 +1,253 @@ +"use client"; + import Link from 'next/link'; +import Image from 'next/image'; +import { useState, useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import logo from '../logo.svg'; + +const checkIcon = "http://localhost:3845/assets/68720b08a673d8b68ae6482d642eeab286c9462b.svg"; + +type CheckboxProps = { + checked: boolean; + onChange: () => void; + label: string; +}; + +function Checkbox({ checked, onChange, label }: CheckboxProps) { + return ( +
+ + {label} +
+ ); +} export default function LoginPage() { + const router = useRouter(); + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [rememberId, setRememberId] = useState(false); + const [autoLogin, setAutoLogin] = useState(false); + const [usernameError, setUsernameError] = useState(''); + const [passwordError, setPasswordError] = useState(''); + const [showErrorPopup, setShowErrorPopup] = useState(false); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + setUsernameError(''); + setPasswordError(''); + + // 아이디와 비밀번호가 비어있는지 확인 + const isUsernameEmpty = !username.trim(); + const isPasswordEmpty = !password.trim(); + + if (isUsernameEmpty) { + setUsernameError('이메일을 입력해 주세요.'); + } + + if (isPasswordEmpty) { + setPasswordError('비밀번호를 입력해 주세요.'); + } + + // 둘 중 하나라도 비어있으면 검증 중단 + if (isUsernameEmpty || isPasswordEmpty) { + return; + } + + // 아이디와 비밀번호 검증 + if (username === 'admin' && password === '1234') { + // 로그인 성공 + localStorage.setItem('isLoggedIn', 'true'); + // 아이디 기억하기 체크 시 아이디 저장 + if (rememberId) { + localStorage.setItem('rememberedUsername', username); + } else { + localStorage.removeItem('rememberedUsername'); + } + // 루트 경로로 이동 (루트 페이지에서 로그인 상태를 확인하여 메인 페이지 표시) + window.location.href = '/'; + } else { + // 로그인 실패 - 팝업 표시 + setShowErrorPopup(true); + setUsernameError('아이디 또는 비밀번호가 올바르지 않습니다.'); + setPasswordError('아이디 또는 비밀번호가 올바르지 않습니다.'); + } + }; + + // 아이디 기억하기 기능 - 컴포넌트 마운트 시 저장된 아이디 불러오기 + useEffect(() => { + const rememberedUsername = localStorage.getItem('rememberedUsername'); + if (rememberedUsername) { + setUsername(rememberedUsername); + setRememberId(true); + } + }, []); + return ( -
+
{/* 상단 로고 */}
-

Logo

+ Logo
{/* 로그인 폼 */} -
+ {/* 아이디 입력폼 */} -
- - +
+
+ { + setUsername(e.target.value); + setUsernameError(''); + }} + className={`w-full px-4 py-2.5 border rounded-[8px] focus:outline-none ${usernameError + ? 'border-[#e61a1a] border-solid' + : 'border-gray-300 focus:ring-2 focus:ring-blue-500' + }`} + placeholder="아이디(이메일)" + /> + {usernameError && ( +

+ {usernameError} +

+ )} +
{/* 비밀번호 입력폼 */} -
- - +
+
+ { + setPassword(e.target.value); + setPasswordError(''); + }} + className={`w-full px-4 py-2.5 border rounded-[8px] focus:outline-none ${passwordError + ? 'border-[#e61a1a] border-solid' + : 'border-gray-300 focus:ring-2 focus:ring-blue-500' + }`} + placeholder="비밀번호를 입력하세요" + /> + {passwordError && ( +

+ {passwordError} +

+ )} +
{/* 체크박스 */} -
- - +
+ setRememberId(!rememberId)} + label="아이디 기억하기" + /> + setAutoLogin(!autoLogin)} + label="자동 로그인" + />
{/* 로그인 버튼 */} - +
+ +
{/* 하단 링크 버튼들 */} -
+
회원가입 + +
{/* 카피라이트 */} -
+
© 2024 XR LMS. All rights reserved.
+ + {/* 에러 팝업 */} + {showErrorPopup && ( +
+
+
+
+

아이디 또는 비밀번호가 일치하지 않아요.

+

확인 후 다시 시도해 주세요.

+
+
+
+ +
+
+
+ )}
); } diff --git a/app/logo.svg b/app/logo.svg new file mode 100644 index 0000000..b5adb10 --- /dev/null +++ b/app/logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/page.tsx b/app/page.tsx index d7dacaa..e637e42 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,164 +1,47 @@ "use client"; -import Link from "next/link"; +import { useEffect, useState } from 'react'; +import { useRouter } from 'next/navigation'; +import LoginPage from './login/page'; -// 이미지 상수 -const imgLogo = "http://localhost:3845/assets/89fda8e949171025b1232bae70fc9d442e4e70c8.png"; +export default function HomePage() { + const router = useRouter(); + const [isLoggedIn, setIsLoggedIn] = useState(false); + const [isLoading, setIsLoading] = useState(true); -type CheckboxProps = { - className?: string; - status?: "Inactive" | "Focused" | "Disabled"; -}; + useEffect(() => { + // 로그인 상태 확인 + const loginStatus = localStorage.getItem('isLoggedIn') === 'true'; + setIsLoggedIn(loginStatus); + setIsLoading(false); + }, []); -function Checkbox({ className, status = "Inactive" }: CheckboxProps) { + if (isLoading) { + return null; // 로딩 중 + } + + // 로그인되지 않았으면 로그인 페이지 표시 + if (!isLoggedIn) { + return ; + } + + // 로그인되었으면 메인 페이지 표시 return ( -
-
-
- ); -} +
+
+

메인 페이지

+

로그인 후 메인 페이지입니다.

-export default function LoginPage() { - return ( -
- {/* 로고 */} -
-
- Logo -
-
- - {/* 콘텐츠 영역 */} -
- {/* 입력 폼 영역 */} -
- {/* 아이디 입력 필드 */} -
-
-
-
- -
-
-
-
- - {/* 비밀번호 입력 필드 */} -
-
-
-
- -
-
-
-
- - {/* 체크박스 영역 */} -
-
- -

- 아이디 기억하기 -

-
-
- -

- 자동 로그인 -

-
-
-
- - {/* 로그인 버튼 */} -
- -
-
- - {/* 하단 링크 영역 */} -
- { + localStorage.removeItem('isLoggedIn'); + router.push('/'); + }} + className="mt-4 px-4 py-2 bg-red-500 text-white rounded-md hover:bg-red-600" > -

- 회원가입 -

- -
-

- | -

-
- -
-

- | -

-
- -
- - {/* 카피라이트 */} -
-

- Copyright ⓒ 2025 XL LMS. All rights reserved -

); diff --git a/next.config.ts b/next.config.ts index e9ffa30..b60527b 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,9 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + images: { + unoptimized: false, + }, }; export default nextConfig; diff --git a/public/Divider.svg b/public/Divider.svg new file mode 100644 index 0000000..54d4313 --- /dev/null +++ b/public/Divider.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/logo.png b/public/logo.png deleted file mode 100644 index 4add0d7ad838d8d6407ee91f22ee8ef01534e229..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2440 zcmV;333v91P)=uqivN>6xQ zN2XV|PWQ+3I(DO8cq7b!mW5S2bBEtPWoJlQ<5im@D6tqgQgfTpET^YAa$)bxgFn9a z6nIW({}{0Sx8Pk6XfV2_wZ^_>*rI5wR8*rc5-1foKx$wPm^&&>6K2IOZ4Yf;G3UU6 zm63$gFhCn9c;X+b@H>B*mpSdejjCO9h=C%KP$KX@ghUK*MSuxB3j&tVlE)Ptn!(86 zt_v$SuDs>B{&T@~+Vz4r<4v?>>7w717R|5R)!Igj*cBy-WQ-sQ5V$4?+>JUq!KHgm zI9~I(298c}sU$=dD;~nd`R4cLZ~y(>Z@>VkbUonR6i=M^gcV)9ViPUOdN6)oN~#!j z!6HD6v<6AEMxGC1jO7i2vsc1UBCbef2}aBq8=bQpbi_C&q_hNOMcHs-&%Rv`zWw%> zj`~gn%0dwNN~BeetNw6_^P@E}&4zqUozu#~Xfuo`bCAr2*A7S<=Pb;E7J6vg_=cxQ zDV%Sb^qD-?M&gR3C@tI!AaJHYbIB-0XItBK+3h3lJMzfPmWI;OM{WPTJP5F)V~PxYF;_XC~aZ=z1jQ~JrJ=U4QEi@qC)DP?Pl(MMHMmZwDQ;|#gYydu;zj!};bVOyFGwdLU}udUDafioYlTYM8HuO9DD zA^m&2rn>lhM-MMZE6a-S$Q^{^rBVo7A&r3JlT1@rVlT*0Qfz@DKY53abRUPb1XWD* zQ#i!+;rFNRUUS#G;PF#HKiOOObmV<2CGGP=IOw+K=6Ut?zrZkmL`s|dZ z_RCJT4VK9O7{w|`2lA6QXJvMFZvE(5xB^8!b_OHj7N#qC$XF~k4Yq1K)dF6$MFQB= z%G&&=mg{IwJY`bkBTyIHgv!Vo{o6&4M^9FK;nfo8%M59F=ZnV=oL)CRlUWR-Z>R(2 zpaB&>5csqdPq&R`b5s>8hGFY;M8WihCn_r|%@7=ck$mXDtkSWSE$7ZToTD`2SZXOw zogS^DQTx`i;)hSY`qPf3zO%*&)zS5LW@8?V`cNM{NAMXcniN~GPVZ6=aHGv}JYrx# zf$doQ$~ggr6}2aEMC{RLLRGoNxBW5HBAgUqaaR%@KU)epAJe=z2hP9pyBF3%Ke~2) zcv5F_qc(j}H>sqACta+PYN_jCHdYoAgvH&(NHdQ*_##dZ?{!MPzEq#Dvk)h|?cp@Lu*2z*LL zmPZlc@!1mIQf{K{cZhBl61|FzBnpS@`K!Juwu9S9^k$enymiow`~nCW6>^K;mqu@O zb(QJH>O2j(kvCD*uKWp)Dkl0=E5|m?nNpH^Knx8C$4>rYm;Ulg3)N2O)WdDa%*3b&+5jg?IT=80sm{#EKqco0#s^^v zm~qsf_AWVf&0Mw3cfc~|y1E6>+S!;sBGpo$aBHQ!dg;}9bl`FTT!Fw3zcmefZWEs_ z=!g^iTcSoqJXus4AJR@HFe`q!{h8(K2AeG$`@;v5 zV(qjsV`wVk*HZZ0)o#X+WZ@3^z^@mmt0zTW0k|iWiAD=4LCtRV z(giuAuF`eD@f}cgN50*);n>S%qf>44H!g6eBhzulJF;?<3TB{fU>a!}IWnJ}HCsHS z3(YGK4b{uDo`Ak0H0Hi+-JuZ|Tb$;i;Wjz|!YfEG3)rr3+14s&)X(pAz*WVK#}=P$ zjPAYdbAxSbR4R+V)w%zxjhDA1rG21npzoxdGy9#2lAQ9ztACX{7yfne`L{WruRK#a zCQ@?hA2A^;$vHZq{fTfhE?8CpYpC2zXM#^5<9x-0s=UndBOP^hbqWG}4!NGM2mL=y zm=`+9=6&IT<73Csk%wA(9APQTUH*-nq8EzA1RFCJHr@M*RS5&M>-qTU?m_+EPLi?i zxEHsLI=nw}4co8WS_)qi;H2KXmlP&t#dpUm<1)Y=2D6@G%r0H~gf3^=iv4#9Z zn;WF5rB#FcYXkbGW}STO<^8XNG=B6da|O9au7TtK$NmXA$xVBioi_Ra0000 + + + + + + + +