wr 개발 로그인 페이지 추가
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 132 KiB |
@@ -1,7 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { client } from "../../../shared/api";
|
||||
import type { IUserInfo } from "../../../shared/store";
|
||||
import { useUserStore } from "../../../shared/lib";
|
||||
@@ -42,16 +43,17 @@ const AuthWrapper = ({ children }: { children: React.ReactNode }) => {
|
||||
} catch (refreshError) {
|
||||
console.error("Token refresh failed:", refreshError);
|
||||
setUserInfo(null);
|
||||
|
||||
|
||||
const hostname = window.location.hostname;
|
||||
|
||||
// 개발 환경에서는 localhost 도메인 사용
|
||||
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "con.mind-one.co.kr")
|
||||
router.push("/"); // 개발시 홈으로 돌아가기
|
||||
router.push("/auth/login");
|
||||
|
||||
// 운영 환경에서는 서브도메인을 포함한 최상위 도메인 사용
|
||||
// router.push는 같은 Next.js 앱 내에서만 작동하므로 다른 앱으로 이동할 때는 window.location.href 사용
|
||||
if (hostname.includes("hasuon.or.kr")) window.location.href = LOGIN_PAGE_URL; // 세션 만료 시 CM 로그인 페이지로 리다이렉트
|
||||
if (hostname.includes("hasuon.or.kr"))
|
||||
window.location.href = LOGIN_PAGE_URL; // 세션 만료 시 CM 로그인 페이지로 리다이렉트
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -1,321 +1,130 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import Image from "next/image";
|
||||
import { images } from "@common/assets";
|
||||
import { LoginApi } from "@/src/entities/auth";
|
||||
import { ENDPOINT } from "@/src/shared/config";
|
||||
import OtpModal from "./OtpModal";
|
||||
import TwoFAModal from "./TwoFAModal";
|
||||
import UserSignupModal from "./UserSignupModal";
|
||||
import "./login.css";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
function LoginPage() {
|
||||
const [isOtpEnabled, setIsOtpEnabled] = useState(false);
|
||||
const [showOtpModal, setShowOtpModal] = useState(false);
|
||||
const [showTwoFAModal, setShowTwoFAModal] = useState(false);
|
||||
const [showSignupModal, setShowSignupModal] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const [successMessage, setSuccessMessage] = useState("");
|
||||
const router = useRouter();
|
||||
|
||||
// 모달 상태 디버깅
|
||||
useEffect(() => {
|
||||
console.log("Modal states - OTP:", showOtpModal, "2FA:", showTwoFAModal);
|
||||
}, [showOtpModal, showTwoFAModal]);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setIsLoading(true);
|
||||
setError("");
|
||||
setSuccessMessage("");
|
||||
|
||||
try {
|
||||
const formData = new FormData(e.target as HTMLFormElement);
|
||||
const userId = formData.get("id") as string;
|
||||
const password = formData.get("password") as string;
|
||||
const formData = new FormData(e.target as HTMLFormElement);
|
||||
const userId = formData.get("id") as string;
|
||||
const password = formData.get("password") as string;
|
||||
|
||||
console.log("Login attempt - OTP enabled:", isOtpEnabled);
|
||||
console.log("User ID:", userId);
|
||||
|
||||
if (isOtpEnabled) {
|
||||
// 2FA 로그인 - Proxy를 통해 처리 (2FA 전용 엔드포인트 사용)
|
||||
console.log("Starting 2FA login...");
|
||||
|
||||
const response = await fetch(`/api/proxy${ENDPOINT.USER_SERVICE.LOGIN_2FA}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
userId,
|
||||
password,
|
||||
provider: 'password'
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
let errorMsg = errorData.message || '로그인 실패';
|
||||
if (Array.isArray(errorData.errors) && errorData.errors.length > 0) {
|
||||
errorMsg = errorData.errors.map((e: { message: string }) => e.message).join(', ');
|
||||
}
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log("2FA login response:", data);
|
||||
console.log("Response headers:", Object.fromEntries(response.headers.entries()));
|
||||
|
||||
// 2FA 로그인 응답에는 tempToken과 registered2FA만 포함됨
|
||||
if (!data.tempToken) {
|
||||
throw new Error("Missing tempToken in 2FA login response");
|
||||
}
|
||||
|
||||
// 임시 토큰을 localStorage에 저장 (OTP 인증용)
|
||||
localStorage.setItem('tempToken', data.tempToken);
|
||||
console.log("tempToken saved:", data.tempToken);
|
||||
|
||||
if (data.registered2FA) {
|
||||
console.log("User has 2FA registered, showing OTP modal");
|
||||
setShowOtpModal(true);
|
||||
} else {
|
||||
console.log("User doesn't have 2FA registered, showing 2FA setup modal");
|
||||
setShowTwoFAModal(true);
|
||||
}
|
||||
} else {
|
||||
// 기본 로그인 - proxy를 통해 처리
|
||||
console.log("Starting basic login...");
|
||||
await LoginApi.login({ userId, password });
|
||||
window.location.href = "/map/mainMap";
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
console.error("Login error:", error);
|
||||
|
||||
// 에러 처리
|
||||
if (error instanceof Error) {
|
||||
setError(error.message);
|
||||
} else {
|
||||
setError("로그인에 실패했습니다. 아이디와 비밀번호를 확인해주세요.");
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOtpSuccess = () => {
|
||||
console.log("OTP authentication successful, redirecting to dashboard");
|
||||
localStorage.removeItem("tempToken"); // 임시 토큰 제거
|
||||
router.push("/dashboard");
|
||||
};
|
||||
|
||||
const handleTwoFASuccess = () => {
|
||||
console.log("2FA setup successful, redirecting to dashboard");
|
||||
localStorage.removeItem("tempToken"); // 임시 토큰 제거
|
||||
router.push("/dashboard");
|
||||
};
|
||||
|
||||
const handleOtpClose = () => {
|
||||
console.log("OTP modal closed");
|
||||
setShowOtpModal(false);
|
||||
localStorage.removeItem("tempToken");
|
||||
};
|
||||
|
||||
const handleTwoFAClose = () => {
|
||||
console.log("2FA modal closed");
|
||||
setShowTwoFAModal(false);
|
||||
localStorage.removeItem("tempToken");
|
||||
};
|
||||
|
||||
const handleSignupSuccess = () => {
|
||||
console.log("회원가입 성공");
|
||||
setError(""); // 에러 메시지 초기화
|
||||
setSuccessMessage("회원가입이 완료되었습니다. 로그인해주세요.");
|
||||
};
|
||||
|
||||
const handleSignupClose = () => {
|
||||
console.log("회원가입 모달 닫기");
|
||||
setShowSignupModal(false);
|
||||
setSuccessMessage(""); // 성공 메시지 초기화
|
||||
await LoginApi.login({ userId, password });
|
||||
window.location.href = "/map/mainMap";
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="login fx">
|
||||
<div className="left_box fx">
|
||||
<div className="text">
|
||||
<h1>
|
||||
유역하수도 통합플랫폼
|
||||
<br />
|
||||
<span>통합관리시스템</span>
|
||||
</h1>
|
||||
<p>유역하수도 통합플랫폼에 오신 것을 환영합니다.</p>
|
||||
<div className="login fx">
|
||||
<div className="left_box fx">
|
||||
<div className="text">
|
||||
<h1>
|
||||
하수도정보시스템
|
||||
<br />
|
||||
<span>CMS 통합관리시스템</span>
|
||||
</h1>
|
||||
<p>하수도정보시스템 CMS 통합관리시스템에 오신 것을 환영합니다.</p>
|
||||
</div>
|
||||
<div className="img">
|
||||
<Image src={images.logo} width={229} height={46} alt="logo" />
|
||||
</div>
|
||||
</div>
|
||||
<form
|
||||
id="loginForm"
|
||||
name="loginForm"
|
||||
method="post"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<div className="login_box fx">
|
||||
<ul className="fx">
|
||||
<li>
|
||||
<h2>ID</h2>
|
||||
<div className="input_st st1">
|
||||
<i className="diveicon di-account-box" />
|
||||
<input
|
||||
type="text"
|
||||
className="id"
|
||||
title="아이디를 입력하세요."
|
||||
id="id"
|
||||
name="id"
|
||||
maxLength={12}
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<h2>PASSWORD</h2>
|
||||
<div className="input_st st2">
|
||||
<i className="diveicon di-key" />
|
||||
<input
|
||||
type="password"
|
||||
className="input_style"
|
||||
maxLength={25}
|
||||
title="비밀번호를 입력하세요."
|
||||
id="password"
|
||||
name="password"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="bottom_box fx">
|
||||
<div className="checks fx">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="checkId"
|
||||
title="로그인ID 저장여부"
|
||||
id="checkId"
|
||||
/>
|
||||
<label htmlFor="checkId">아이디저장</label>
|
||||
</div>
|
||||
{/* <a href="javascript:fnUserSearch();" className=" pw_message fx"><i className="xi-external-link"></i>비밀번호 또는 아이디를 잃어버리셨나요?</a> */}
|
||||
</div>
|
||||
<div className="img">
|
||||
<Image src={images.logo} width={229} height={46} alt="logo" />
|
||||
<button type="submit" className="login_btn fx">
|
||||
<span>LOGIN</span>
|
||||
</button>
|
||||
<div className="footer">
|
||||
<div className="copy fx">
|
||||
<p>copyright © 2023 KECO. All rights reserved.</p>
|
||||
</div>
|
||||
<div className="fot_btn fx">
|
||||
<a
|
||||
href="https://keco.or.kr/web/lay1/S1T6C1348/contents.do"
|
||||
target="_blank"
|
||||
>
|
||||
개인정보처리방침
|
||||
</a>
|
||||
<a
|
||||
href="https://keco.or.kr/web/lay1/S1T6C1349/contents.do"
|
||||
target="_blank"
|
||||
>
|
||||
저작권정책안내
|
||||
</a>
|
||||
<a
|
||||
href="https://keco.or.kr/web/lay1/S1T6C1350/contents.do"
|
||||
target="_blank"
|
||||
>
|
||||
이메일무단수집거부
|
||||
</a>
|
||||
</div>
|
||||
<p className="addr fx">
|
||||
<span>
|
||||
주소 : (22689) 인천광역시 서구 환경로 42(경서동
|
||||
종합환경연구단지)
|
||||
</span>
|
||||
<span>
|
||||
전화 : (시스템) 032-590-4321, 3752, 4314 / (통 계) 032-590-3723,
|
||||
3722
|
||||
</span>
|
||||
<span>팩스 :032-590-3729</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<form
|
||||
id="loginForm"
|
||||
name="loginForm"
|
||||
method="post"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<div className="login_box fx">
|
||||
<ul className="fx">
|
||||
<li>
|
||||
<h2>ID</h2>
|
||||
<div className="input_st st1">
|
||||
<i className="diveicon di-account-box" />
|
||||
<input
|
||||
type="text"
|
||||
className="id"
|
||||
title="아이디를 입력하세요."
|
||||
id="id"
|
||||
name="id"
|
||||
maxLength={12}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<h2>PASSWORD</h2>
|
||||
<div className="input_st st2">
|
||||
<i className="diveicon di-key" />
|
||||
<input
|
||||
type="password"
|
||||
className="input_style"
|
||||
maxLength={25}
|
||||
title="비밀번호를 입력하세요."
|
||||
id="password"
|
||||
name="password"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="bottom_box fx">
|
||||
<div className="checks fx">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="checkId"
|
||||
title="로그인ID 저장여부"
|
||||
id="checkId"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<label htmlFor="checkId">아이디저장</label>
|
||||
</div>
|
||||
<div className="checks fx">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="checkTwoFA"
|
||||
title="OTP 인증 여부"
|
||||
id="checkTwoFA"
|
||||
checked={isOtpEnabled}
|
||||
onChange={(e) => setIsOtpEnabled(e.target.checked)}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<label htmlFor="checkTwoFA">OTP 인증</label>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="login_btn fx"
|
||||
disabled={isLoading}
|
||||
>
|
||||
<span>{isLoading ? "로그인 중..." : "LOGIN"}</span>
|
||||
</button>
|
||||
{error && (
|
||||
<div className="error-message" style={{ color: "red", textAlign: "center", marginTop: "10px" }}>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
{successMessage && (
|
||||
<div className="success-message" style={{ color: "green", textAlign: "center", marginTop: "10px" }}>
|
||||
{successMessage}
|
||||
</div>
|
||||
)}
|
||||
<div className="signup-link" style={{ textAlign: "center", marginTop: "15px" }}>
|
||||
<span style={{ color: "#666", fontSize: "14px" }}>계정이 없으신가요? </span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowSignupModal(true)}
|
||||
style={{
|
||||
background: "none",
|
||||
border: "none",
|
||||
color: "#3b82f6",
|
||||
textDecoration: "underline",
|
||||
cursor: "pointer",
|
||||
fontSize: "14px"
|
||||
}}
|
||||
>
|
||||
회원가입
|
||||
</button>
|
||||
</div>
|
||||
<div className="footer">
|
||||
<div className="copy fx">
|
||||
<p>copyright © 2023 KECO. All rights reserved.</p>
|
||||
</div>
|
||||
<div className="fot_btn fx">
|
||||
<a
|
||||
href="https://keco.or.kr/web/lay1/S1T6C1348/contents.do"
|
||||
target="_blank"
|
||||
>
|
||||
개인정보처리방침
|
||||
</a>
|
||||
<a
|
||||
href="https://keco.or.kr/web/lay1/S1T6C1349/contents.do"
|
||||
target="_blank"
|
||||
>
|
||||
저작권정책안내
|
||||
</a>
|
||||
<a
|
||||
href="https://keco.or.kr/web/lay1/S1T6C1350/contents.do"
|
||||
target="_blank"
|
||||
>
|
||||
이메일무단수집거부
|
||||
</a>
|
||||
</div>
|
||||
<p className="addr fx">
|
||||
<span>
|
||||
주소 : (22689) 인천광역시 서구 환경로 42(경서동
|
||||
종합환경연구단지)
|
||||
</span>
|
||||
<span>
|
||||
전화 : (시스템) 032-590-4321, 3752, 4314 / (통 계) 032-590-3723,
|
||||
3722
|
||||
</span>
|
||||
<span>팩스 :032-590-3729</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{/* OTP 인증 모달 */}
|
||||
<OtpModal
|
||||
onClose={handleOtpClose}
|
||||
onSuccess={handleOtpSuccess}
|
||||
open={showOtpModal}
|
||||
onOpenChange={setShowOtpModal}
|
||||
/>
|
||||
|
||||
{/* 2FA 등록 모달 */}
|
||||
<TwoFAModal
|
||||
onClose={handleTwoFAClose}
|
||||
onSuccess={handleTwoFASuccess}
|
||||
open={showTwoFAModal}
|
||||
onOpenChange={setShowTwoFAModal}
|
||||
/>
|
||||
|
||||
{/* 회원가입 모달 */}
|
||||
<UserSignupModal
|
||||
onClose={handleSignupClose}
|
||||
onSuccess={handleSignupSuccess}
|
||||
open={showSignupModal}
|
||||
/>
|
||||
</>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { LoginPage };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user