- 모바일 약관 탭 UI 개선 - 스크롤 추가 및 스타일 조정
- 필수 입력 경고 메세지 추가 및 인증 미완료 경고 추가
This commit is contained in:
@@ -3,31 +3,72 @@ package com.eactive.apim.portal.apps.auth.service;
|
|||||||
import com.eactive.apim.portal.portaluser.entity.TwoFactorAuth;
|
import com.eactive.apim.portal.portaluser.entity.TwoFactorAuth;
|
||||||
import com.eactive.apim.portal.portaluser.repository.TwoFactorAuthRepository;
|
import com.eactive.apim.portal.portaluser.repository.TwoFactorAuthRepository;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.crypto.Mac;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class AuthNumberStorage {
|
public class AuthNumberStorage {
|
||||||
|
|
||||||
|
private static final String HASH_ALGORITHM = "HmacSHA256";
|
||||||
|
|
||||||
private final TwoFactorAuthRepository twoFactorAuthRepository;
|
private final TwoFactorAuthRepository twoFactorAuthRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* recipient(이메일/휴대폰)는 암호화 컬럼이라 값으로 동등 조회/중복정리가 불가능하다.
|
||||||
|
* 그래서 recipient 평문을 결정적 해시(HMAC-SHA256)로 변환해 별도 조회 키(recipientKey)로 사용한다.
|
||||||
|
* 평문을 그대로 노출하지 않도록 pepper 로 기존 암호화 키를 재사용한다.
|
||||||
|
*/
|
||||||
|
@Value("${encryption.key:kjbank_portal_application_1357902}")
|
||||||
|
private String hashPepper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public AuthNumberStorage(TwoFactorAuthRepository twoFactorAuthRepository) {
|
public AuthNumberStorage(TwoFactorAuthRepository twoFactorAuthRepository) {
|
||||||
this.twoFactorAuthRepository = twoFactorAuthRepository;
|
this.twoFactorAuthRepository = twoFactorAuthRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveAuthNumber(String recipientKey, String authNumber, LocalDateTime expiresAt) {
|
public void saveAuthNumber(String recipientKey, String authNumber, LocalDateTime expiresAt) {
|
||||||
twoFactorAuthRepository.deleteAllByRecipient(recipientKey);
|
String lookupKey = hashRecipient(recipientKey);
|
||||||
|
twoFactorAuthRepository.deleteAllByRecipientKey(lookupKey);
|
||||||
TwoFactorAuth auth = new TwoFactorAuth(recipientKey, authNumber, expiresAt);
|
TwoFactorAuth auth = new TwoFactorAuth(recipientKey, authNumber, expiresAt);
|
||||||
|
auth.setRecipientKey(lookupKey);
|
||||||
twoFactorAuthRepository.save(auth);
|
twoFactorAuthRepository.save(auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<TwoFactorAuth> getAuthNumber(String recipientKey) {
|
public Optional<TwoFactorAuth> getAuthNumber(String recipientKey) {
|
||||||
return twoFactorAuthRepository.findByRecipient(recipientKey);
|
return twoFactorAuthRepository.findByRecipientKey(hashRecipient(recipientKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteAuthNumber(String recipientKey) {
|
public void deleteAuthNumber(String recipientKey) {
|
||||||
twoFactorAuthRepository.deleteById(recipientKey);
|
twoFactorAuthRepository.deleteAllByRecipientKey(hashRecipient(recipientKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* recipient 평문을 결정적 해시(HMAC-SHA256, 소문자 hex)로 변환한다.
|
||||||
|
* 동일 입력 → 동일 키이므로 저장/조회/정리에서 동등 매칭이 가능하다.
|
||||||
|
*/
|
||||||
|
private String hashRecipient(String recipient) {
|
||||||
|
if (recipient == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Mac mac = Mac.getInstance(HASH_ALGORITHM);
|
||||||
|
mac.init(new SecretKeySpec(hashPepper.getBytes(StandardCharsets.UTF_8), HASH_ALGORITHM));
|
||||||
|
byte[] digest = mac.doFinal(recipient.getBytes(StandardCharsets.UTF_8));
|
||||||
|
StringBuilder sb = new StringBuilder(digest.length * 2);
|
||||||
|
for (byte b : digest) {
|
||||||
|
sb.append(Character.forDigit((b >> 4) & 0xF, 16));
|
||||||
|
sb.append(Character.forDigit(b & 0xF, 16));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
|
throw new IllegalStateException("2FA recipient 해시 생성 실패", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,11 +5,14 @@ import com.eactive.apim.portal.common.converter.EnabledStatusConverter;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import nz.net.ultraq.thymeleaf.layoutdialect.LayoutDialect;
|
import nz.net.ultraq.thymeleaf.layoutdialect.LayoutDialect;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
import org.springframework.boot.web.servlet.ServletComponentScan;
|
import org.springframework.boot.web.servlet.ServletComponentScan;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Profile;
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.core.env.Profiles;
|
||||||
import org.springframework.data.domain.PageRequest;
|
import org.springframework.data.domain.PageRequest;
|
||||||
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
|
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
|
||||||
import org.springframework.format.FormatterRegistry;
|
import org.springframework.format.FormatterRegistry;
|
||||||
@@ -18,10 +21,13 @@ import org.springframework.web.filter.CharacterEncodingFilter;
|
|||||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||||
import org.springframework.web.multipart.support.MultipartFilter;
|
import org.springframework.web.multipart.support.MultipartFilter;
|
||||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||||
|
import org.springframework.web.servlet.config.annotation.ResourceChainRegistration;
|
||||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
import org.springframework.web.servlet.resource.PathResourceResolver;
|
import org.springframework.web.servlet.resource.PathResourceResolver;
|
||||||
|
import org.springframework.web.servlet.resource.ResourceUrlEncodingFilter;
|
||||||
|
import org.springframework.web.servlet.resource.VersionResourceResolver;
|
||||||
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
|
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
|
||||||
|
|
||||||
|
|
||||||
@@ -32,6 +38,16 @@ public class PortalConfigWebDispatcherServlet implements WebMvcConfigurer {
|
|||||||
|
|
||||||
public static final String ERROR = "error";
|
public static final String ERROR = "error";
|
||||||
|
|
||||||
|
private final Environment environment;
|
||||||
|
|
||||||
|
// 정적자원 해시 버전닝 토글(application.yml: app.resource-versioning.enabled).
|
||||||
|
// prod 는 이 값을 무시하고 항상 ON 으로 동작한다(isResourceVersioningEnabled 참고).
|
||||||
|
@Value("${app.resource-versioning.enabled:true}")
|
||||||
|
private boolean resourceVersioningEnabled;
|
||||||
|
|
||||||
|
public PortalConfigWebDispatcherServlet(Environment environment) {
|
||||||
|
this.environment = environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -61,16 +77,45 @@ public class PortalConfigWebDispatcherServlet implements WebMvcConfigurer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
|
addStaticResourceHandler(registry, "/css/**", "/css/", "classpath:/static/css/");
|
||||||
|
addStaticResourceHandler(registry, "/webfonts/**", "/webfonts/", "classpath:/static/webfonts/");
|
||||||
|
addStaticResourceHandler(registry, "/font/**", "/font/", "classpath:/static/font/");
|
||||||
|
addStaticResourceHandler(registry, "/html/**", "/html/", "classpath:/static/html/");
|
||||||
|
addStaticResourceHandler(registry, "/images/**", "/images/", "classpath:/static/images/");
|
||||||
|
addStaticResourceHandler(registry, "/img/**", "/img/", "classpath:/static/img/");
|
||||||
|
addStaticResourceHandler(registry, "/js/**", "/js/", "classpath:/static/js/");
|
||||||
|
addStaticResourceHandler(registry, "/plugins/**", "/plugins/", "classpath:/static/plugins/");
|
||||||
|
addStaticResourceHandler(registry, "/favicon.ico", "/favicon.ico", "classpath:/static/favicon.ico");
|
||||||
|
}
|
||||||
|
|
||||||
registry.addResourceHandler("/css/**").addResourceLocations("/css/", "classpath:/static/css/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver());
|
/**
|
||||||
registry.addResourceHandler("/webfonts/**").addResourceLocations("/webfonts/", "classpath:/static/webfonts/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver());
|
* 정적자원 핸들러 공통 등록.
|
||||||
registry.addResourceHandler("/font/**").addResourceLocations("/font/", "classpath:/static/font/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver());
|
*
|
||||||
registry.addResourceHandler("/html/**").addResourceLocations("/html/", "classpath:/static/html/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver());
|
* <p>콘텐츠 해시 버전닝이 켜져 있으면 {@link VersionResourceResolver} 를 체인 앞단에 추가하여
|
||||||
registry.addResourceHandler("/images/**").addResourceLocations("/images/", "classpath:/static/images/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver());
|
* {@code /css/main.css} 요청을 내용 해시가 포함된 {@code /css/main-<hash>.css} 로 매핑한다.
|
||||||
registry.addResourceHandler("/img/**").addResourceLocations("/img/", "classpath:/static/img/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver());
|
* 내용이 바뀌면 URL 이 바뀌므로 강제 새로고침 없이 브라우저 캐시가 무효화된다. Thymeleaf 의
|
||||||
registry.addResourceHandler("/js/**").addResourceLocations("/js/", "classpath:/static/js/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver());
|
* {@code @{/css/main.css}} 링크는 {@link #resourceUrlEncodingFilter()} 가 해시 URL 로 치환한다.</p>
|
||||||
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/", "classpath:/static/plugins/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver());
|
*/
|
||||||
registry.addResourceHandler("/favicon.ico").addResourceLocations("/favicon.ico", "classpath:/static/favicon.ico").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver());
|
private void addStaticResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) {
|
||||||
|
ResourceChainRegistration chain = registry.addResourceHandler(pattern)
|
||||||
|
.addResourceLocations(locations)
|
||||||
|
.setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS))
|
||||||
|
.resourceChain(true);
|
||||||
|
if (isResourceVersioningEnabled()) {
|
||||||
|
chain.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
|
||||||
|
}
|
||||||
|
chain.addResolver(new PathResourceResolver());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 정적자원 해시 버전닝 활성 여부. prod 프로파일은 토글과 무관하게 항상 ON,
|
||||||
|
* 그 외 프로파일은 {@code app.resource-versioning.enabled} 값을 따른다.
|
||||||
|
*/
|
||||||
|
private boolean isResourceVersioningEnabled() {
|
||||||
|
if (environment.acceptsProfiles(Profiles.of("prod"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return resourceVersioningEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -123,4 +168,13 @@ public class PortalConfigWebDispatcherServlet implements WebMvcConfigurer {
|
|||||||
registrationBean.addUrlPatterns("/*");
|
registrationBean.addUrlPatterns("/*");
|
||||||
return registrationBean;
|
return registrationBean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thymeleaf {@code @{...}} 링크를 해시 버전 URL 로 치환하기 위한 필터.
|
||||||
|
* {@code @EnableWebMvc} 환경에서는 자동 등록되지 않으므로 명시적으로 빈 등록한다.
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public ResourceUrlEncodingFilter resourceUrlEncodingFilter() {
|
||||||
|
return new ResourceUrlEncodingFilter();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ gateway:
|
|||||||
|
|
||||||
|
|
||||||
portal:
|
portal:
|
||||||
auth-virtual-code: 654321
|
# auth-virtual-code: 654321
|
||||||
test-auth-notice-enabled: true
|
test-auth-notice-enabled: true
|
||||||
dev:
|
dev:
|
||||||
# application.yml의 `page:` 트리(브레드크럼/메뉴 이름) 라이브 반영
|
# application.yml의 `page:` 트리(브레드크럼/메뉴 이름) 라이브 반영
|
||||||
|
|||||||
@@ -11,13 +11,9 @@ spring:
|
|||||||
cachecontrol:
|
cachecontrol:
|
||||||
max-age: 86400
|
max-age: 86400
|
||||||
public: true
|
public: true
|
||||||
# 정적자원 콘텐츠 해시 버전닝: 운영은 항상 ON (파라미터로 비활성화 불가).
|
# 정적자원 콘텐츠 해시 버전닝은 PortalConfigWebDispatcherServlet 가 prod 프로파일에서
|
||||||
|
# 항상 ON 으로 수행한다(app.resource-versioning.enabled 토글 무시).
|
||||||
# 해시 URL + 장기 캐시(max-age 86400)로 배포 시 자동 캐시 무효화.
|
# 해시 URL + 장기 캐시(max-age 86400)로 배포 시 자동 캐시 무효화.
|
||||||
chain:
|
|
||||||
strategy:
|
|
||||||
content:
|
|
||||||
enabled: true
|
|
||||||
paths: /**
|
|
||||||
jpa:
|
jpa:
|
||||||
properties:
|
properties:
|
||||||
hibernate:
|
hibernate:
|
||||||
|
|||||||
@@ -33,15 +33,10 @@ spring:
|
|||||||
max-age: 0
|
max-age: 0
|
||||||
must-revalidate: true
|
must-revalidate: true
|
||||||
no-cache: true
|
no-cache: true
|
||||||
# 정적자원 콘텐츠 해시 버전닝: /css/main.css -> /css/main-<hash>.css 자동 치환.
|
# 정적자원 콘텐츠 해시 버전닝(/css/main.css -> /css/main-<hash>.css)은
|
||||||
# 내용이 바뀌면 URL이 바뀌어 강제 새로고침 없이 캐시가 무효화됨.
|
# @EnableWebMvc 로 WebMvcAutoConfiguration 이 비활성화되어 spring.web.resources.chain
|
||||||
# 비-prod 는 app.resource-versioning.enabled 파라미터로 제어(기본 ON),
|
# 설정으로는 적용되지 않는다. 실제 버전닝은 PortalConfigWebDispatcherServlet 가
|
||||||
# false 로 두면 버전닝을 꺼서 sourceMap 디버깅이 가능. (prod 는 application-prod.yml 에서 항상 ON)
|
# 아래 app.resource-versioning.enabled 토글을 읽어 직접 수행한다.
|
||||||
chain:
|
|
||||||
strategy:
|
|
||||||
content:
|
|
||||||
enabled: ${app.resource-versioning.enabled:true}
|
|
||||||
paths: /**
|
|
||||||
|
|
||||||
jta:
|
jta:
|
||||||
enabled: true
|
enabled: true
|
||||||
@@ -61,7 +56,7 @@ spring:
|
|||||||
|
|
||||||
# 정적자원 해시 버전닝 토글 (prod 제외 전 프로파일에 적용).
|
# 정적자원 해시 버전닝 토글 (prod 제외 전 프로파일에 적용).
|
||||||
# false 로 변경 시 /css/main.css 가 해시 없이 그대로 서빙되어 sourceMap 디버깅 가능.
|
# false 로 변경 시 /css/main.css 가 해시 없이 그대로 서빙되어 sourceMap 디버깅 가능.
|
||||||
# prod 는 application-prod.yml 에서 항상 ON 으로 고정되어 이 값을 무시함.
|
# prod 는 PortalConfigWebDispatcherServlet 에서 항상 ON 으로 고정되어 이 값을 무시함.
|
||||||
app:
|
app:
|
||||||
resource-versioning:
|
resource-versioning:
|
||||||
enabled: true
|
enabled: true
|
||||||
@@ -257,7 +252,7 @@ page:
|
|||||||
path: "#"
|
path: "#"
|
||||||
children:
|
children:
|
||||||
terms:
|
terms:
|
||||||
name: "이용약관 및 개인정보처리방침"
|
name: "이용약관"
|
||||||
path: "/agreements/terms"
|
path: "/agreements/terms"
|
||||||
about:
|
about:
|
||||||
name: 안내
|
name: 안내
|
||||||
|
|||||||
@@ -17822,6 +17822,13 @@ body.commission-print-page .btn-primary:hover {
|
|||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.terms-tabs {
|
.terms-tabs {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
overflow-x: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
.terms-tabs::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.terms-tabs .tab-link {
|
.terms-tabs .tab-link {
|
||||||
@@ -17843,8 +17850,10 @@ body.commission-print-page .btn-primary:hover {
|
|||||||
}
|
}
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.terms-tabs .tab-link {
|
.terms-tabs .tab-link {
|
||||||
padding: 16px 16px;
|
flex: 1 0 auto;
|
||||||
font-size: 16px;
|
white-space: nowrap;
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 14px;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -86,6 +86,15 @@
|
|||||||
|
|
||||||
@media (max-width: $breakpoint-sm) {
|
@media (max-width: $breakpoint-sm) {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
// 탭이 가로폭을 넘어갈 경우 가로 스크롤 허용
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
overflow-x: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
scrollbar-width: none; // Firefox
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none; // Chrome/Safari
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-link {
|
.tab-link {
|
||||||
@@ -107,8 +116,11 @@
|
|||||||
border-radius: 12px 12px 0 0;
|
border-radius: 12px 12px 0 0;
|
||||||
|
|
||||||
@media (max-width: $breakpoint-sm) {
|
@media (max-width: $breakpoint-sm) {
|
||||||
padding: $spacing-md $spacing-md;
|
// 글자 한 줄 유지 + 글자 크기/높이 축소
|
||||||
font-size: $font-size-base;
|
flex: 1 0 auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: $spacing-sm $spacing-md;
|
||||||
|
font-size: $font-size-sm;
|
||||||
gap: $spacing-xs;
|
gap: $spacing-xs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -179,7 +179,9 @@
|
|||||||
|
|
||||||
form.submit();
|
form.submit();
|
||||||
} else {
|
} else {
|
||||||
customPopups.showAlert('모든 필수 항목을 입력하고 약관에 동의해주세요.');
|
customPopups.showAlert(!isAuthVerified
|
||||||
|
? '인증번호확인을 완료해주세요.'
|
||||||
|
: '모든 필수 항목을 입력하고 약관에 동의해주세요.');
|
||||||
isSubmitting = false;
|
isSubmitting = false;
|
||||||
btn_register.disabled = false;
|
btn_register.disabled = false;
|
||||||
document.getElementById('loadingOverlay').classList.remove('active');
|
document.getElementById('loadingOverlay').classList.remove('active');
|
||||||
|
|||||||
Reference in New Issue
Block a user