- 약관 동의서 노출 및 동작 방식 관리 기능 추가
- '전체 동의' 및 약관 항목별 활성화 설정 반영 - 사용자 타입 및 약관 페이지 구성에 따른 렌더링 로직 개선
This commit is contained in:
+70
-23
@@ -1,10 +1,16 @@
|
||||
package com.eactive.apim.portal.apps.agreements.controller;
|
||||
|
||||
import com.eactive.apim.portal.agreements.entity.AgreementType;
|
||||
import com.eactive.apim.portal.agreements.service.AgreementTypeConfigService;
|
||||
import com.eactive.apim.portal.apps.agreements.dto.AgreementTabDTO;
|
||||
import com.eactive.apim.portal.apps.agreements.dto.AgreementsDTO;
|
||||
import com.eactive.apim.portal.apps.agreements.service.AgreementsFacade;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
@@ -21,38 +27,56 @@ public class AgreementsController {
|
||||
public static final String PRIVACY_POLICY_EXTERNAL_URL =
|
||||
"https://www.jejubank.co.kr/hmpg/csct/secuCenr/ptctPlcy/procsPlcy/ctnt.do";
|
||||
|
||||
/**
|
||||
* 약관 종류별 {@code tab} 파라미터 값. 기존 북마크/외부 링크 호환을 위해 슬러그를 유지한다.
|
||||
* (초대 팝업 등에서 {@code tab=notification} 으로 직접 링크한다)
|
||||
*/
|
||||
private static final Map<AgreementType, String> TAB_SLUGS = new EnumMap<>(AgreementType.class);
|
||||
|
||||
static {
|
||||
TAB_SLUGS.put(AgreementType.TERMS_OF_USE, "terms");
|
||||
TAB_SLUGS.put(AgreementType.PRIVACY_POLICY, "privacy");
|
||||
TAB_SLUGS.put(AgreementType.PRIVACY_COLLECT, "privacy-collect");
|
||||
TAB_SLUGS.put(AgreementType.PRIVACY_COLLECT_IND, "privacy-collect-ind");
|
||||
TAB_SLUGS.put(AgreementType.PRIVACY_COLLECT_ORG, "privacy-collect-org");
|
||||
TAB_SLUGS.put(AgreementType.NOTIFICATION_CONSENT, "notification");
|
||||
}
|
||||
|
||||
private final AgreementsFacade agreementsFacade;
|
||||
private final AgreementTypeConfigService agreementTypeConfigService;
|
||||
|
||||
@Autowired
|
||||
public AgreementsController(AgreementsFacade agreementsFacade) {
|
||||
public AgreementsController(AgreementsFacade agreementsFacade,
|
||||
AgreementTypeConfigService agreementTypeConfigService) {
|
||||
this.agreementsFacade = agreementsFacade;
|
||||
this.agreementTypeConfigService = agreementTypeConfigService;
|
||||
}
|
||||
|
||||
@GetMapping("/terms")
|
||||
public String showTerms(@RequestParam(required = false) String tab,
|
||||
@RequestParam(required = false) String publishedOn,
|
||||
Model model) {
|
||||
String currentTab = tab != null ? tab : "terms";
|
||||
|
||||
// 구 개인정보처리방침 탭(tab=privacy)은 외부 링크로 이동했으므로 외부 URL로 리다이렉트(북마크 호환)
|
||||
if ("privacy".equals(currentTab)) {
|
||||
if ("privacy".equals(tab)) {
|
||||
return "redirect:" + PRIVACY_POLICY_EXTERNAL_URL;
|
||||
}
|
||||
|
||||
AgreementType type;
|
||||
switch (currentTab) {
|
||||
case "privacy-collect":
|
||||
// 개인정보수집동의서 = PRIVACY_COLLECT (신설항목)
|
||||
type = AgreementType.PRIVACY_COLLECT;
|
||||
break;
|
||||
case "notification":
|
||||
type = AgreementType.NOTIFICATION_CONSENT;
|
||||
break;
|
||||
case "terms":
|
||||
default:
|
||||
currentTab = "terms";
|
||||
type = AgreementType.TERMS_OF_USE;
|
||||
break;
|
||||
// 노출 대상과 순서는 관리 콘솔의 '약관 종류 관리'에서 설정한다
|
||||
List<AgreementType> displayTypes = agreementTypeConfigService.getDisplayTypes();
|
||||
|
||||
// 요청한 탭이 노출 대상이 아니면(미배치/사용안함) 첫 번째 탭으로 보정한다
|
||||
AgreementType type = resolveType(tab, displayTypes);
|
||||
|
||||
if (type == null) {
|
||||
// 노출할 약관 종류가 하나도 없는 경우 — 빈 화면으로 방어
|
||||
model.addAttribute("termsTabs", Collections.<AgreementTabDTO>emptyList());
|
||||
model.addAttribute("agreementsList", Collections.<AgreementsDTO>emptyList());
|
||||
model.addAttribute("selectedAgreement", null);
|
||||
model.addAttribute("selectedDate", publishedOn);
|
||||
model.addAttribute("agreementTitle", "약관");
|
||||
model.addAttribute("agreementType", null);
|
||||
model.addAttribute("currentTab", null);
|
||||
return TERMS_AGREEMENTS;
|
||||
}
|
||||
|
||||
List<AgreementsDTO> agreementsList = agreementsFacade.getAgreementsList(String.valueOf(type));
|
||||
@@ -68,17 +92,40 @@ public class AgreementsController {
|
||||
model.addAttribute("selectedAgreement", selectedAgreement);
|
||||
model.addAttribute("selectedDate", publishedOn);
|
||||
|
||||
model.addAttribute("isTermsOfUse", type == AgreementType.TERMS_OF_USE);
|
||||
model.addAttribute("isPrivacyCollect", type == AgreementType.PRIVACY_COLLECT);
|
||||
model.addAttribute("isNotification", type == AgreementType.NOTIFICATION_CONSENT);
|
||||
model.addAttribute("termsTabs", buildTabs(displayTypes, type));
|
||||
model.addAttribute("agreementTitle", type.getDescription());
|
||||
model.addAttribute("agreementType", type.getCode());
|
||||
|
||||
model.addAttribute("currentTab", currentTab);
|
||||
model.addAttribute("currentTab", TAB_SLUGS.get(type));
|
||||
|
||||
return TERMS_AGREEMENTS;
|
||||
}
|
||||
|
||||
/** 요청한 탭 슬러그를 노출 대상 약관 종류로 해석한다. 없거나 노출 대상이 아니면 첫 번째 탭. */
|
||||
private AgreementType resolveType(String tab, List<AgreementType> displayTypes) {
|
||||
if (displayTypes.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (tab != null && !tab.isEmpty()) {
|
||||
for (AgreementType displayType : displayTypes) {
|
||||
if (tab.equals(TAB_SLUGS.get(displayType))) {
|
||||
return displayType;
|
||||
}
|
||||
}
|
||||
}
|
||||
return displayTypes.get(0);
|
||||
}
|
||||
|
||||
private List<AgreementTabDTO> buildTabs(List<AgreementType> displayTypes, AgreementType currentType) {
|
||||
List<AgreementTabDTO> tabs = new ArrayList<>();
|
||||
for (AgreementType displayType : displayTypes) {
|
||||
tabs.add(new AgreementTabDTO(
|
||||
TAB_SLUGS.get(displayType),
|
||||
displayType.getDescription(),
|
||||
displayType == currentType));
|
||||
}
|
||||
return tabs;
|
||||
}
|
||||
|
||||
private AgreementsDTO findAgreementByDate(List<AgreementsDTO> agreements, String publishedOn) {
|
||||
return agreements.stream()
|
||||
.filter(a -> publishedOn.equals(
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.eactive.apim.portal.apps.agreements.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 약관 페이지({@code /agreements/terms})의 탭 하나.
|
||||
*
|
||||
* <p>노출 대상과 순서는 관리 콘솔의 '약관 종류 관리'에서 설정한 값
|
||||
* ({@code PTL_PROPERTY Portal/portal.terms.display-types})을 따른다.
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class AgreementTabDTO {
|
||||
|
||||
/** URL 파라미터 {@code tab} 값 (북마크 호환을 위해 기존 슬러그를 유지한다) */
|
||||
private final String tab;
|
||||
|
||||
/** 탭에 표시할 약관명 */
|
||||
private final String title;
|
||||
|
||||
/** 현재 선택된 탭인지 여부 */
|
||||
private final boolean active;
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package com.eactive.apim.portal.apps.agreements.service;
|
||||
|
||||
import com.eactive.apim.portal.agreements.entity.AgreementType;
|
||||
import com.eactive.apim.portal.agreements.service.AgreementTypeConfigService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.ui.Model;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 약관 동의 폼({@code apps/register/userAgreementContent :: agreementContent})에 필요한 모델 속성을 채운다.
|
||||
*
|
||||
* <p>관리 콘솔의 '약관 종류 관리'에서 사용하지 않도록 설정한 약관 종류는 동의 항목 자체를 노출하지 않는다
|
||||
* ({@code show*} 플래그가 {@code false} 이고 내용도 {@code null}).
|
||||
*
|
||||
* @see AgreementTypeConfigService
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class AgreementFormModelSupport {
|
||||
|
||||
private final AgreementsFacade agreementsFacade;
|
||||
private final AgreementTypeConfigService agreementTypeConfigService;
|
||||
|
||||
/** 이용약관 / 개인정보수집동의서 / 알림 수신 동의서 모델 속성을 사용 여부에 맞춰 채운다. */
|
||||
public void applyAgreements(Model model) {
|
||||
applyAgreeAllMode(model);
|
||||
|
||||
Set<AgreementType> enabledTypes = agreementTypeConfigService.getEnabledTypes();
|
||||
|
||||
applyAgreement(model, enabledTypes, AgreementType.TERMS_OF_USE, "termsOfUse", "showTermsOfUse");
|
||||
applyAgreement(model, enabledTypes, AgreementType.PRIVACY_COLLECT, "privacyCollect", "showPrivacyCollect");
|
||||
applyAgreement(model, enabledTypes, AgreementType.NOTIFICATION_CONSENT, "notificationConsent",
|
||||
"showNotificationConsent");
|
||||
}
|
||||
|
||||
/**
|
||||
* '전체 동의' 허용 여부만 모델에 담는다.
|
||||
*
|
||||
* <p>동의 항목 구성은 그대로 두고 동의 방식만 설정에 맞추면 되는 화면(법인 전환 등)에서 쓴다.
|
||||
* {@code false} 면 '전체 동의' 체크박스를 숨기고 항목별로 끝까지 읽어야 동의할 수 있다.
|
||||
*/
|
||||
public void applyAgreeAllMode(Model model) {
|
||||
model.addAttribute("agreeAllEnabled", agreementTypeConfigService.isAgreeAllEnabled());
|
||||
}
|
||||
|
||||
private void applyAgreement(Model model, Set<AgreementType> enabledTypes, AgreementType type,
|
||||
String contentAttribute, String flagAttribute) {
|
||||
boolean enabled = enabledTypes.contains(type);
|
||||
model.addAttribute(flagAttribute, enabled);
|
||||
model.addAttribute(contentAttribute, enabled ? agreementsFacade.getAgreement(type.getCode()) : null);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.eactive.apim.portal.apps.user.controller;
|
||||
|
||||
import com.eactive.apim.portal.apps.agreements.service.AgreementFormModelSupport;
|
||||
import com.eactive.apim.portal.apps.agreements.service.AgreementsFacade;
|
||||
import com.eactive.apim.portal.apps.auth.twofactor.StepUpProtectedPaths;
|
||||
import com.eactive.apim.portal.apps.auth.twofactor.TwoFactorProperties;
|
||||
@@ -51,6 +52,7 @@ public class AccountController {
|
||||
private final UserFacade userFacade;
|
||||
private final OrgRegisterFacade orgRegisterFacade;
|
||||
private final AgreementsFacade agreementsFacade;
|
||||
private final AgreementFormModelSupport agreementFormModelSupport;
|
||||
private final com.eactive.apim.portal.apps.user.facade.AuthFacade authFacade;
|
||||
private final UserInvitationRepository userInvitationRepository;
|
||||
private final UserSessionService userSessionService;
|
||||
@@ -365,6 +367,8 @@ public class AccountController {
|
||||
model.addAttribute("termsOfUse", agreementsFacade.getAgreement("TERMS_OF_USE"));
|
||||
model.addAttribute("privacyCollect", agreementsFacade.getAgreement("PRIVACY_COLLECT"));
|
||||
model.addAttribute("registrationType", "corporate");
|
||||
// 동의 항목 구성은 이 화면 그대로 두고 '전체 동의' 허용 여부만 설정을 따른다
|
||||
agreementFormModelSupport.applyAgreeAllMode(model);
|
||||
return "apps/mypage/orgTransfer";
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
+6
-8
@@ -1,6 +1,6 @@
|
||||
package com.eactive.apim.portal.apps.user.controller;
|
||||
|
||||
import com.eactive.apim.portal.apps.agreements.service.AgreementsFacade;
|
||||
import com.eactive.apim.portal.apps.agreements.service.AgreementFormModelSupport;
|
||||
import com.eactive.apim.portal.apps.user.dto.PortalOrgRegistrationDTO;
|
||||
import com.eactive.apim.portal.apps.user.dto.UserAgreementDTO;
|
||||
import com.eactive.apim.portal.apps.user.dto.ValidationResponse;
|
||||
@@ -21,7 +21,7 @@ public class OrgRegisterController {
|
||||
private static final String MAIN_ORG_REGISTER = "apps/register/orgUserRegister";
|
||||
private static final String MAIN_REGISTER_RESULT = "apps/register/userRegisterResult";
|
||||
|
||||
private final AgreementsFacade agreementsFacade;
|
||||
private final AgreementFormModelSupport agreementFormModelSupport;
|
||||
private final PortalProperties portalProperties;
|
||||
private final OrgRegisterFacade orgRegisterFacade;
|
||||
|
||||
@@ -30,9 +30,8 @@ public class OrgRegisterController {
|
||||
model.addAttribute("registrationType", "corporate");
|
||||
model.addAttribute("authTtl", portalProperties.getAuthTtl());
|
||||
model.addAttribute("portalOrg", new PortalOrgRegistrationDTO());
|
||||
model.addAttribute("termsOfUse", agreementsFacade.getAgreement("TERMS_OF_USE"));
|
||||
model.addAttribute("privacyCollect", agreementsFacade.getAgreement("PRIVACY_COLLECT"));
|
||||
model.addAttribute("notificationConsent", agreementsFacade.getAgreement("NOTIFICATION_CONSENT"));
|
||||
// 사용하지 않도록 설정한 약관 종류는 동의 항목을 노출하지 않는다 (관리 콘솔 > 약관 종류 관리)
|
||||
agreementFormModelSupport.applyAgreements(model);
|
||||
return MAIN_ORG_REGISTER;
|
||||
}
|
||||
|
||||
@@ -105,8 +104,7 @@ public class OrgRegisterController {
|
||||
model.addAttribute("registrationType", "corporate");
|
||||
model.addAttribute("portalOrg", orgDTO);
|
||||
model.addAttribute("error", errorMessage);
|
||||
model.addAttribute("termsOfUse", agreementsFacade.getAgreement("TERMS_OF_USE"));
|
||||
model.addAttribute("privacyCollect", agreementsFacade.getAgreement("PRIVACY_COLLECT"));
|
||||
model.addAttribute("notificationConsent", agreementsFacade.getAgreement("NOTIFICATION_CONSENT"));
|
||||
// 사용하지 않도록 설정한 약관 종류는 동의 항목을 노출하지 않는다 (관리 콘솔 > 약관 종류 관리)
|
||||
agreementFormModelSupport.applyAgreements(model);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.eactive.apim.portal.apps.user.controller;
|
||||
|
||||
import com.eactive.apim.portal.agreements.entity.AgreementType;
|
||||
import com.eactive.apim.portal.agreements.service.AgreementTypeConfigService;
|
||||
import com.eactive.apim.portal.apps.user.dto.PortalUserDTO;
|
||||
import com.eactive.apim.portal.apps.user.facade.UserManFacade;
|
||||
import com.eactive.apim.portal.common.util.SecurityUtil;
|
||||
@@ -24,6 +26,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
public class UserManController {
|
||||
|
||||
private final UserManFacade userManFacade;
|
||||
private final AgreementTypeConfigService agreementTypeConfigService;
|
||||
|
||||
@GetMapping
|
||||
public String userList(@PageableDefault(sort = "createdDate", direction = Sort.Direction.DESC) Pageable pageable, Model model) {
|
||||
@@ -34,6 +37,10 @@ public class UserManController {
|
||||
model.addAttribute("pendingUsers", pendingUsers);
|
||||
model.addAttribute("page", users);
|
||||
model.addAttribute("currentUserId", SecurityUtil.getPortalAuthenticatedUser().getId());
|
||||
// 초대/초대 취소 팝업의 알림 수신 동의 체크박스 노출 여부.
|
||||
// 약관 페이지에 배치되어 있어야(= 사용 중이고 미배치가 아니어야) 동의서 링크를 안내할 수 있다.
|
||||
model.addAttribute("notificationConsentAvailable",
|
||||
agreementTypeConfigService.isDisplayed(AgreementType.NOTIFICATION_CONSENT));
|
||||
return "apps/users/userList";
|
||||
}
|
||||
|
||||
|
||||
+10
-14
@@ -1,6 +1,6 @@
|
||||
package com.eactive.apim.portal.apps.user.controller;
|
||||
|
||||
import com.eactive.apim.portal.apps.agreements.service.AgreementsFacade;
|
||||
import com.eactive.apim.portal.apps.agreements.service.AgreementFormModelSupport;
|
||||
import com.eactive.apim.portal.apps.user.dto.PortalUserRegistrationDTO;
|
||||
import com.eactive.apim.portal.apps.user.dto.UserAgreementDTO;
|
||||
import com.eactive.apim.portal.apps.user.dto.ValidationResponse;
|
||||
@@ -54,7 +54,7 @@ public class UserRegisterController {
|
||||
private final AuthFacade authFacade;
|
||||
private final PortalUserRepository portalUserRepository;
|
||||
private final PortalOrgRepository portalOrgRepository;
|
||||
private final AgreementsFacade agreementsFacade;
|
||||
private final AgreementFormModelSupport agreementFormModelSupport;
|
||||
private final PortalProperties portalProperties;
|
||||
private final UserInvitationRepository userInvitationRepository;
|
||||
private final AgreementValidator agreementValidator;
|
||||
@@ -94,9 +94,8 @@ public class UserRegisterController {
|
||||
|
||||
model.addAttribute("authTtl", portalProperties.getAuthTtl());
|
||||
model.addAttribute("portalUser", new PortalUserRegistrationDTO());
|
||||
model.addAttribute("termsOfUse", agreementsFacade.getAgreement("TERMS_OF_USE"));
|
||||
model.addAttribute("privacyCollect", agreementsFacade.getAgreement("PRIVACY_COLLECT"));
|
||||
model.addAttribute("notificationConsent", agreementsFacade.getAgreement("NOTIFICATION_CONSENT"));
|
||||
// 사용하지 않도록 설정한 약관 종류는 동의 항목을 노출하지 않는다 (관리 콘솔 > 약관 종류 관리)
|
||||
agreementFormModelSupport.applyAgreements(model);
|
||||
|
||||
return MAIN_USER_REGISTER;
|
||||
|
||||
@@ -360,9 +359,8 @@ public class UserRegisterController {
|
||||
model.addAttribute("userName", SecurityUtil.getPortalAuthenticatedUser().getUsername());
|
||||
model.addAttribute("orgName", org.get().getOrgName());
|
||||
model.addAttribute("invitationCode", invitation.get().getToken());
|
||||
model.addAttribute("termsOfUse", agreementsFacade.getAgreement("TERMS_OF_USE"));
|
||||
model.addAttribute("privacyCollect", agreementsFacade.getAgreement("PRIVACY_COLLECT"));
|
||||
model.addAttribute("notificationConsent", agreementsFacade.getAgreement("NOTIFICATION_CONSENT"));
|
||||
// 사용하지 않도록 설정한 약관 종류는 동의 항목을 노출하지 않는다 (관리 콘솔 > 약관 종류 관리)
|
||||
agreementFormModelSupport.applyAgreements(model);
|
||||
model.addAttribute("agreementTitle", "법인 회원 전환을 위한 약관 동의");
|
||||
model.addAttribute("registrationType", "corporate");
|
||||
|
||||
@@ -391,9 +389,8 @@ public class UserRegisterController {
|
||||
|
||||
model.addAttribute("userName", SecurityUtil.getPortalAuthenticatedUser().getUsername());
|
||||
model.addAttribute("orgName", org.get().getOrgName());
|
||||
model.addAttribute("termsOfUse", agreementsFacade.getAgreement("TERMS_OF_USE"));
|
||||
model.addAttribute("privacyCollect", agreementsFacade.getAgreement("PRIVACY_COLLECT"));
|
||||
model.addAttribute("notificationConsent", agreementsFacade.getAgreement("NOTIFICATION_CONSENT"));
|
||||
// 사용하지 않도록 설정한 약관 종류는 동의 항목을 노출하지 않는다 (관리 콘솔 > 약관 종류 관리)
|
||||
agreementFormModelSupport.applyAgreements(model);
|
||||
model.addAttribute("agreementTitle", "법인 회원 전환을 위한 약관 동의");
|
||||
model.addAttribute("registrationType", "corporate");
|
||||
|
||||
@@ -435,9 +432,8 @@ public class UserRegisterController {
|
||||
model.addAttribute("error", errorMessage);
|
||||
model.addAttribute("agreement", agreement);
|
||||
|
||||
model.addAttribute("termsOfUse", agreementsFacade.getAgreement("TERMS_OF_USE"));
|
||||
model.addAttribute("privacyCollect", agreementsFacade.getAgreement("PRIVACY_COLLECT"));
|
||||
model.addAttribute("notificationConsent", agreementsFacade.getAgreement("NOTIFICATION_CONSENT"));
|
||||
// 사용하지 않도록 설정한 약관 종류는 동의 항목을 노출하지 않는다 (관리 콘솔 > 약관 종류 관리)
|
||||
agreementFormModelSupport.applyAgreements(model);
|
||||
model.addAttribute("msgType", "sms");
|
||||
}
|
||||
|
||||
|
||||
@@ -377,10 +377,13 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
// 사용하지 않도록 설정한 약관은 화면에 없으므로(관리 콘솔 > 약관 종류 관리) 없으면 통과 처리
|
||||
const termsOfUseEl = document.getElementById('termsOfUse');
|
||||
const privacyCollectEl = document.getElementById('privacyCollect');
|
||||
const notificationConsentEl = document.getElementById('notificationConsent');
|
||||
const validations = {
|
||||
termsOfUse: $('#termsOfUse').prop('checked'),
|
||||
privacyPolicy: $('#privacyCollect').prop('checked'),
|
||||
termsOfUse: !termsOfUseEl || termsOfUseEl.checked,
|
||||
privacyPolicy: !privacyCollectEl || privacyCollectEl.checked,
|
||||
notificationConsent: !notificationConsentEl || notificationConsentEl.checked,
|
||||
compRegNo: $('#compRegNo').val().trim() !== '',
|
||||
corpRegNo: $('#corpRegNo').val().trim() !== '',
|
||||
@@ -423,13 +426,15 @@
|
||||
}
|
||||
|
||||
// 공통 항목
|
||||
const termsOfUseEl = document.getElementById('termsOfUse');
|
||||
const privacyCollectEl = document.getElementById('privacyCollect');
|
||||
const notificationConsentEl = document.getElementById('notificationConsent');
|
||||
if ($('#orgName').val().trim() === '') errors.push('회사명을 입력해주세요.');
|
||||
if ($('#compRegNo').val().trim() === '') errors.push('사업자등록번호를 올바르게 입력해주세요.');
|
||||
if ($('#corpRegNo').val().trim() === '') errors.push('법인등록번호를 올바르게 입력해주세요.');
|
||||
if ($('#compRegFile').val().trim() === '') errors.push('사업자등록증 파일을 첨부해주세요.');
|
||||
if (!$('#termsOfUse').prop('checked')) errors.push('이용약관에 동의해주세요.');
|
||||
if (!$('#privacyCollect').prop('checked')) errors.push('개인정보 수집·이용에 동의해주세요.');
|
||||
if (termsOfUseEl && !termsOfUseEl.checked) errors.push('이용약관에 동의해주세요.');
|
||||
if (privacyCollectEl && !privacyCollectEl.checked) errors.push('개인정보 수집·이용에 동의해주세요.');
|
||||
if (notificationConsentEl && !notificationConsentEl.checked) errors.push('알림 수신에 동의해주세요.');
|
||||
|
||||
return errors;
|
||||
|
||||
@@ -11,83 +11,97 @@
|
||||
</div>
|
||||
|
||||
<form id="agreementForm" class="agreement-form">
|
||||
<!-- Agree All Checkbox -->
|
||||
<div class="agreement-all-section">
|
||||
<label class="agreement-checkbox-label">
|
||||
<input type="checkbox" id="agree_all" class="agreement-checkbox-input">
|
||||
<span class="agreement-checkbox-custom"></span>
|
||||
<span class="agreement-checkbox-text agreement-all-text">전체동의</span>
|
||||
</label>
|
||||
</div>
|
||||
<!--/*
|
||||
'전체 동의' 허용 여부(관리 콘솔 > 약관 종류 관리)에 따라 두 가지 방식으로 동작한다.
|
||||
- 허용 : '전체 동의' 체크박스를 노출하고, 항목별 스크롤 없이 바로 동의할 수 있다
|
||||
- 미허용 : '전체 동의' 체크박스가 없고, 각 항목을 펼쳐 끝까지 읽어야 동의가 활성화된다
|
||||
agreeAllEnabled 를 내려주지 않는 화면은 미허용(스크롤 동의)으로 동작한다.
|
||||
*/-->
|
||||
<!-- Agree All Checkbox : '전체 동의'를 허용할 때만 노출 -->
|
||||
<th:block th:if="${agreeAllEnabled != null and agreeAllEnabled}">
|
||||
<div class="agreement-all-section">
|
||||
<label class="agreement-checkbox-label">
|
||||
<input type="checkbox" id="agree_all" class="agreement-checkbox-input">
|
||||
<span class="agreement-checkbox-custom"></span>
|
||||
<span class="agreement-checkbox-text agreement-all-text">전체동의</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Divider -->
|
||||
<div class="agreement-divider"></div>
|
||||
<!-- Divider -->
|
||||
<div class="agreement-divider"></div>
|
||||
</th:block>
|
||||
|
||||
<!-- Individual Agreements -->
|
||||
<div class="agreement-items-list">
|
||||
<!-- Terms of Use -->
|
||||
<div class="agreement-item-row">
|
||||
<label class="agreement-checkbox-label">
|
||||
<input type="checkbox" name="termsOfUse" id="termsOfUse" class="agreement-checkbox-input"
|
||||
required disabled>
|
||||
<span class="agreement-checkbox-custom"></span>
|
||||
<span class="agreement-checkbox-text">
|
||||
<span class="agreement-required">[필수]</span>
|
||||
<span class="agreement-title">이용약관 동의</span>
|
||||
</span>
|
||||
</label>
|
||||
<button type="button" class="agreement-toggle-icon" data-target="termsOfUseContent">
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="termsOfUseContent" class="agreement-content" style="display:none;">
|
||||
<div class="agreement-scroll" style="max-height: 400px; overflow-y: auto;">
|
||||
<div class="editor-content" th:utext="${termsOfUse?.contents ?: '이용약관 내용을 불러올 수 없습니다.'}"></div>
|
||||
</div>
|
||||
<div class="agreement-btn-wrapper" style="text-align: center; margin-top: 15px;">
|
||||
<button type="button" class="btn-action-primary md agreement-agree-btn"
|
||||
data-checkbox-id="termsOfUse" disabled>동의</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Privacy Policy -->
|
||||
<div class="agreement-item-row">
|
||||
<label class="agreement-checkbox-label">
|
||||
<input type="checkbox" name="privacyCollect" id="privacyCollect"
|
||||
class="agreement-checkbox-input" required disabled>
|
||||
<span class="agreement-checkbox-custom"></span>
|
||||
<th:block th:switch="${registrationType}">
|
||||
<span class="agreement-checkbox-text" th:case="'personal'">
|
||||
<!-- Terms of Use (사용하지 않도록 설정하면 숨김. 플래그가 없는 화면은 기존대로 노출) -->
|
||||
<th:block th:if="${showTermsOfUse == null or showTermsOfUse}">
|
||||
<div class="agreement-item-row">
|
||||
<label class="agreement-checkbox-label">
|
||||
<input type="checkbox" name="termsOfUse" id="termsOfUse" class="agreement-checkbox-input"
|
||||
required th:disabled="${agreeAllEnabled == null or !agreeAllEnabled}">
|
||||
<span class="agreement-checkbox-custom"></span>
|
||||
<span class="agreement-checkbox-text">
|
||||
<span class="agreement-required">[필수]</span>
|
||||
<span class="agreement-title">개인정보 수집 및 이용 동의</span>
|
||||
<span class="agreement-title">이용약관 동의</span>
|
||||
</span>
|
||||
<span class="agreement-checkbox-text" th:case="'corporate'">
|
||||
<span class="agreement-required">[필수]</span>
|
||||
<span class="agreement-title">개인정보 수집 및 이용 동의</span>
|
||||
</span>
|
||||
</th:block>
|
||||
</label>
|
||||
<button type="button" class="agreement-toggle-icon" data-target="privacyCollectContent">
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="privacyCollectContent" class="agreement-content" style="display:none;">
|
||||
<div class="agreement-scroll" style="max-height: 400px; overflow-y: auto;">
|
||||
<div class="editor-content"
|
||||
th:utext="${privacyCollect?.contents ?: '개인정보수집동의서 내용을 불러올 수 없습니다.'}"></div>
|
||||
</label>
|
||||
<button type="button" class="agreement-toggle-icon" data-target="termsOfUseContent">
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="agreement-btn-wrapper" style="text-align: center; margin-top: 15px;">
|
||||
<button type="button" class="btn-action-primary md agreement-agree-btn"
|
||||
data-checkbox-id="privacyCollect" disabled>동의</button>
|
||||
<div id="termsOfUseContent" class="agreement-content" style="display:none;">
|
||||
<div class="agreement-scroll" style="max-height: 400px; overflow-y: auto;">
|
||||
<div class="editor-content" th:utext="${termsOfUse?.contents ?: '이용약관 내용을 불러올 수 없습니다.'}"></div>
|
||||
</div>
|
||||
<div class="agreement-btn-wrapper" style="text-align: center; margin-top: 15px;">
|
||||
<button type="button" class="btn-action-primary md agreement-agree-btn"
|
||||
data-checkbox-id="termsOfUse" th:disabled="${agreeAllEnabled == null or !agreeAllEnabled}">동의</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notification Consent (모델에 notificationConsent가 있을 때만 노출) -->
|
||||
<th:block th:if="${notificationConsent != null}">
|
||||
</th:block>
|
||||
|
||||
<!-- Privacy Collect (사용하지 않도록 설정하면 숨김. 플래그가 없는 화면은 기존대로 노출) -->
|
||||
<th:block th:if="${showPrivacyCollect == null or showPrivacyCollect}">
|
||||
<div class="agreement-item-row">
|
||||
<label class="agreement-checkbox-label">
|
||||
<input type="checkbox" name="privacyCollect" id="privacyCollect"
|
||||
class="agreement-checkbox-input" required th:disabled="${agreeAllEnabled == null or !agreeAllEnabled}">
|
||||
<span class="agreement-checkbox-custom"></span>
|
||||
<th:block th:switch="${registrationType}">
|
||||
<span class="agreement-checkbox-text" th:case="'personal'">
|
||||
<span class="agreement-required">[필수]</span>
|
||||
<span class="agreement-title">개인정보 수집 및 이용 동의</span>
|
||||
</span>
|
||||
<span class="agreement-checkbox-text" th:case="'corporate'">
|
||||
<span class="agreement-required">[필수]</span>
|
||||
<span class="agreement-title">개인정보 수집 및 이용 동의</span>
|
||||
</span>
|
||||
</th:block>
|
||||
</label>
|
||||
<button type="button" class="agreement-toggle-icon" data-target="privacyCollectContent">
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="privacyCollectContent" class="agreement-content" style="display:none;">
|
||||
<div class="agreement-scroll" style="max-height: 400px; overflow-y: auto;">
|
||||
<div class="editor-content"
|
||||
th:utext="${privacyCollect?.contents ?: '개인정보수집동의서 내용을 불러올 수 없습니다.'}"></div>
|
||||
</div>
|
||||
<div class="agreement-btn-wrapper" style="text-align: center; margin-top: 15px;">
|
||||
<button type="button" class="btn-action-primary md agreement-agree-btn"
|
||||
data-checkbox-id="privacyCollect" th:disabled="${agreeAllEnabled == null or !agreeAllEnabled}">동의</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</th:block>
|
||||
|
||||
<!-- Notification Consent (사용하지 않도록 설정했거나 내용이 없으면 숨김) -->
|
||||
<th:block th:if="${(showNotificationConsent == null or showNotificationConsent) and notificationConsent != null}">
|
||||
<div class="agreement-item-row">
|
||||
<label class="agreement-checkbox-label">
|
||||
<input type="checkbox" name="notificationConsent" id="notificationConsent"
|
||||
class="agreement-checkbox-input" required disabled>
|
||||
class="agreement-checkbox-input" required th:disabled="${agreeAllEnabled == null or !agreeAllEnabled}">
|
||||
<span class="agreement-checkbox-custom"></span>
|
||||
<span class="agreement-checkbox-text">
|
||||
<span class="agreement-required">[필수]</span>
|
||||
@@ -105,7 +119,7 @@
|
||||
</div>
|
||||
<div class="agreement-btn-wrapper" style="text-align: center; margin-top: 15px;">
|
||||
<button type="button" class="btn-action-primary md agreement-agree-btn"
|
||||
data-checkbox-id="notificationConsent" disabled>동의</button>
|
||||
data-checkbox-id="notificationConsent" th:disabled="${agreeAllEnabled == null or !agreeAllEnabled}">동의</button>
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
||||
@@ -276,6 +290,12 @@
|
||||
row.style.cursor = 'pointer';
|
||||
row.addEventListener('click', function (e) {
|
||||
if (e.target.closest('.agreement-checkbox-input') || e.target.closest('.agreement-checkbox-custom')) {
|
||||
// 개별 체크박스도 전체동의와 동일하게, 아직 다 읽지 않았으면 경고 알림 표시
|
||||
const checkbox = this.querySelector('.agreement-checkbox-input');
|
||||
if (checkbox && checkbox.disabled) {
|
||||
e.preventDefault();
|
||||
customPopups.showAlert('약관을 상세히 펼쳐서 끝까지 스크롤하여 읽으신 후에 동의하실 수 있습니다.');
|
||||
}
|
||||
return;
|
||||
}
|
||||
const toggleIcon = this.querySelector('.agreement-toggle-icon');
|
||||
|
||||
@@ -122,8 +122,9 @@
|
||||
isPasswordValid &&
|
||||
isPasswordMatch &&
|
||||
userNameElement.val().trim() !== '' &&
|
||||
termsOfUseElement.prop('checked') &&
|
||||
privacyCollectElement.prop('checked') &&
|
||||
// 사용하지 않도록 설정한 약관은 화면에 없으므로(관리 콘솔 > 약관 종류 관리) 없으면 통과 처리
|
||||
(termsOfUseElement.length === 0 || termsOfUseElement.prop('checked')) &&
|
||||
(privacyCollectElement.length === 0 || privacyCollectElement.prop('checked')) &&
|
||||
(notificationConsentElement.length === 0 || notificationConsentElement.prop('checked')) &&
|
||||
mobileNumberElement.val().trim() !== '' &&
|
||||
isAuthVerified;
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<div class="search-box">
|
||||
<button type="button" class="btn btn-primary" id="addUserBtn">
|
||||
<i class="fas fa-plus"></i>
|
||||
<span>개발자 추가</span>
|
||||
<span>개발자 초대</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -96,7 +96,7 @@
|
||||
th:if="${user.userStatus.toString() == 'PENDING'}"
|
||||
th:data-user-id="${user.id}"
|
||||
th:data-user-email="${user.maskedEmailAddr}">
|
||||
초대취소
|
||||
초대 취소
|
||||
</button>
|
||||
</div>
|
||||
<!-- Action Dropdown (Mobile) -->
|
||||
@@ -139,7 +139,7 @@
|
||||
th:if="${user.userStatus.toString() == 'PENDING'}"
|
||||
th:data-user-id="${user.id}"
|
||||
th:data-user-email="${user.maskedEmailAddr}">
|
||||
초대취소
|
||||
초대 취소
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -163,7 +163,7 @@
|
||||
<button type="button" class="list-table-btn list-table-btn--secondary cancel_invitation"
|
||||
th:data-user-id="${user.id}"
|
||||
th:data-user-email="${user.maskedMobileNumber}">
|
||||
초대취소
|
||||
초대 취소
|
||||
</button>
|
||||
</div>
|
||||
<!-- Mobile Dropdown -->
|
||||
@@ -180,7 +180,7 @@
|
||||
<button type="button" class="dropdown-item cancel_invitation"
|
||||
th:data-user-id="${user.id}"
|
||||
th:data-user-email="${user.maskedMobileNumber}">
|
||||
초대취소
|
||||
초대 취소
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -294,7 +294,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Add event listeners to the "개발자 추가" buttons
|
||||
// Add event listeners to the "개발자 초대" buttons
|
||||
const addUserBtn = document.getElementById('addUserBtn');
|
||||
|
||||
if (addUserBtn) {
|
||||
|
||||
@@ -5,33 +5,25 @@
|
||||
<section layout:fragment="title">
|
||||
<div class="page-title-banner">
|
||||
<img th:src="@{/img/img_title_bg.png}" class="title-image">
|
||||
<h1 th:text="${isTermsOfUse ? '이용약관' : (isPrivacyCollect ? '개인정보수집동의서' : '알림 수신 동의서')}">이용 약관</h1>
|
||||
<h1 th:text="${agreementTitle} ?: '약관'">이용 약관</h1>
|
||||
</div>
|
||||
</section>
|
||||
<section layout:fragment="contentFragment">
|
||||
<div class="terms-container">
|
||||
<!-- Title Bar -->
|
||||
<div class="common-title-bar">
|
||||
<h2 class="common-title" th:text="${isTermsOfUse ? '이용약관' : (isPrivacyCollect ? '개인정보수집동의서' : '알림 수신 동의서')}">이용약관</h2>
|
||||
<h2 class="common-title" th:text="${agreementTitle} ?: '약관'">이용약관</h2>
|
||||
</div>
|
||||
|
||||
<!-- Tab Navigation -->
|
||||
<div class="terms-tabs">
|
||||
<a th:href="@{/agreements/terms(tab='terms')}"
|
||||
<!-- Tab Navigation : 노출 대상과 순서는 관리 콘솔 '약관 종류 관리'에서 설정한다 -->
|
||||
<div class="terms-tabs" th:if="${not #lists.isEmpty(termsTabs)}">
|
||||
<a th:each="termsTab : ${termsTabs}"
|
||||
th:href="@{/agreements/terms(tab=${termsTab.tab})}"
|
||||
class="tab-link"
|
||||
th:classappend="${isTermsOfUse ? 'active' : ''}">
|
||||
th:classappend="${termsTab.active ? 'active' : ''}"
|
||||
th:text="${termsTab.title}">
|
||||
이용약관
|
||||
</a>
|
||||
<a th:href="@{/agreements/terms(tab='privacy-collect')}"
|
||||
class="tab-link"
|
||||
th:classappend="${isPrivacyCollect ? 'active' : ''}">
|
||||
개인정보수집동의서
|
||||
</a>
|
||||
<a th:href="@{/agreements/terms(tab='notification')}"
|
||||
class="tab-link"
|
||||
th:classappend="${isNotification ? 'active' : ''}">
|
||||
알림 수신 동의서
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Version Selector -->
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<!-- Modal Header -->
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title">초대취소</h3>
|
||||
<h3 class="modal-title">초대 취소</h3>
|
||||
<button type="button" class="modal-close" id="cancelInvitationPopupCloseButton">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
@@ -24,8 +24,8 @@
|
||||
<strong id="cancelInvitationTarget" style="color: #4B9BFF;"></strong>에 대한<br>초대를 취소하시겠습니까?
|
||||
</p>
|
||||
|
||||
<!-- 알림 수신 동의 체크박스 -->
|
||||
<div class="pop_consent_group" style="margin-top: 16px;">
|
||||
<!-- 알림 수신 동의 체크박스 : '알림 수신 동의서'가 약관 페이지에 배치되어 있을 때만 노출 -->
|
||||
<div class="pop_consent_group" style="margin-top: 16px;" th:if="${notificationConsentAvailable}">
|
||||
<label class="pop_consent_label"
|
||||
style="display: flex; align-items: flex-start; gap: 8px; font-size: 13px; color: #475569; line-height: 1.5; cursor: pointer;">
|
||||
<input type="checkbox" id="cancelInvitationNotifyConsent" style="margin-top: 3px; flex-shrink: 0;">
|
||||
@@ -37,12 +37,20 @@
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- 알림 수신 동의서를 사용하지 않거나 약관 페이지에 배치하지 않은 경우 : 안내 문구만 노출 -->
|
||||
<div class="pop_consent_group" style="margin-top: 16px;" th:unless="${notificationConsentAvailable}">
|
||||
<p style="margin: 0; font-size: 13px; color: #475569; line-height: 1.5;">
|
||||
현재 알림 수신 동의서를 운영하지 않아 수신자에게 초대 취소 알림 메시지가 발송되지 않습니다.
|
||||
초대 취소는 정상적으로 진행됩니다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Footer -->
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary btn-submit" id="cancelInvitationPopupCancelButton">닫기</button>
|
||||
<button type="button" class="btn btn-primary btn-submit" id="cancelInvitationPopupConfirmButton">초대취소</button>
|
||||
<button type="button" class="btn btn-primary btn-submit" id="cancelInvitationPopupConfirmButton">초대 취소</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<!-- Modal Header -->
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title">개발자 추가</h3>
|
||||
<h3 class="modal-title">개발자 초대</h3>
|
||||
<button type="button" class="modal-close" id="userInvitePopupCloseButton">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
@@ -21,7 +21,7 @@
|
||||
<!-- Modal Body -->
|
||||
<div class="modal-body">
|
||||
<p id="userInvitePopupMessage" style="text-align: center; margin-bottom: 24px; color: #64748B;">
|
||||
추가할 개발자 휴대폰 번호를 입력해 주세요.
|
||||
초대할 개발자 휴대폰 번호를 입력해 주세요.
|
||||
</p>
|
||||
|
||||
<!-- Mobile Input Field -->
|
||||
@@ -34,8 +34,8 @@
|
||||
<div id="userInvitePopupError" class="error-message"></div>
|
||||
</div>
|
||||
|
||||
<!-- 알림 수신 동의 체크박스 -->
|
||||
<div class="pop_consent_group" style="margin-top: 16px;">
|
||||
<!-- 알림 수신 동의 체크박스 : '알림 수신 동의서'가 약관 페이지에 배치되어 있을 때만 노출 -->
|
||||
<div class="pop_consent_group" style="margin-top: 16px;" th:if="${notificationConsentAvailable}">
|
||||
<label class="pop_consent_label"
|
||||
style="display: flex; align-items: flex-start; gap: 8px; font-size: 13px; color: #475569; line-height: 1.5; cursor: pointer;">
|
||||
<input type="checkbox" id="userInviteNotifyConsent" style="margin-top: 3px; flex-shrink: 0;">
|
||||
@@ -47,6 +47,14 @@
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- 알림 수신 동의서를 사용하지 않거나 약관 페이지에 배치하지 않은 경우 : 안내 문구만 노출 -->
|
||||
<div class="pop_consent_group" style="margin-top: 16px;" th:unless="${notificationConsentAvailable}">
|
||||
<p style="margin: 0; font-size: 13px; color: #475569; line-height: 1.5;">
|
||||
현재 알림 수신 동의서를 운영하지 않아 수신자에게 초대 메시지가 발송되지 않습니다.
|
||||
초대는 정상적으로 진행되며, 초대받은 개발자에게 직접 안내해 주세요.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Footer -->
|
||||
|
||||
Reference in New Issue
Block a user