diff --git a/.gitignore b/.gitignore
index 3daecca..f44e616 100644
--- a/.gitignore
+++ b/.gitignore
@@ -103,4 +103,5 @@ TODO.txt
*.log
.claude
*.report.md
+*.claude.md
diff
diff --git a/src/main/resources/static/js/login-success-handler.js b/src/main/resources/static/js/login-success-handler.js
new file mode 100644
index 0000000..a811257
--- /dev/null
+++ b/src/main/resources/static/js/login-success-handler.js
@@ -0,0 +1,127 @@
+/**
+ * 로그인 성공 후 처리 핸들러
+ *
+ * PortalAuthenticationSuccessHandler에서 세션에 설정한 값들을 읽어
+ * 적절한 팝업을 표시하고 리다이렉트를 처리합니다.
+ *
+ * 필요한 세션 속성:
+ * - success: 일반 성공 메시지
+ * - session.emailVerificationRequired: 이메일 인증 필요 여부
+ * - session.dormantAccount: 휴면 계정 여부
+ * - session.passwordExpired: 비밀번호 만료 여부
+ * - session.pendingInvitation: 법인 초대 대기 여부
+ * - session.pendingInvitationToken: 초대 토큰
+ * - session.pendingInvitationOrgName: 초대 기관명
+ * - session.redirectUrl: 리다이렉트 URL
+ *
+ * 의존성:
+ * - jQuery ($)
+ * - customPopups (custom-popups.js)
+ *
+ * @see PortalAuthenticationSuccessHandler.java
+ */
+var LoginSuccessHandler = (function() {
+ 'use strict';
+
+ var config = {
+ successMsg: null,
+ emailVerificationRequired: false,
+ dormantAccount: false,
+ passwordExpired: false,
+ pendingInvitation: false,
+ pendingInvitationToken: '',
+ pendingInvitationOrgName: '',
+ sessionSuccessMsg: '',
+ redirectUrl: ''
+ };
+
+ /**
+ * 핸들러 초기화
+ * @param {Object} options - Thymeleaf에서 전달받은 세션 값들
+ */
+ function init(options) {
+ if (!options) {
+ console.warn('LoginSuccessHandler: options not provided');
+ return;
+ }
+
+ config = $.extend({}, config, options);
+
+ console.log('LoginSuccessHandler initialized:', config);
+
+ handleLoginSuccess();
+ }
+
+ /**
+ * 로그인 성공 후 처리 로직 실행
+ */
+ function handleLoginSuccess() {
+ // 1. 기존 success 메시지 처리
+ if (config.successMsg) {
+ console.log("Success message:", config.successMsg);
+ customPopups.showAlert(config.successMsg);
+ }
+
+ // 2. 이메일 인증 체크
+ if (config.emailVerificationRequired) {
+ console.log('emailVerifyMsg:', config.sessionSuccessMsg);
+ console.log('redirectUrl:', config.redirectUrl);
+
+ customPopups.showAlert(config.sessionSuccessMsg);
+ $('#customAlertOkButton').off('click.loginHandler').on('click.loginHandler', function() {
+ customPopups.hideAlert('#customAlert');
+ window.location.href = config.redirectUrl;
+ });
+ return; // 다른 체크 중단
+ }
+
+ // 3. 장기미사용 계정 체크
+ if (config.dormantAccount) {
+ console.log('dormantMsg:', config.sessionSuccessMsg);
+ console.log('redirectUrl:', config.redirectUrl);
+
+ customPopups.showAlert(config.sessionSuccessMsg);
+ $('#customAlertOkButton').off('click.loginHandler').on('click.loginHandler', function() {
+ customPopups.hideAlert('#customAlert');
+ window.location.href = config.redirectUrl;
+ });
+ return; // 다른 체크 중단
+ }
+
+ // 4. 비밀번호 만료 체크
+ if (config.passwordExpired) {
+ var formattedMsg = config.sessionSuccessMsg ? config.sessionSuccessMsg.replace(/\n/g, '
') : '';
+ console.log('pwdExpiredMsg:', config.sessionSuccessMsg);
+ console.log('redirectUrl:', config.redirectUrl);
+
+ $('#customAlertMessage').html(formattedMsg);
+ $('#customAlert').css('display', 'flex');
+
+ $('#customAlertOkButton').off('click.loginHandler').on('click.loginHandler', function() {
+ customPopups.hideAlert('#customAlert');
+ window.location.href = config.redirectUrl;
+ });
+ return; // 다른 체크 중단
+ }
+
+ // 5. 초대 대기 확인 (로그인할 때마다 표시)
+ if (config.pendingInvitation) {
+ var orgName = config.pendingInvitationOrgName || '알 수 없는 기관';
+ var token = config.pendingInvitationToken;
+
+ customPopups.showConfirm(
+ '' + orgName + '에서 초대가 도착했습니다.
초대를 확인하시겠습니까?',
+ function(selection) {
+ if (selection) {
+ window.location.href = '/signup/decision?invitation=' + token;
+ }
+ }
+ );
+ }
+ }
+
+ // Public API
+ return {
+ init: init
+ };
+})();
diff --git a/src/main/resources/templates/views/apps/main/index.html b/src/main/resources/templates/views/apps/main/index.html
index a27a593..a561864 100644
--- a/src/main/resources/templates/views/apps/main/index.html
+++ b/src/main/resources/templates/views/apps/main/index.html
@@ -329,6 +329,24 @@