Merge branch 'main-design-layout' of https://git.eactive.synology.me:8090/kjb-eapim/eapim-portal into main-design-layout

This commit is contained in:
Rinjae
2025-11-21 21:59:50 +09:00
68 changed files with 1433 additions and 1913 deletions
@@ -9,12 +9,8 @@ public final class Constants {
public static final String APIM_FOR_OBP_KEY = "x-obp-trust-system";
public static final String APIM_FOR_OBP_VALUE = "APIMPT-0002-QVBJTVBULTAwMDI=";
public static final String APIM_CODE_FOR_OBP_KEY = "x-obp-partnercode";
public static final String APIM_CODE_FOR_OBP_VALUE = "100001-01";
public static final String SMS_URL = "/api/messaging/sms";
public static final String COMMISSION_URL = "/api/billing/billing/findBillingForCondition";
@@ -6,6 +6,7 @@ import com.eactive.apim.portal.apps.commission.dto.CommissionSearch;
import com.eactive.apim.portal.apps.commission.exception.ErrorUtil;
import com.eactive.apim.portal.apps.commission.service.ExternalService;
import com.eactive.apim.portal.common.util.SecurityUtil;
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
@@ -16,7 +17,6 @@ import lombok.RequiredArgsConstructor;
import org.apache.commons.collections.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
@@ -38,14 +38,7 @@ public class CommissionController {
private final Logger log = LoggerFactory.getLogger(CommissionController.class);
private final ExternalService externalService;
@Value("${obm-url:http://localhost:8080}")
private String obmUrl;
@Value("${provider-code:100001-01}")
private String providerCode;
// ==================== View Controllers ====================
private final PortalPropertyService portalPropertyService;
@GetMapping("/manage")
public String commissionManagePage(Model model) {
@@ -66,11 +59,10 @@ public class CommissionController {
search.setYear(year);
search.setMonth(month);
search.setProvider(providerCode);
if (!StringUtils.isEmpty(search.getPartnerCode())) {
try {
String url = obmUrl + Constants.COMMISSION_URL;
String obmUrl = portalPropertyService.getPortalPropertiesAsMap("Portal").getOrDefault("obp.obm.url", "");
String url = obmUrl + Constants.COMMISSION_PRINT_URL;
String result = externalService.getResponseHttpPost(url, toJson(search));
HashMap<String, Object> returnObj = new ObjectMapper().readValue(result, HashMap.class);
@@ -85,10 +77,10 @@ public class CommissionController {
search.setPartnerCode("000002-02");
String togetherResult = externalService.getResponseHttpPost(url, toJson(search));
HashMap<String, Object> togetherLendingReturnObj = new ObjectMapper().readValue(togetherResult, HashMap.class);
HashMap togetherLendingReturnObj = new ObjectMapper().readValue(togetherResult, HashMap.class);
log.debug("togetherLendingReturnObj : " + togetherLendingReturnObj.toString());
if (!StringUtils.isEmpty(togetherLendingReturnObj.get("error"))) {
HashMap<String, Object> error = (HashMap) togetherLendingReturnObj.get("error");
HashMap error = (HashMap) togetherLendingReturnObj.get("error");
model.addAttribute("error", error.get("message").toString());
return "apps/commission/commissionPrint";
}
@@ -119,11 +111,11 @@ public class CommissionController {
@PostMapping()
public ResponseEntity<?> list(@RequestBody CommissionSearch search) {
CommissionDTO commissionDTO = null;
search.setProvider(providerCode);
search.setPartnerCode(SecurityUtil.getUserOrg().getOrgCode());
if (!StringUtils.isEmpty(search.getPartnerCode())) {
try {
String obmUrl = portalPropertyService.getPortalPropertiesAsMap("Portal").getOrDefault("obp.obm.url", "");
String url = obmUrl + Constants.COMMISSION_URL;
String result = externalService.getResponseHttpPost(url, toJson(search));
HashMap<String, Object> returnObj = new ObjectMapper().readValue(result, HashMap.class);
@@ -170,6 +162,28 @@ public class CommissionController {
}
}
/**
* Safely parse double value from HashMap
* Handles both numeric types and string representations
*/
private double safeGetDouble(HashMap<String, Object> map, String key) {
Object value = map.get(key);
if (value == null) {
return 0.0;
}
try {
if (value instanceof Number) {
return ((Number) value).doubleValue();
} else if (value instanceof String) {
return Double.parseDouble((String) value);
}
return 0.0;
} catch (NumberFormatException e) {
log.warn("Failed to parse double value for key: {} with value: {}", key, value);
return 0.0;
}
}
public HashMap<String, Object> mergeCommissionData(HashMap<String, Object> o1, HashMap<String, Object> o2) {
HashMap<String, Object> mergeMap = new HashMap<String, Object>();
@@ -182,22 +196,22 @@ public class CommissionController {
} else {
resultStatus = o2Status;
}
double apiTotalValueSum = MapUtils.getDouble(o1, "apiTotalValueSum") + MapUtils.getDouble(o2, "apiTotalValueSum");
double apiSum = MapUtils.getDouble(o1, "apiSum") + MapUtils.getDouble(o2, "apiSum");
double apiTotalValueSum2 = MapUtils.getDouble(o1, "apiTotalValueSum2") + MapUtils.getDouble(o2, "apiTotalValueSum2");
double apiSum2 = MapUtils.getDouble(o1, "apiSum2") + MapUtils.getDouble(o2, "apiSum2");
double productTotalValueCountSum = MapUtils.getDouble(o1, "productTotalValueCountSum") + MapUtils.getDouble(o2, "productTotalValueCountSum");
double productTotalValueAmountSum = MapUtils.getDouble(o1, "productTotalValueAmountSum") + MapUtils.getDouble(o2, "productTotalValueAmountSum");
double productSum = MapUtils.getDouble(o1, "productSum") + MapUtils.getDouble(o2, "productSum");
double productTotalValueCountSum2 = MapUtils.getDouble(o1, "productTotalValueCountSum2") + MapUtils.getDouble(o2, "productTotalValueCountSum2");
double productTotalValueAmountSum2 = MapUtils.getDouble(o1, "productTotalValueAmountSum2") + MapUtils.getDouble(o2, "productTotalValueAmountSum2");
double productSum2 = MapUtils.getDouble(o1, "productSum2") + MapUtils.getDouble(o2, "productSum2");
double totalValueCountSum = MapUtils.getDouble(o1, "totalValueCountSum") + MapUtils.getDouble(o2, "totalValueCountSum");
double totalValueCountSum2 = MapUtils.getDouble(o1, "totalValueCountSum2") + MapUtils.getDouble(o2, "totalValueCountSum2");
double totalValueAmountSum = MapUtils.getDouble(o1, "totalValueAmountSum") + MapUtils.getDouble(o2, "totalValueAmountSum");
double totalValueAmountSum2 = MapUtils.getDouble(o1, "totalValueAmountSum2") + MapUtils.getDouble(o2, "totalValueAmountSum2");
double sum = MapUtils.getDouble(o1, "sum") + MapUtils.getDouble(o2, "sum");
double sum2 = MapUtils.getDouble(o1, "sum2") + MapUtils.getDouble(o2, "sum2");
double apiTotalValueSum = safeGetDouble(o1, "apiTotalValueSum") + safeGetDouble(o2, "apiTotalValueSum");
double apiSum = safeGetDouble(o1, "apiSum") + safeGetDouble(o2, "apiSum");
double apiTotalValueSum2 = safeGetDouble(o1, "apiTotalValueSum2") + safeGetDouble(o2, "apiTotalValueSum2");
double apiSum2 = safeGetDouble(o1, "apiSum2") + safeGetDouble(o2, "apiSum2");
double productTotalValueCountSum = safeGetDouble(o1, "productTotalValueCountSum") + safeGetDouble(o2, "productTotalValueCountSum");
double productTotalValueAmountSum = safeGetDouble(o1, "productTotalValueAmountSum") + safeGetDouble(o2, "productTotalValueAmountSum");
double productSum = safeGetDouble(o1, "productSum") + safeGetDouble(o2, "productSum");
double productTotalValueCountSum2 = safeGetDouble(o1, "productTotalValueCountSum2") + safeGetDouble(o2, "productTotalValueCountSum2");
double productTotalValueAmountSum2 = safeGetDouble(o1, "productTotalValueAmountSum2") + safeGetDouble(o2, "productTotalValueAmountSum2");
double productSum2 = safeGetDouble(o1, "productSum2") + safeGetDouble(o2, "productSum2");
double totalValueCountSum = safeGetDouble(o1, "totalValueCountSum") + safeGetDouble(o2, "totalValueCountSum");
double totalValueCountSum2 = safeGetDouble(o1, "totalValueCountSum2") + safeGetDouble(o2, "totalValueCountSum2");
double totalValueAmountSum = safeGetDouble(o1, "totalValueAmountSum") + safeGetDouble(o2, "totalValueAmountSum");
double totalValueAmountSum2 = safeGetDouble(o1, "totalValueAmountSum2") + safeGetDouble(o2, "totalValueAmountSum2");
double sum = safeGetDouble(o1, "sum") + safeGetDouble(o2, "sum");
double sum2 = safeGetDouble(o1, "sum2") + safeGetDouble(o2, "sum2");
List<Map> o1List = (ArrayList) o1.get("list");
List<Map> o2pList = (ArrayList) o2.get("list");
@@ -1,6 +1,5 @@
package com.eactive.apim.portal.apps.commission.dto;
import com.eactive.apim.portal.portalorg.entity.PortalOrg;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@@ -15,7 +14,4 @@ public class CommissionSearch {
private String partnerCode;
private PortalOrg organization;
private String provider;
}
@@ -1,7 +1,10 @@
package com.eactive.apim.portal.apps.commission.service;
import com.eactive.apim.portal.apps.commission.constant.Constants;
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
import java.util.Map;
import javax.inject.Inject;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
@@ -22,16 +25,13 @@ import org.springframework.web.client.RestTemplate;
*/
@Service
@Transactional
@RequiredArgsConstructor
public class ExternalService {
private final Logger log = LoggerFactory.getLogger(ExternalService.class);
private final RestTemplate restTemplate;
@Inject
public ExternalService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
private final PortalPropertyService portalPropertyService;
/**
* HTTP POST 요청을 통해 외부 API를 호출하고 응답을 받습니다.
@@ -42,11 +42,14 @@ public class ExternalService {
*/
public String getResponseHttpPost(String url, String payload) {
try {
Map<String, String> portalProperties = portalPropertyService.getPortalPropertiesAsMap("Portal");
// HTTP 헤더 설정
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set(Constants.APIM_FOR_OBP_KEY, Constants.APIM_FOR_OBP_VALUE);
headers.set(Constants.APIM_CODE_FOR_OBP_KEY, Constants.APIM_CODE_FOR_OBP_VALUE);
headers.set(Constants.APIM_FOR_OBP_KEY, portalProperties.getOrDefault("obp.api_key","APIMPT-0002-QVBJTVBULTAwMDI="));
headers.set(Constants.APIM_CODE_FOR_OBP_KEY, portalProperties.getOrDefault("obp.partner_code","100001-01"));
// HTTP 요청 엔티티 생성 (헤더 + 본문)
HttpEntity<String> requestEntity = new HttpEntity<>(payload, headers);
@@ -0,0 +1,61 @@
package com.eactive.apim.portal.apps.community.partnership.controller;
import com.eactive.apim.portal.apps.community.partnership.dto.PartnershipApplicationDTO;
import com.eactive.apim.portal.apps.community.partnership.service.PartnershipApplicationFacade;
import com.eactive.apim.portal.common.util.SecurityUtil;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
@Controller
@RequestMapping("/partnership")
public class PartnershipApplicationController {
private final PartnershipApplicationFacade partnershipApplicationFacade;
@Autowired
public PartnershipApplicationController(PartnershipApplicationFacade partnershipApplicationFacade){
this.partnershipApplicationFacade = partnershipApplicationFacade;
}
@GetMapping
public String newPartnershipApplicationForm(Model model, HttpServletRequest request) {
if (!SecurityUtil.isAuthenticated()) {
return "redirect:/login?redirect=/partnership";
}
String referer = request.getHeader("Referer");
if(referer != null && !referer.contains("/partnership")) {
request.getSession().setAttribute("previousPage", referer);
}
model.addAttribute("partnership", new PartnershipApplicationDTO());
return "apps/community/mainPartnershipForm";
}
@PostMapping
public String createPartnershipApplication(@Valid @ModelAttribute PartnershipApplicationDTO partnershipApplicationDTO,
BindingResult bindingResult, HttpServletRequest httpServletRequest, RedirectAttributes redirectAttributes) throws IOException {
if (bindingResult.hasErrors()) {
return "apps/community/mainPartnershipForm";
}
partnershipApplicationFacade.createPartnershipApplication(partnershipApplicationDTO);
// 성공 메시지 추가
redirectAttributes.addFlashAttribute("success", "사업제휴신청이 완료되었습니다.");
httpServletRequest.getSession().removeAttribute("previousPage");
return "redirect:/partnership";
}
}
@@ -0,0 +1,29 @@
package com.eactive.apim.portal.apps.community.partnership.dto;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.Size;
import org.springframework.web.multipart.MultipartFile;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PartnershipApplicationDTO {
@Size(max = 255, message = "제목은 255자를 초과할 수 없습니다.")
private String bizSubject;
private String bizDetail;
/**
* 첨부파일ID
*/
private String fileId;
private List<MultipartFile> files;
}
@@ -0,0 +1,19 @@
package com.eactive.apim.portal.apps.community.partnership.mapper;
import com.eactive.apim.portal.apps.community.partnership.dto.PartnershipApplicationDTO;
import com.eactive.apim.portal.common.mapper.CommonMapper;
import com.eactive.apim.portal.partnershipapplication.entity.PartnershipApplication;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
import org.springframework.stereotype.Component;
@Mapper(componentModel = "spring",
uses = {CommonMapper.class},
unmappedTargetPolicy = ReportingPolicy.IGNORE)
@Component
public interface PartnershipApplicationMapper {
PartnershipApplication toEntity(PartnershipApplicationDTO dto);
}
@@ -0,0 +1,12 @@
package com.eactive.apim.portal.apps.community.partnership.repository;
import com.eactive.apim.portal.partnershipapplication.entity.PartnershipApplication;
import com.eactive.eai.rms.data.EMSDataSource;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
@Repository
@EMSDataSource
public interface PartnershipApplicationRepository extends JpaRepository<PartnershipApplication, String>, JpaSpecificationExecutor<PartnershipApplication> {
}
@@ -0,0 +1,9 @@
package com.eactive.apim.portal.apps.community.partnership.service;
import com.eactive.apim.portal.apps.community.partnership.dto.PartnershipApplicationDTO;
import java.io.IOException;
public interface PartnershipApplicationFacade {
void createPartnershipApplication(PartnershipApplicationDTO partnershipApplicationDTO) throws IOException;
}
@@ -0,0 +1,37 @@
package com.eactive.apim.portal.apps.community.partnership.service;
import com.eactive.apim.portal.apps.community.partnership.dto.PartnershipApplicationDTO;
import com.eactive.apim.portal.apps.community.partnership.mapper.PartnershipApplicationMapper;
import com.eactive.apim.portal.file.entity.FileInfo;
import com.eactive.apim.portal.file.service.FileService;
import com.eactive.apim.portal.file.service.FileTypeContext;
import com.eactive.apim.portal.partnershipapplication.entity.PartnershipApplication;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.io.IOException;
@Service
@RequiredArgsConstructor
public class PartnershipApplicationFacadeImpl implements PartnershipApplicationFacade {
private final PartnershipApplicationService partnershipApplicationService;
private final PartnershipApplicationMapper partnershipApplicationMapper;
private final FileService fileService;
@Override
public void createPartnershipApplication(PartnershipApplicationDTO partnershipApplicationDTO) throws IOException {
FileInfo file = null;
if (!partnershipApplicationDTO.getFiles().isEmpty()) {
FileTypeContext.setFileType("business");
file = fileService.createFile(partnershipApplicationDTO.getFiles());
}
PartnershipApplication partnershipApplication = partnershipApplicationMapper.toEntity(partnershipApplicationDTO);
if (file != null) {
partnershipApplication.setFileId(file.getFileId());
}
partnershipApplicationService.createPartnershipApplication(partnershipApplication);
}
}
@@ -0,0 +1,24 @@
package com.eactive.apim.portal.apps.community.partnership.service;
import com.eactive.apim.portal.apps.community.partnership.repository.PartnershipApplicationRepository;
import com.eactive.apim.portal.partnershipapplication.entity.PartnershipApplication;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class PartnershipApplicationService {
private final PartnershipApplicationRepository partnershipApplicationRepository;
@Autowired
public PartnershipApplicationService(PartnershipApplicationRepository partnershipApplicationRepository) {
this.partnershipApplicationRepository = partnershipApplicationRepository;
}
public void createPartnershipApplication(PartnershipApplication partnershipApplication) {
partnershipApplicationRepository.save(partnershipApplication);
}
}
@@ -10,6 +10,7 @@ import com.eactive.apim.portal.apps.community.notice.service.PortalNoticeFacade;
import com.eactive.apim.portal.apps.main.service.MainApiFacade;
import com.eactive.apim.portal.common.util.SecurityUtil;
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
import java.util.*;
import java.util.stream.Collectors;
@@ -25,13 +26,12 @@ public class IndexController {
private final ApiSearchFacade apiSearchFacade;
private final MainApiFacade mainApiFacade;
private final PortalNoticeFacade portalNoticeFacade;
private final PortalPropertyService portalPropertyService;
/**
* 메인 페이지 API는 Portal Property 에 main.service.list 에 등록된 그룹을 기준으로 API를 조회함.
* 프로퍼티에 등록되어 있어도 어드민의 그룹 표시 설정이 N이면 표시 되지 않음.
* 어드민의 API 스펙의 표시 설정이 N이면 표시되지 않음.
* 그룹 또는 권한이 지정된 경우, 해당사용자의 권한 또는 기관이 일치해야 표시됨.
* 어드민의 API 스펙의 표시 설정이 N이면 표시되지 않음. 그룹 또는 권한이 지정된 경우, 해당사용자의 권한 또는 기관이 일치해야 표시됨.
* <p>
* 검색어는 Portal Property 또는 메인 관리 화면에 main.keyword.list 에 등록된 내용이 표시 됨.
*
@@ -40,23 +40,8 @@ public class IndexController {
*/
@GetMapping("/")
public String index(Model model) {
// Step 1: Fetch and sort API services
List<ApiServiceDTO> apiServices = getSortedApiServices(null);
// Step 2: Create a map of API ID to ApiServiceDTO for icon mapping
Map<String, ApiServiceDTO> mainIconsMap = createMainIconsMap(apiServices);
// Step 3: Merge all API lists from apiServices
List<ApiServiceApiDTO> allApis = getAllApis(apiServices);
// Step 4: Enrich API spec info DTOs with main icon and service info
List<ApiSpecInfoDto> apiSpecInfoDtos = enrichApiSpecInfo(apiServices, allApis, mainIconsMap);
// Step 5: Calculate total API count
int totalApiCount = calculateTotalApiCount(apiServices);
// Step 6: Fetch portal notices and hashtags
List<PortalNoticeDTO> portalNotices = portalNoticeFacade.getLatestNotices();
List<ApiServiceDTO> services = mainApiFacade.getOpenApiServices();
List<String> hashTags = mainApiFacade.getHashTags();
ApiGroupSearch search = new ApiGroupSearch();
@@ -64,107 +49,19 @@ public class IndexController {
int selectedApiCount = (int) searchResult.get("selectedApiCount");
// 서비스 이용 수
int serviceCount = ((List<?>) searchResult.get("services")).size();
// 승인된 기업 건수
int approvedOrgCount = mainApiFacade.getApprovedOrgCount();
// Step 7: Add attributes to model
addAttributesToModel(model, apiServices, apiSearchFacade.sortApisByService(apiSpecInfoDtos, apiServices), totalApiCount, portalNotices, hashTags, serviceCount, approvedOrgCount, selectedApiCount);
addAttributesToModel(model, services, hashTags, serviceCount, approvedOrgCount, selectedApiCount);
return "apps/main/index";
}
private List<ApiServiceDTO> getSortedApiServices(String apiId) {
List<ApiServiceDTO> apiServices = mainApiFacade.getOpenApiServices(apiId);
apiServices.sort(Comparator.comparing(ApiServiceDTO::getDisplayOrder));
return apiServices;
}
private Map<String, ApiServiceDTO> createMainIconsMap(List<ApiServiceDTO> apiServices) {
Map<String, ApiServiceDTO> mainIconsMap = new HashMap<>();
apiServices.forEach(service ->
service.getApiGroupApiList().forEach(api ->
mainIconsMap.put(api.getApiId(), service)
)
);
return mainIconsMap;
}
private List<ApiServiceApiDTO> getAllApis(List<ApiServiceDTO> apiServices) {
return apiServices.stream()
.flatMap(service -> service.getApiGroupApiList().stream())
.collect(Collectors.toList());
}
private List<ApiSpecInfoDto> enrichApiSpecInfo(List<ApiServiceDTO> services, List<ApiServiceApiDTO> allApis, Map<String, ApiServiceDTO> mainIconsMap) {
List<String> apiIds = allApis.stream().map(ApiServiceApiDTO::getApiId).collect(Collectors.toList());
List<ApiSpecInfoDto> apiSpecInfoDtos = mainApiFacade.getOpenApis(apiIds);
apiSpecInfoDtos.forEach(spec -> {
ApiServiceDTO serviceDTO = mainIconsMap.get(spec.getApiId());
if (serviceDTO != null) {
spec.setMainIcon(serviceDTO.getMainIcon());
spec.setService(serviceDTO.getGroupName());
}
});
boolean isAuthenticated = SecurityUtil.isAuthenticated();
String roleCode;
String org;
if (isAuthenticated) {
roleCode = SecurityUtil.getPortalAuthenticatedUser().getRoleCode().toString();
org = SecurityUtil.getUserOrg().getId();
} else {
roleCode = null;
org = null;
}
List<ApiSpecInfoDto> filteredApiSpecInfoDtos = apiSpecInfoDtos.stream()
.filter(spec -> {
if (!isAuthenticated) {
return spec.getDisplayOrg() == null && spec.getDisplayRoleCode() == null;
}
boolean orgMatch = false;
if (org != null) {
orgMatch = spec.getDisplayOrg() == null || spec.getDisplayOrg().isEmpty() || spec.getDisplayOrg().contains(org);
}
boolean roleMatch = spec.getDisplayRoleCode() == null || spec.getDisplayRoleCode().isEmpty() || spec.getDisplayRoleCode().contains(roleCode);
return orgMatch || roleMatch;
})
.collect(Collectors.toList());
Set<String> filteredApiIds = filteredApiSpecInfoDtos.stream()
.map(ApiSpecInfoDto::getApiId)
.collect(Collectors.toSet());
services.forEach(service -> {
if (service.getApiGroupApiList() != null) {
List<ApiServiceApiDTO> filteredApiList = service.getApiGroupApiList().stream()
.filter(api -> filteredApiIds.contains(api.getApiId()))
.collect(Collectors.toList());
service.setApiGroupApiList(filteredApiList);
}
});
return filteredApiSpecInfoDtos.stream().collect(Collectors.toList());
}
private int calculateTotalApiCount(List<ApiServiceDTO> apiServices) {
return apiServices.stream()
.mapToInt(service -> service.getApiGroupApiList().size())
.sum();
}
private void addAttributesToModel(Model model, List<ApiServiceDTO> apiServices,
List<ApiSpecInfoDto> apiSpecInfoDtos, int totalApiCount,
List<PortalNoticeDTO> portalNotices, List<String> hashTags, int serviceCount, int approvedOrgCount, int selectedApiCount) {
private void addAttributesToModel(Model model, List<ApiServiceDTO> apiServices, List<String> hashTags, int serviceCount, int approvedOrgCount, int selectedApiCount) {
model.addAttribute("services", apiServices);
model.addAttribute("apis", apiSpecInfoDtos);
model.addAttribute("totalApiCount", totalApiCount);
model.addAttribute("portalNotices", portalNotices);
model.addAttribute("hashtags", hashTags);
model.addAttribute("serviceCount", serviceCount);
@@ -172,15 +69,4 @@ public class IndexController {
model.addAttribute("selectedApiCount", selectedApiCount);
}
@GetMapping("/api_fragments")
public String getApiFragments(@RequestParam(value = "id", required = false) String id, Model model) {
List<ApiServiceDTO> apiServices = getSortedApiServices(id);
Map<String, ApiServiceDTO> mainIconsMap = createMainIconsMap(apiServices);
List<ApiServiceApiDTO> allApis = getAllApis(apiServices);
List<ApiSpecInfoDto> apiSpecInfoDtos = enrichApiSpecInfo(apiServices, allApis, mainIconsMap);
model.addAttribute("apis", apiSpecInfoDtos);
return "apps/main/apiListFragment :: apiListFragment";
}
}
@@ -7,17 +7,13 @@ import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO;
import com.eactive.apim.portal.apps.apiservice.service.ApiServiceService;
import com.eactive.apim.portal.apps.user.service.PortalOrgService;
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
@Transactional
@Service
@@ -34,17 +30,13 @@ public class MainApiFacade {
return portalOrgService.getActiveApprovedOrgCount();
}
public List<ApiServiceDTO> getOpenApiServices(String serviceId) {
public List<ApiServiceDTO> getOpenApiServices() {
ApiGroupSearch search = new ApiGroupSearch();
Map<String, String> property = portalPropertyService.getPortalPropertiesAsMap(PORTAL_PROPERTY);
String services = property.getOrDefault("main.service.list", "");
if (StringUtils.hasText(serviceId) && services.contains(serviceId)) {
search.setGroupIds(Collections.singletonList(serviceId));
} else {
search.setGroupIds(Arrays.asList(services.split(",")));
}
search.setGroupIds(Arrays.asList(services.replace(" ", "").split(",")));
return apiServiceService.searchApiGroups(search);
}
@@ -1,146 +0,0 @@
package com.eactive.apim.portal.apps.proxy;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
@RestController
@RequestMapping("/_proxy") // /proxy 대신 /_proxy 사용
@Slf4j
public class ApimProxyController {
private final RestTemplate restTemplate;
private final ProxyProperties proxyProperties;
private final ObjectMapper objectMapper;
private final MediaType JSON_UTF8 = new MediaType(MediaType.APPLICATION_JSON, StandardCharsets.UTF_8);
public ApimProxyController(
RestTemplateBuilder restTemplateBuilder,
ProxyProperties proxyProperties,
ObjectMapper objectMapper) {
this.proxyProperties = proxyProperties;
this.objectMapper = objectMapper;
this.restTemplate = restTemplateBuilder
.setConnectTimeout(Duration.ofMillis(proxyProperties.getTimeout().getConnect()))
.setReadTimeout(Duration.ofMillis(proxyProperties.getTimeout().getRead()))
.build();
}
private ResponseEntity<String> createJsonResponse(HttpStatus status, String message) {
ObjectNode jsonResponse = objectMapper.createObjectNode()
.put("status", status.value())
.put("message", message);
return ResponseEntity
.status(status)
.contentType(JSON_UTF8)
.body(jsonResponse.toString());
}
@RequestMapping(value = "/**")
public ResponseEntity<String> proxyRequest(
HttpServletRequest request,
@RequestBody(required = false) String body,
@RequestHeader HttpHeaders headers) {
String targetKey = headers.getFirst("target_host");
if (targetKey == null || targetKey.trim().isEmpty()) {
log.error("Missing or empty target_host header");
return createJsonResponse(HttpStatus.BAD_REQUEST, "Error: target_host header is required");
}
String targetUrl = proxyProperties.getTargets().get(targetKey.trim().toLowerCase());
if (targetUrl == null) {
log.error("Invalid target_host: {}. Available targets: {}",
targetKey, proxyProperties.getTargets().keySet());
return createJsonResponse(HttpStatus.BAD_REQUEST,
"Error: Invalid target_host. Must be one of: " +
String.join(", ", proxyProperties.getTargets().keySet()));
}
try {
String requestUrl = request.getRequestURI().replace("/_proxy", "");
URI uri = UriComponentsBuilder
.fromHttpUrl(targetUrl + requestUrl)
.query(request.getQueryString())
.build()
.toUri();
HttpMethod method = HttpMethod.valueOf(request.getMethod());
HttpHeaders proxyHeaders = new HttpHeaders();
headers.forEach((key, value) -> {
if (!key.equalsIgnoreCase("host") &&
!key.equalsIgnoreCase("target_host")) {
proxyHeaders.addAll(key, value);
}
});
HttpEntity<String> httpEntity = new HttpEntity<>(body, proxyHeaders);
log.debug("Forwarding request to: {} ({})", targetKey, uri);
ResponseEntity<String> response = restTemplate.exchange(
uri,
method,
httpEntity,
String.class
);
log.debug("Received response status: {}", response.getStatusCode());
// Create JSON response with the original response data
ObjectNode jsonResponse = objectMapper.createObjectNode()
.put("status", response.getStatusCode().value());
// Try to parse the response body as JSON, if it fails, include it as a string
try {
jsonResponse.set("data", objectMapper.readTree(response.getBody()));
} catch (Exception e) {
jsonResponse.put("data", response.getBody());
}
return ResponseEntity
.status(response.getStatusCode())
.contentType(JSON_UTF8)
.body(jsonResponse.toString());
} catch (HttpStatusCodeException e) {
log.error("Target server returned error: {} for host: {}", e.getStatusCode(), targetKey);
ObjectNode jsonResponse = objectMapper.createObjectNode()
.put("status", e.getStatusCode().value());
// Try to parse the error response body as JSON, if it fails, include it as a string
try {
jsonResponse.set("data", objectMapper.readTree(e.getResponseBodyAsString()));
} catch (Exception ex) {
jsonResponse.put("data", e.getResponseBodyAsString());
}
return ResponseEntity
.status(e.getStatusCode())
.contentType(JSON_UTF8)
.body(jsonResponse.toString());
} catch (Exception e) {
log.error("Error during proxy request to: {}", targetKey, e);
return createJsonResponse(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage());
}
}
}
@@ -1,23 +0,0 @@
package com.eactive.apim.portal.apps.proxy;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "proxy")
@Getter
@Setter
public class ProxyProperties {
private Map<String, String> targets;
private Timeout timeout = new Timeout();
@Getter
@Setter
public static class Timeout {
private int connect = 5000;
private int read = 5000;
}
}
@@ -49,9 +49,11 @@ public final class SecurityUtil {
public static PortalOrgDTO getUserOrg() {
PortalOrgDTO portalOrgDTO = new PortalOrgDTO();
if (Objects.requireNonNull(getPortalAuthenticatedUser()).getPortalOrg() != null) {
portalOrgDTO.setOrgName(getPortalAuthenticatedUser().getPortalOrg().getOrgName());
portalOrgDTO.setId(getPortalAuthenticatedUser().getPortalOrg().getId());
PortalAuthenticatedUser portalAuthenticatedUser = getPortalAuthenticatedUser();
if (Objects.requireNonNull(portalAuthenticatedUser).getPortalOrg() != null) {
portalOrgDTO.setOrgName(portalAuthenticatedUser.getPortalOrg().getOrgName());
portalOrgDTO.setId(portalAuthenticatedUser.getPortalOrg().getId());
portalOrgDTO.setOrgCode(portalAuthenticatedUser.getPortalOrg().getOrgCode());
}
return portalOrgDTO;
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 202 KiB

After

Width:  |  Height:  |  Size: 149 KiB

@@ -60,7 +60,7 @@
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
z-index: 1000;
height: 90px;
height: 112px;
transition: all 0.3s ease;
border-bottom: 1px solid #dddddd;
}
@@ -693,9 +693,7 @@
transition: all 0.3s ease;
&:hover {
background: var(--light-bg);
color: var(--primary-blue);
transform: translateY(-2px);
}
}
@@ -788,7 +786,7 @@
// Body Layout Compensation
// ===========================
body {
padding-top: 65px; // Compensate for fixed header
padding-top: 112px; // Compensate for fixed header (112px height)
}
// ===========================
@@ -796,7 +794,7 @@ body {
// ===========================
@media (max-width: 768px) {
.global-header {
height: 60px;
height: 90px;
.container .header-content {
padding: 0 16px;
+1
View File
@@ -49,6 +49,7 @@
@import 'pages/notice';
@import 'pages/inquiry';
@import 'pages/org-register';
@import 'pages/partnership';
@import 'pages/user-management';
@import 'pages/commission';
@import 'pages/terms-agreements';
@@ -6,21 +6,22 @@
.api-market-container {
display: flex;
min-height: 100vh;
background-color: $gray-bg;
background-color: $white;
position: relative;
}
// Sidebar Navigation
.api-market-sidebar {
width: 280px;
background-color: $white;
border-right: 1px solid $border-gray;
width: 283px;
background-color: #FDFDFD;
border-radius: 20px;
padding: $spacing-2xl $spacing-lg;
position: sticky;
top: 0;
height: 100vh;
top: $spacing-lg;
height: calc(100vh - 32px);
overflow-y: auto;
flex-shrink: 0;
margin: $spacing-md;
@media (max-width: $breakpoint-md) {
width: 240px;
@@ -35,6 +36,8 @@
box-shadow: $shadow-lg;
z-index: $z-index-modal;
height: calc(100vh - 60px); // Full height minus header
margin: 0;
border-radius: 0;
&.mobile-open {
transform: translateX(0);
@@ -42,30 +45,59 @@
}
}
.api-sidebar-header {
display: flex;
justify-content: center;
margin-bottom: $spacing-xl;
padding-bottom: $spacing-lg;
img {
max-width: 100%;
height: auto;
}
}
.api-sidebar-nav {
.menu-section {
margin-bottom: $spacing-xs;
margin-bottom: 0;
border-bottom: 1px solid #DDDDDD;
&:last-child {
border-bottom: none;
}
}
.menu-title {
padding: 12px $spacing-md;
padding: 16px 10px;
font-size: $font-size-sm;
font-weight: $font-weight-semibold;
color: $secondary-blue;
color: $text-dark;
cursor: pointer;
border-radius: $border-radius-md;
transition: $transition-base;
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
&:hover {
background-color: $gray-bg;
background-color: rgba($primary-blue, 0.05);
}
&.active {
background-color: $light-bg;
color: $secondary-blue;
&::after {
content: '';
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
width: 0;
height: 0;
border-style: solid;
border-width: 6px 0 6px 8px;
border-color: transparent transparent transparent $text-dark;
}
}
.api-count {
@@ -303,9 +335,15 @@
// Card Grid
.api-card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
grid-template-columns: repeat(3, 1fr); // 3 cards per row on desktop
gap: $spacing-lg;
// Tablet view - 2 cards per row
@media (max-width: $breakpoint-md) {
grid-template-columns: repeat(2, 1fr);
}
// Mobile view - 1 card per row
@media (max-width: $breakpoint-sm) {
grid-template-columns: 1fr;
}
@@ -317,119 +355,80 @@
border-radius: $border-radius-lg;
padding: $spacing-xl;
box-shadow: $shadow-sm;
transition: $transition-base;
cursor: pointer;
display: flex;
flex-direction: column;
justify-content: space-between;
min-height: 220px;
gap: $spacing-md;
min-height: 180px;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: $gradient-primary;
transform: scaleX(0);
transform-origin: left;
transition: transform 0.3s ease;
}
&:hover {
box-shadow: $shadow-lg;
transform: translateY(-5px);
&::before {
transform: scaleX(1);
}
.api-card-icon {
transform: scale(1.1);
}
}
border: 1px solid $border-gray;
&:active {
transform: translateY(-3px);
transform: scale(0.98);
}
}
.api-card-header {
// Group Info Section (Icon + Name)
.api-card-group {
display: flex;
align-items: center;
gap: $spacing-sm;
margin-bottom: $spacing-md;
}
.api-card-badge {
display: inline-flex;
.api-card-group-icon {
width: 32px;
height: 32px;
display: flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
background-color: $gray-bg;
border-radius: $border-radius-sm;
font-size: $font-size-xs;
color: $text-gray;
font-weight: $font-weight-medium;
justify-content: center;
background: $gray-bg;
border-radius: $border-radius-md;
flex-shrink: 0;
&::before {
content: "";
img {
width: 20px;
height: 20px;
object-fit: contain;
}
i {
font-size: 16px;
color: $primary-blue;
font-size: 10px;
}
}
.api-card-title {
font-size: $font-size-md;
.api-card-group-name {
font-size: $font-size-sm;
color: $text-gray;
font-weight: $font-weight-medium;
}
// API Name (Title)
.api-card-name {
font-size: $font-size-lg;
font-weight: $font-weight-semibold;
color: $text-dark;
margin-bottom: 12px;
line-height: $line-height-tight;
margin: 0;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-align: left;
}
// API Description
.api-card-description {
font-size: $font-size-sm;
color: $text-gray;
line-height: $line-height-normal;
margin: 0;
flex-grow: 1;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.api-card-icon {
text-align: center;
margin-top: $spacing-lg;
font-size: 48px;
opacity: 0.8;
transition: transform 0.3s ease;
img {
max-width: 60px;
max-height: 60px;
object-fit: contain;
}
}
// Icon color variations
.api-card:nth-child(3n+1) .api-card-icon {
color: #60a5fa;
}
.api-card:nth-child(3n+2) .api-card-icon {
color: #34d399;
}
.api-card:nth-child(3n+3) .api-card-icon {
color: #818cf8;
text-align: left;
}
// Empty State
@@ -777,4 +776,4 @@
color: $primary-blue;
}
}
}
}
+257 -142
View File
@@ -11,7 +11,7 @@
.hero-carousel-container {
position: relative;
width: 100%;
height: 800px;
height: 720px;
overflow: hidden;
@include respond-to('md') {
@@ -369,8 +369,19 @@
.api-search-section {
position: relative;
padding: 0;
margin-bottom: 80px;
margin-top: -80px;
//margin-top: -80px;
overflow: hidden;
background: #e9f9ff;
.search-background {
position: absolute;
inset: 0;
background-image: url('/img/bg_main_intersect.png');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
pointer-events: none;
}
.search-content-wrapper {
position: relative;
@@ -378,7 +389,6 @@
align-items: center;
justify-content: center;
gap: 40px;
padding-top: 60px;
z-index: 1;
@include respond-to('md') {
@@ -527,12 +537,13 @@
align-items: center;
justify-content: center;
gap: 12px;
padding: 0 20px 40px;
padding: 0 20px 0px;
z-index: 1;
@include respond-to('md') {
flex-direction: column;
align-items: flex-start;
align-items: center;
justify-content: center;
gap: 8px;
padding: 0 20px 30px;
}
@@ -553,6 +564,7 @@
flex-wrap: wrap;
@include respond-to('md') {
justify-content: center;
gap: 10px;
}
}
@@ -597,8 +609,19 @@
position: relative;
display: flex;
align-items: center; // 수직 중앙 정렬
padding: 80px 0 100px;
padding: 175px 0 100px;
background: $white;
overflow: hidden;
.showcase-background {
position: absolute;
inset: 0;
background-image: url('/img/bg_main_recommend_apis.png');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
pointer-events: none;
}
.showcase-wrapper {
max-width: 1228px;
@@ -606,15 +629,17 @@
padding: 0 20px;
position: relative;
width: 100%;
z-index: 1;
}
.section-header {
text-align: center;
margin-bottom: 140px;
margin-bottom: 24px;
position: relative;
padding-right: 150px; // 버튼 공간 확보
@include respond-to('md') {
margin-bottom: 80px;
padding-right: 0;
}
}
@@ -624,7 +649,7 @@
font-weight: 700;
line-height: 1.3;
color: #000000;
margin: 0 0 16px 0;
margin: 0;
@include respond-to('md') {
font-size: 36px;
@@ -641,7 +666,7 @@
font-weight: 400;
line-height: 1.5;
color: #1E1E1E;
margin: 0;
margin-bottom: 30px;
@include respond-to('md') {
font-size: 20px;
@@ -655,34 +680,43 @@
.btn-all-apis {
position: absolute;
top: 50%;
right: 0;
top: 64px;
transform: translateY(-50%);
display: inline-flex;
align-items: center;
gap: 8px;
padding: 15px 20px;
justify-content: center;
gap: 21px;
padding: 12px 24px;
font-family: 'Noto Sans KR', sans-serif;
font-size: 18px;
font-size: 15px;
font-weight: 500;
color: #000000;
color: #FFFFFF;
background: #008ae2;
text-decoration: none;
border-radius: 6px;
border-radius: 10px;
transition: all 0.3s ease;
height: 40px;
width: 135px;
@include respond-to('md') {
position: static;
margin-top: 24px;
justify-content: center;
transform: none;
margin: 24px auto 0;
}
svg {
width: 7px;
height: 15px;
width: 11px;
height: 17px;
transition: transform 0.3s ease;
path {
stroke: #FFFFFF;
}
}
&:hover {
background: #F5F5F5;
background: darken(#008ae2, 10%);
svg {
transform: translateX(4px);
@@ -708,16 +742,24 @@
.api-card {
width: 283px;
height: 360px;
height: 320px;
background: #FFFFFF;
border: 1px solid #DDDDDD;
border-radius: 30px;
padding: 56px 25px 48px;
border-radius: 20px;
padding: 36px 38px;
display: flex;
flex-direction: column;
align-items: flex-start;
position: relative;
transition: all 0.3s ease;
gap: 0px;
cursor: pointer;
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
border-color: #008ae2;
}
@include respond-to('md') {
width: calc(50% - 10px);
@@ -736,12 +778,12 @@
font-weight: 700;
line-height: 1.67;
color: #2A2A2A;
margin: 0 0 16px 0;
margin: 0 0 20px 0;
}
.card-description {
font-family: 'Noto Sans KR', sans-serif;
font-size: 14px;
font-size: 15px;
font-weight: 400;
line-height: 1.43;
color: #515151;
@@ -751,13 +793,13 @@
}
.card-illustration {
width: 110px;
height: 110px;
width: 90px;
height: 90px;
display: flex;
align-items: center;
justify-content: center;
margin-top: auto;
align-self: center;
align-self: flex-end;
img {
width: 100%;
@@ -783,7 +825,6 @@
min-height: 980px;
display: flex;
align-items: center;
margin-top: 150px;
.info-background {
position: absolute;
@@ -894,6 +935,7 @@
white-space: nowrap;
transition: all 0.3s ease;
min-width: 310px;
height: 128px;
@include respond-to('sm') {
font-size: 20px;
@@ -912,20 +954,20 @@
}
.action-btn-primary {
background: #00acdd;
background: #0049B4;
color: #FFFFFF;
&:hover {
background: darken(#00acdd, 10%);
background: darken(#0049B4, 10%);
}
}
.action-btn-secondary {
background: #d5a654;
background: #00ACDD;
color: #FFFFFF;
&:hover {
background: darken(#d5a654, 10%);
background: darken(#00ACDD, 10%);
}
}
@@ -953,52 +995,70 @@
// Support Center section styles
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// Support Center Section - 비지니스의 시작 (Figma Design)
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// Support Center Section - 비지니스의 시작 (Figma Design)
// -----------------------------------------------------------------------------
.support-center {
position: relative;
padding: $spacing-6xl 0;
background: #f2f6fb;
padding: 150px 0;
background: #eef7fd;
overflow: hidden;
min-height: 980px;
.support-background-pattern {
.support-background {
position: absolute;
right: 320px;
top: 0;
width: 700px;
height: 660px;
background-image: url('/img/bg_main_community.png');
background-size: cover;
background-position: center;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100%;
height: 100%;
max-width: 1920px;
pointer-events: none;
&::before {
content: '';
position: absolute;
top: 17px; // Figma: top-[3234px] relative to page, section starts at ~3217px
left: 46%; // Approximate positioning
width: 693px;
height: 597px;
background-image: url('/img/bg_support_center.png'); // Assuming this image exists or use placeholder
background-size: cover;
background-repeat: no-repeat;
opacity: 0.5; // Adjust based on visual
}
}
.container {
position: relative;
z-index: 1;
max-width: 1280px;
margin: 0 auto;
padding: 0 40px;
}
.section-header {
margin-bottom: $spacing-4xl;
text-align: left;
margin-bottom: 80px;
text-align: center;
.section-title {
font-family: 'Noto Sans KR', sans-serif;
font-size: 44px;
line-height: 70px;
line-height: 1.6;
color: #000000;
margin: 0;
text-align: left;
@include respond-to('md') {
font-size: 36px;
line-height: 60px;
line-height: 1.4;
}
@include respond-to('sm') {
font-size: 28px;
line-height: 50px;
}
.title-regular {
@@ -1014,104 +1074,46 @@
.support-grid {
display: flex;
gap: 32px;
align-items: flex-start;
justify-content: center;
align-items: stretch;
max-width: 1228px; // 598 + 283 + 283 + (32 * 2) = 1228px
margin: 0 auto;
@include respond-to('md') {
flex-direction: column;
align-items: stretch;
align-items: center;
gap: 20px;
}
}
// 공지사항 - 카드
.support-card-large {
flex: 1;
max-width: 598px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 55px 52px 37px;
// 통합 Support Card
.support-card {
background: #FFFFFF;
border-radius: 20px;
text-decoration: none;
transition: all 0.3s ease;
min-height: 377px;
display: flex;
box-sizing: border-box;
position: relative;
overflow: hidden;
height: 377px; // 모든 카드 동일 높이
.card-icon {
width: 104px;
height: 104px;
margin-bottom: 42px;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
h3 {
font-family: 'Noto Sans KR', sans-serif;
font-size: 36px;
font-weight: 700;
color: #212529;
margin: 0 0 10px 0;
text-align: center;
}
p {
font-family: 'Noto Sans KR', sans-serif;
font-size: 20px;
font-weight: 400;
color: #515961;
margin: 0;
text-align: center;
@include respond-to('md') {
width: 100%;
max-width: 598px;
height: auto;
min-height: 260px;
}
&:hover {
transform: translateY(-8px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(0, 73, 180, 0.15);
}
@include respond-to('md') {
max-width: 100%;
}
}
// 오른쪽 작은 카드들 컨테이너
.support-cards-small {
display: flex;
flex-direction: row;
gap: 32px;
@include respond-to('md') {
flex-direction: column;
gap: 20px;
}
@include respond-to('sm') {
flex-direction: column;
}
}
// FAQ, Q&A - 작은 카드
.support-card-small {
width: 283px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 55px 52px 37px;
background: #FAFAFA;
border: 1px solid #DDDDDD;
border-radius: 20px;
text-decoration: none;
transition: all 0.3s ease;
min-height: 377px;
.card-icon {
width: 160px;
height: 160px;
margin-bottom: 30px;
display: flex;
align-items: center;
justify-content: center;
img {
width: 100%;
@@ -1121,33 +1123,146 @@
}
.card-content {
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
h3 {
font-family: 'Noto Sans KR', sans-serif;
font-size: 36px;
font-size: 32px;
font-weight: 700;
color: #000000;
color: #212529;
margin: 0 0 10px 0;
line-height: 1.3;
@include respond-to('md') {
font-size: 28px;
}
}
p {
font-family: 'Noto Sans KR', sans-serif;
font-size: 24px;
font-weight: 500;
font-size: 18px;
font-weight: 400;
color: #515961;
margin: 0;
line-height: 1.4;
@include respond-to('md') {
font-size: 16px;
}
}
}
&:hover {
transform: translateY(-8px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
background: #FFFFFF;
// 공지사항 카드 (598px width)
&--notice {
width: 598px;
flex-shrink: 0;
flex-direction: column;
align-items: center;
padding: 60px 40px;
background: #0049B4;
@include respond-to('md') {
width: 100%;
max-width: 598px;
padding: 50px 40px;
}
.card-icon {
width: 174px;
height: 120px;
margin-bottom: 36px;
}
.card-content {
align-items: center;
text-align: center;
h3 {
color: #FFFFFF;
font-size: 36px;
@include respond-to('md') {
font-size: 32px;
}
}
p {
color: rgba(255, 255, 255, 0.9);
font-size: 20px;
@include respond-to('md') {
font-size: 18px;
}
}
}
&:hover {
background: darken(#0049B4, 5%);
}
}
@include respond-to('md') {
width: 100%;
// FAQ & Q&A Cards (283px width each)
&--faq, &--qna {
width: 283px;
flex-shrink: 0;
flex-direction: column;
align-items: center;
padding: 60px 40px;
@include respond-to('md') {
width: 100%;
max-width: 598px;
padding: 40px;
}
.card-icon {
width: 120px;
height: 120px;
margin-bottom: 36px;
}
.card-content {
align-items: center;
text-align: center;
flex: none;
h3 {
font-size: 36px;
@include respond-to('md') {
font-size: 32px;
}
}
p {
font-size: 20px;
@include respond-to('md') {
font-size: 18px;
}
}
}
}
// FAQ Card Specifics
&--faq {
background: #DAF0FF;
.card-content h3 {
color: #212529;
}
}
// Q&A Card Specifics
&--qna {
background: #D9F6F8;
.card-content h3 {
color: #212529;
}
}
}
}
@@ -13,7 +13,6 @@
display: flex;
align-items: center;
justify-content: center;
margin-bottom: $spacing-4xl;
overflow: hidden;
&::before {
@@ -1,6 +1,6 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_api_layout}">
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
<body>
@@ -1,6 +1,6 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_api_layout}">
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
<body>
@@ -1,6 +1,6 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_api_layout}">
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
<body>
@@ -1,6 +1,6 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_api_layout}">
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
<body>
@@ -1,6 +1,6 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_api_layout}">
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
<body>
@@ -1,6 +1,6 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_api_layout}">
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
<body>
@@ -1,6 +1,6 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_api_layout}">
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
<body>
@@ -1,6 +1,6 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_api_layout}">
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
<body>
@@ -1,6 +1,6 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_api_layout}"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
@@ -1,140 +0,0 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_apikey_layout}">
<body>
<section layout:fragment="contentFragment" class="content">
<div class="content_wrap">
<div class="sub_title3">
<h2 class="title">API키 신청</h2>
</div>
<div class="inner i_cs h_inner5">
<div class="form_type">
<div class="info1 form_top">
<p class="title w_tit">
<span class="dot">API키 이름</span>
</p>
<div class="info_line info_add">
<div class="info_box1 w_inp4">
<input type="text" name="name" style="max-width: 300px;" id="appRequestClientName" class="common_input_type_1 h_inp2" placeholder="API키 이름 입력">
</div>
</div>
</div>
<div class="infor api_dot top" style="margin-top: 10px;">
<ul>
<li>
<p>인증키가 없을 경우, 승인 시, 인증키는 신규로 발급 됩니다.</p>
</li>
</ul>
</div>
</div>
<p class="pop_text l_text m-only" style="margin-top: 10px; font-weight: 700;">API</p>
<div class="board_search sh_box top">
<p class="pop_text l_text pc-only">API</p>
<div class="search_txt ser_api w_inp2">
<input type="text" class="common_input_type_1 h_inp2" id="api_keyword" placeholder="API 검색">
</div>
<div class="btn_wrap search_btn">
<button type="button" class="btn_search" id="api_search">검색</button>
<i class="i_search"></i>
</div>
<p class="pop_text l_text pop_text2 pc-only" style="margin-left:76px;">신청 API</p>
</div>
<div class="tb_conbox top">
<div class="box table_type2 top_type">
<table class="table_col table_min tb_bdtop" id="sourceTable" cellspacing="0" cellpadding="0" width="100%">
<colgroup>
<col width="60px">
<col width="140px">
<col width="230px">
</colgroup>
<thead>
<tr>
<th>
<div class="total_wrap">
<span class="inp_check b_tit">
<input type="checkbox" class="agree_all" id="selectAllSourceTable" name="selectall" data-group="total">
<label for="selectAllSourceTable"></label>
</span>
</div>
</th>
<th>서비스</th>
<th>API</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div class="box pop_arrow m-only" style="text-align:center;">
<a href="#none" class="add_apis"><img th:src="@{/img/icon/Icon_arrow_btn_down.png}" alt="올리기"></a>
<a href="#none" class="remove_apis"><img th:src="@{/img/icon/Icon_arrow_btn_up.png}" alt="내리기"></a>
</div>
<div class="box pop_arrow pc-only" style="text-align:center;">
<p>
<a href="#none" class="add_apis"><img th:src="@{/img/icon/icon_arrow_btn_r.png}" alt="넣기"></a>
</p>
<p>
<a href="#none" class="remove_apis"><img th:src="@{/img/icon/icon_arrow_btn_l.png}" alt="빼기"></a>
</p>
</div>
<div class="box table_type2 top_type">
<table class="table_col table_min tb_bdtop" id="targetTable" cellspacing="0" cellpadding="0" width="100%">
<colgroup>
<col width="60px">
<col width="140px">
<col width="230px">
</colgroup>
<thead>
<tr>
<th>
<div class="total_wrap">
<span class="inp_check b_tit">
<input type="checkbox" class="agree_all" id="selectAllTargetTable" name="selectall2" data-group="total2">
<label for="selectAllTargetTable"></label>
</span>
</div>
</th>
<th>서비스</th>
<th>API</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<p class="pop_text l_text2">신청사유</p>
<div class="pop_textbox">
<textarea name="reason" rows="5" cols="30" class="common_textareaType_1" placeholder="신청사유를 입력해 주세요."></textarea>
</div>
<div class="form_type">
<div class="btn_application btn_top3">
<a th:href="@{/}" class="common_btn_type_1 gray"><span>취소</span></a>
<div class="btn_gap"></div>
<a href="#none" class="common_btn_type_1 request_apis"><span>확인</span></a>
</div>
</div>
</div>
</div>
</section>
<section layout:fragment="pagePopups" class="content pop">
<th:block th:replace="~{fragment/apikey-request :: apiKeyRequestScript}"></th:block>
</section>
</body>
<th:block layout:fragment="contentScript">
<script th:inline="javascript">
document.addEventListener('DOMContentLoaded', function () {
$('.btn_cancel').on('click', function(){
window.location.href = '/apis';
})
});
</script>
</th:block>
</html>
@@ -2,15 +2,25 @@
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/kjbank_base_layout}">
layout:decorate="~{layout/kjbank_title_layout}">
<body>
<th:block layout:fragment="title">
<div class="org-page-title-banner" style="margin-bottom: 40px;">
<h1>API 마켓</h1>
</div>
</th:block>
<th:block layout:fragment="contentFragment">
<section class="api-market-container">
<!-- Sidebar Navigation -->
<aside class="api-market-sidebar" id="apiSidebar">
<!-- Sidebar Header Image -->
<div class="api-sidebar-header">
<img th:src="@{/img/api_sidebar.png}" alt="API">
</div>
<nav class="api-sidebar-nav">
<!-- All APIs -->
<div class="menu-section">
@@ -45,10 +55,6 @@
<!-- Header -->
<div class="api-market-header">
<div class="api-market-title">
<h1>오픈API</h1>
<h2>API 마켓</h2>
</div>
<div class="api-market-search">
<form id="searchForm" th:action="@{/apis}" method="get">
<input type="hidden" name="groupIds" th:value="${search.groupIds != null and !search.groupIds.isEmpty() ? search.groupIds[0] : ''}"/>
@@ -76,33 +82,29 @@
th:data-href="@{/apis/detail(id=${api.apiId})}"
role="button"
tabindex="0">
<div>
<!-- Card Header with Badge -->
<div class="api-card-header">
<span class="api-card-badge" th:if="${api.apiGroupName}" th:text="${api.apiGroupName}">
카테고리
</span>
<!-- Group Info -->
<div class="api-card-group">
<div class="api-card-group-icon">
<img th:if="${api.mainIcon}"
th:src="${api.mainIcon}"
th:alt="${api.apiGroupName}"
onerror="this.style.display='none'; this.parentElement.innerHTML='<i class=\'fas fa-cube\'></i>'">
<i th:if="${#strings.isEmpty(api.mainIcon)}" class="fas fa-cube"></i>
</div>
<!-- Card Title -->
<h3 class="api-card-title" th:text="${api.apiName}">
API 이름
</h3>
<!-- Card Description -->
<p class="api-card-description" th:text="${api.apiSimpleDescription ?: 'API 설명이 없습니다.'}">
API 설명
</p>
<span class="api-card-group-name" th:text="${api.apiGroupName}">
그룹 이름
</span>
</div>
<!-- Card Icon -->
<div class="api-card-icon">
<img th:if="${api.mainIcon}"
th:src="${api.mainIcon}"
th:alt="${api.apiName}"
onerror="this.style.display='none'; this.parentElement.innerHTML='📄'">
<span th:if="${#strings.isEmpty(api.mainIcon)}">📄</span>
</div>
<!-- API Name -->
<h3 class="api-card-name" th:text="${api.apiName}">
API 이름
</h3>
<!-- API Description -->
<p class="api-card-description" th:text="${api.apiSimpleDescription ?: 'API 설명이 없습니다.'}">
API 설명
</p>
</div>
</div>
@@ -1,6 +1,6 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_api_layout}">
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
<body>
<section layout:fragment="contentFragment" class="content api_con">
@@ -379,4 +379,4 @@ grant_type=client_credentials&client_id=your_client_id&client_secret=your_client
</script>
</th:block>
</body>
</html>
</html>
@@ -215,7 +215,7 @@
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': /*[[${_csrf.token}]]*/ ''
'X-XSRF-TOKEN': /*[[${_csrf.token}]]*/ ''
},
body: JSON.stringify({
year: year,
@@ -1,77 +1,111 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_base_layout}">
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
<body>
<section class="content" layout:fragment="contentFragment">
<div class="content_wrap">
<div class="sub_title3">
<h2 class="title">사업 제휴 신청</h2>
</div>
<div class="inner i_cs h_inner6">
<div class="con_title f_tit">
<p>Kbank는 온라인 비즈니스 혁신을 위한 <span>사업 제안을 환영</span>합니다.</p>
<!-- Page Header -->
<div class="page-header">
<div class="page-header-content">
<h1 class="page-title">
<i class="fas fa-handshake"></i>
사업 제휴 신청
</h1>
<p class="page-description">
광주은행은 온라인 비즈니스 혁신을 위한 <span class="highlight">사업 제안을 환영</span>합니다.
</p>
</div>
</div>
<div class="form_type">
<!-- Partnership Form Card -->
<div class="partnership-form-container">
<div class="card partnership-card">
<form name="partnershipForm" id="partnershipForm" th:action="@{/partnership}" th:object="${partnership}"
enctype="multipart/form-data" method="post">
<div class="info1">
<p class="title w_tit">
<span class="dot">사업 제목</span>
</p>
<div class="info_line info_add">
<div class="info_box1 w_inp4">
<input type="text" class="common_input_type_1" id="bizSubject" th:field="*{bizSubject}"
placeholder="제안하고자 하시는 사업제목" required>
</div>
</div>
<div class="invalid-feedback" th:if="${#fields.hasErrors('bizSubject')}"
th:errors="*{bizSubject}"></div>
</div>
<div class="info1">
<p class="title w_tit">
<span class="dot">사업 내용</span>
</p>
<div class="info_line info_add">
<div class="info_box1 qna_text w_inp4">
<textarea class="common_textareaType_1 f_inp" id="bizDetail" th:field="*{bizDetail}"
placeholder="제안하고자 하시는 사업내용" required></textarea>
</div>
</div>
<div class="invalid-feedback" th:if="${#fields.hasErrors('bizDetail')}"
th:errors="*{bizDetail}"></div>
</div>
<div class="info1">
<p class="title tit_add w_tit">첨부파일</p>
<div class="info_line info_add">
<div class="info_box1 info_add2">
<div class="filebox">
<input class="upload-name" value="첨부파일을 등록 해 주세요" disabled="disabled">
</div>
<div class="btn_check">
<div class="file_btn">
<label for="file">파일선택</label>
<input type="file" id="file" name="files" accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.hwp,.gif,.jpg,.png">
</div>
</div>
</div>
<div class="list-dot">
<p>첨부파일은 10MB 이내로 등록해 주세요.</p>
<p>문서파일과 이미지 파일만 등록 가능합니다. (pdf, doc, docx, xls, xlsx, ppt, pptx, hwp, gif, jpg,
png)</p>
</div>
<!-- 사업 제목 -->
<div class="form-group">
<label for="bizSubject">
<i class="fas fa-lightbulb"></i>
사업 제목
<span class="required">*</span>
</label>
<input type="text"
class="form-control"
id="bizSubject"
th:field="*{bizSubject}"
placeholder="제안하고자 하시는 사업 제목을 입력해 주세요"
required>
<div class="form-error" th:if="${#fields.hasErrors('bizSubject')}" th:errors="*{bizSubject}">
<i class="fas fa-exclamation-circle"></i>
<span></span>
</div>
</div>
<div class="btn_application m_top">
<a th:href="@{/}" id="cancelButton" class="common_btn_type_1 gray"><span>취소</span></a>
<div class="btn_gap"></div>
<button type="button" class="common_btn_type_1 btn_register"><span>제휴신청</span></button>
<!-- 사업 내용 -->
<div class="form-group">
<label for="bizDetail">
<i class="fas fa-file-alt"></i>
사업 내용
<span class="required">*</span>
</label>
<textarea class="form-control"
id="bizDetail"
th:field="*{bizDetail}"
rows="8"
placeholder="제안하고자 하시는 사업 내용을 상세히 입력해 주세요"
required></textarea>
<div class="form-hint">
<i class="fas fa-info-circle"></i>
사업의 목적, 내용, 기대 효과 등을 구체적으로 작성해 주세요
</div>
<div class="form-error" th:if="${#fields.hasErrors('bizDetail')}" th:errors="*{bizDetail}">
<i class="fas fa-exclamation-circle"></i>
<span></span>
</div>
</div>
<!-- 첨부파일 -->
<div class="form-group">
<label>
<i class="fas fa-paperclip"></i>
첨부파일
</label>
<div class="file-upload-wrapper">
<input type="text"
class="file-name-display"
value="첨부파일을 등록해 주세요"
readonly>
<label for="file" class="btn btn-primary">
<i class="fas fa-upload"></i>
파일 선택
</label>
<button type="button" class="file-remove-btn" style="display: none;">
<i class="fas fa-times"></i>
</button>
<input type="file"
id="file"
name="files"
accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.hwp,.gif,.jpg,.png">
</div>
<div class="form-help-text">
<p><i class="fas fa-check-circle"></i> 첨부파일은 10MB 이내로 등록해 주세요</p>
<p><i class="fas fa-check-circle"></i> 문서파일과 이미지 파일만 등록 가능합니다 (pdf, doc, docx, xls, xlsx, ppt, pptx, hwp, gif, jpg, png)</p>
</div>
</div>
<!-- 버튼 영역 -->
<div class="form-actions">
<a th:href="@{/}" id="cancelButton" class="action-btn-secondary">
<i class="fas fa-times"></i>
취소
</a>
<button type="button" class="btn btn-primary btn_register">
<i class="fas fa-paper-plane"></i>
제휴 신청
</button>
</div>
</form>
</div>
@@ -97,17 +131,21 @@
</script>
<script th:inline="javascript">
$(function () {
let fnRegister = document.querySelector('.btn_register');
let cancelButton = document.getElementById('cancelButton');
const fnRegister = document.querySelector('.btn_register');
const fileInput = document.getElementById('file');
const fileNameInput = document.querySelector('.file-name-display');
const fileRemoveBtn = document.querySelector('.file-remove-btn');
const allowFileExtention = ['pdf','doc','docx','xls','xlsx','ppt','pptx','hwp','gif','jpg','png'];
// Form submission
fnRegister.addEventListener('click', function (event) {
event.preventDefault();
let form = document.getElementById('partnershipForm');
let bizSubject = document.getElementById('bizSubject').value.trim();
let bizDetail = document.getElementById('bizDetail').value.trim();
const form = document.getElementById('partnershipForm');
const bizSubject = document.getElementById('bizSubject').value.trim();
const bizDetail = document.getElementById('bizDetail').value.trim();
// validation 체크
// Validation
if (!bizSubject) {
customPopups.showAlert('사업 제목을 작성해 주세요.');
document.getElementById('bizSubject').focus();
@@ -123,51 +161,54 @@
form.submit();
});
document.getElementById('partnershipForm').bizSubject.focus();
const fileInput = document.getElementById('file');
const fileNameInput = document.querySelector('.upload-name');
const allowFileExtention = ['pdf','doc','docx','xls','xlsx','ppt','pptx','hwp','gif','jpg','png'];
// Auto focus on first field
document.getElementById('bizSubject').focus();
// File validation functions
function isValidFileExtention(filename) {
const extention = filename.split('.').pop().toLowerCase();
return allowFileExtention.includes(extention);
}
function isValidFileSize(filesize) {
const maxSize = 10 * 1024 * 1024;
const maxSize = 10 * 1024 * 1024; // 10MB
return filesize <= maxSize;
}
// File input change event
fileInput.addEventListener('change', function () {
const file = fileInput.files[0];
if (file) {
if(!isValidFileExtention(file.name)) {
const errorMsg = '허용된 파일 형식이 아닙니다. <br>문서파일과 이미지 파일만 등록 가능합니다. <br> (pdf, doc, docx, xls, xlsx, ppt, pptx, hwp, gif, jpg, png)';
const formattedMsg = errorMsg.replace(/\n/g,'<br>');
$('#customAlertMessage').html(formattedMsg);
// Validate file extension
if (!isValidFileExtention(file.name)) {
const errorMsg = '허용된 파일 형식이 아닙니다.<br>문서파일과 이미지 파일만 등록 가능합니다.<br>(pdf, doc, docx, xls, xlsx, ppt, pptx, hwp, gif, jpg, png)';
$('#customAlertMessage').html(errorMsg);
$('#customAlert').css('display','flex');
fileInput.value = '';
return;
}
if(!isValidFileSize(file.size)) {
customPopups.showAlert('첨부파일은 10MB 이내로 등록해 주세요.')
// Validate file size
if (!isValidFileSize(file.size)) {
customPopups.showAlert('첨부파일은 10MB 이내로 등록해 주세요.');
fileInput.value = '';
return;
}
// Update UI
fileNameInput.value = file.name;
fileNameInput.classList.remove('before');
fileNameInput.classList.add('i_trash', 'after');
fileNameInput.classList.add('has-file');
fileRemoveBtn.style.display = 'inline-flex';
}
});
fileNameInput.addEventListener('click', function () {
if (fileNameInput.classList.contains('i_trash')) {
fileInput.value = '';
fileNameInput.value = '';
fileNameInput.classList.remove('i_trash', 'after');
fileNameInput.classList.add('before');
}
// File remove button click event
fileRemoveBtn.addEventListener('click', function () {
fileInput.value = '';
fileNameInput.value = '첨부파일을 등록해 주세요';
fileNameInput.classList.remove('has-file');
fileRemoveBtn.style.display = 'none';
});
});
</script>
@@ -95,6 +95,7 @@
<!-- API 검색 섹션 -->
<section class="api-search-section">
<div class="search-background"></div>
<div class="search-content-wrapper">
<div class="search-character">
<img th:src="@{/img/img_search_character.png}" alt="검색">
@@ -132,60 +133,36 @@
<!-- 추천 API 섹션 -->
<section class="api-showcase">
<div class="showcase-background"></div>
<div class="showcase-wrapper">
<div class="section-header">
<h2 class="section-title">추천 API</h2>
<p class="section-subtitle">다양한 금융 API상품을 소개합니다.</p>
<a th:href="@{/apis}" class="btn-all-apis">
API 전체보기
<svg width="7" height="15" viewBox="0 0 7 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 1L6 7.5L1 14" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
전체보기
<svg width="9" height="15" viewBox="0 0 9 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.509766 13.5496L7.50977 7.04956L0.509766 0.549561" stroke="white" stroke-width="1.5" stroke-linejoin="round"/>
</svg>
</a>
</div>
<div class="api-cards-container">
<!-- 1원 이체 인증 -->
<div class="api-card">
<h3 class="card-title">1원 이체 인증</h3>
<!-- Services 데이터를 반복하여 API 카드 표시 (최대 4개) -->
<div class="api-card" th:each="service, iterStat : ${services}"
th:if="${iterStat.index < 4}"
th:onclick="${service.id != null} ? |location.href='@{/apis(groupId=${service.id})}'| : |location.href='@{/apis}'|">
<h3 class="card-title" th:text="${service.groupName}">API 서비스</h3>
<p class="card-description">
고객이 기존에 가지고 있는 계좌로 이체를 통해서 간편하게 비대면 실명확인을 도와드리는 서비스입니다.
<span th:if="${service.groupDesc != null and !#strings.isEmpty(service.groupDesc)}"
th:text="${service.groupDesc}">서비스 설명</span>
<span th:unless="${service.groupDesc != null and !#strings.isEmpty(service.groupDesc)}">
광주은행 API 서비스를 이용해보세요.
</span>
</p>
<div class="card-illustration">
<img th:src="@{/img/api_icon_1won.png}" alt="1원 이체 인증">
</div>
</div>
<!-- 수신 -->
<div class="api-card">
<h3 class="card-title">수신</h3>
<p class="card-description">
광주은행 수신 계좌들을 조회하는 서비스입니다.
</p>
<div class="card-illustration">
<img th:src="@{/img/api_icon_deposit.png}" alt="수신">
</div>
</div>
<!-- 여신 -->
<div class="api-card">
<h3 class="card-title">여신</h3>
<p class="card-description">
대출관련 한도, 금리와 대출 계좌들을 조회하는 서비스입니다.
</p>
<div class="card-illustration">
<img th:src="@{/img/api_icon_loan.png}" alt="여신">
</div>
</div>
<!-- 간편결제 -->
<div class="api-card">
<h3 class="card-title">간편결제</h3>
<p class="card-description">
광주은행 간편결제 승인/취소와 제휴 대사자료 생성/전송 요청하는 서비스입니다.
</p>
<div class="card-illustration">
<img th:src="@{/img/api_icon_payment.png}" alt="간편결제">
<!-- 서비스별 아이콘 매핑 - base64 인코딩된 이미지 사용 -->
<img th:src="${service.mainIcon != null and !#strings.isEmpty(service.mainIcon)} ? ${service.mainIcon} : @{/img/api_icon_default.png}"
th:alt="${service.groupName}">
</div>
</div>
</div>
@@ -227,7 +204,7 @@
<!-- 지원 센터 섹션 -->
<section class="support-center">
<div class="support-background-pattern"></div>
<div class="support-background"></div>
<div class="container">
<div class="section-header">
<h2 class="section-title">
@@ -236,36 +213,38 @@
</h2>
</div>
<div class="support-grid">
<!-- 공지사항 -카드 -->
<a th:href="@{/portalnotice}" class="support-card support-card-large">
<!-- 공지사항 카드 (50% width) -->
<a th:href="@{/portalnotice}" class="support-card support-card--notice">
<div class="card-icon">
<img th:src="@{/img/icon_notice.png}" alt="공지사항">
</div>
<h3>공지사항</h3>
<p>광주은행 API의 새로운 소식을 확인해보세요.</p>
<div class="card-content">
<h3>공지사항</h3>
<p>광주은행 API의 새로운 소식을 확인해보세요.</p>
</div>
</a>
<!-- 오른쪽 작은 카드들 -->
<div class="support-cards-small">
<a th:href="@{/faq_list}" class="support-card support-card-small">
<div class="card-icon">
<img th:src="@{/img/icon_faq.png}" alt="FAQ">
</div>
<div class="card-content">
<h3>FAQ</h3>
<p>자주 묻는 질문</p>
</div>
</a>
<a th:href="@{/inquiry}" class="support-card support-card-small">
<div class="card-icon">
<img th:src="@{/img/icon_qna.png}" alt="Q&A">
</div>
<div class="card-content">
<h3>Q&A</h3>
<p>문의하기</p>
</div>
</a>
</div>
<!-- FAQ Card (25% width) -->
<a th:href="@{/faq_list}" class="support-card support-card--faq">
<div class="card-icon">
<img th:src="@{/img/icon_faq.png}" alt="FAQ">
</div>
<div class="card-content">
<h3>FAQ</h3>
<p>자주 묻는 질문</p>
</div>
</a>
<!-- Q&A Card (25% width) -->
<a th:href="@{/inquiry}" class="support-card support-card--qna">
<div class="card-icon">
<img th:src="@{/img/icon_qna.png}" alt="Q&A">
</div>
<div class="card-content">
<h3>Q&A</h3>
<p>문의하기</p>
</div>
</a>
</div>
</div>
</section>
@@ -284,13 +263,13 @@
<div class="stat-card">
<p class="stat-label">서비스 이용 수</p>
<div class="stat-icon">
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect width="100" height="100" fill="url(#pattern0_2007_122)"/>
<svg width="90" height="90" viewBox="0 0 90 90" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect width="90" height="90" fill="url(#pattern0_892_75)"/>
<defs>
<pattern id="pattern0_2007_122" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0_2007_122" transform="translate(0 -0.00684932) scale(0.00684932)"/>
<pattern id="pattern0_892_75" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0_892_75" transform="scale(0.00537634)"/>
</pattern>
<image id="image0_2007_122" width="146" height="148" preserveAspectRatio="none" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJIAAACUCAYAAAB4InCTAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MjE2MEY5MTNCMUQwMTFGMEI4MkNBREYwOTFGQThDQ0YiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MjE2MEY5MTRCMUQwMTFGMEI4MkNBREYwOTFGQThDQ0YiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyMTYwRjkxMUIxRDAxMUYwQjgyQ0FERjA5MUZBOENDRiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyMTYwRjkxMkIxRDAxMUYwQjgyQ0FERjA5MUZBOENDRiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PtyO1z0AAAQbSURBVHja7N3BaxxlGMDhmWYT1myQNKlFFBHBg7fSCgUp8RAQLyIVevImnvwD9F/w4FXwJN48CYpIIVRz6KUgaOnNg9CDGMSULUqyW5qk4zt2rGabxOzOZmaSfR54mZKS3fm++ZHdaUOSZlmWQFmnbAFCojnyl7aT/vIW62vHfBizllVnrXjO9kT0c9JDirW1Yr7L6pM/d+ukhzQJL21vxyzX+PzLxTl4j3TMveEchDQOZ52DkBASQoIhtSZ9A7a2tpLNzc1Sj9HpdJLp6WkhTbKdnZ2k3++Xeox2uz3xIXlpQ0gICSGBkBASQkJIICSEhJAQEggJISEkEBJCQkgICYSEkBASQgIhISSEhJBASAgJIYGQGKND/XykLMteisPTFZxP/gO/f07T9FeX5sDr8WwcXoxJK3i63+J6/FQqpDjh83H4LOZcxRv1dRzejQXckc2ufTkTh09j3qz4eW/F4Z24HjeHfmmLT34+DqtVR1TIN2rlJP/E/BEuZr4XK1VHVMgbWC2aGPo90gcx8zXu3YWYKxJ65EqxJ3WZL5oYOqRLDdi8Jf00ai8ujRLSfANOvKOfRu3FvNt/jpSQEBLNMfLtda/XSzY2Nko9+cLCQtJqucMfh+3t7aTb7ZZ6jLm5uWR2drbakPLfGpj/1Pwy/Ibv8an7enhpw3skhISQQEgICSEhJBASQkJICAmEhJAQEggJISEkhARCQkgICSGBkBASQkJIICSEhJBASAgJISEkEBJCQkgICYSEkBASQgIhISSEBEJCSAgJIYGQEBJCQkggJISEkBASCAkhIST4r9aonzg1NZXMzMyUq/hU/R3n52Ad/17TykNqt9t/z3GXb/7i4uKxX0ceQZ3r8NKGkBASExTSegPO748xPEa3AevoNmQvylofJaTVBpz4t2N4jGsNWMe1huxFWfs2kWZZ9vAPabrrL+LjT8Xhh5jnatz81+O8sjIPEut4Ig43Ys7VtI5bMa/EOvol15FfoJWY12paxy8xL8c61gfO6+CvSMUnLMV8E/OgwhPON/zjmMtlIyrWkT/ecsznMfcrXMf94jmXy0ZUrCPfi8vF3vQrXMeDooGlwYgO9RVpoLon47BQ0YmvxbkcyQWPdczG4WxF6/g91tE7onXk//L4TFXv72Idfx5wLocPCf4vJLf/HPntPwgJISEkhARCQkgICYTEWD32PdtZlp2Ow6sxp20Pe7gbcz1N07v7hhQRvReHj2I69osDbEYr70dMn/zzgUf/aZs8/BaFL+0RQ3gr5qvBkH6MOW9vGMLNmAuDIe14882Q8m96mxq8a7tnXxjSvb1u/6/aF4Z0da832y/EfB9zxv5wCHdiLsbc3vUVKW7lbhd/8UVMzz6xj17RyMWimce+IsHI3KUhJJrjLwEGAL+X+MkwwRFJAAAAAElFTkSuQmCC"/>
<image id="image0_892_75" width="186" height="186" preserveAspectRatio="none" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALoAAAC6CAYAAAAZDlfxAAAACXBIWXMAABcSAAAXEgFnn9JSAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAADHdJREFUeNrsXYuR2kgQha0LgAysDJYMVhmYi2CVwXIRmIsAbwRiI2AdAdoIxEYgHIG4CDgNHnx4Dcf0fHtmXlepcLlYkN48vXndPSPGI8TVOBwOpfzn6fV+OCYf/o8aO3mc/v19OPbDsRWv4/F4C+TtxxgQHAk9HV7EUQzHg3wtAp7SVt4E7/Lf2+EG2GGkQHQdpS4lqadnKs05hOo3w/Emid+AviD6JcUWxP5sYDk4E/8Vip8p0Qdyz6RizwLbEF8hiP46HC/w+YkTXSr343BUkdgRkB5EVyZ3IVX7KRPl1klsX4ZjNZB+D6LHmVA+SZIj1GIlVb4B0fkTXNiSL1BvY5V/Hgi/AtF5kVv47blU8Al4atXLP+dga8YRkFwkmBsGBD92Lkf/dTPP/081irOZ6NRlLRjMTntJ+K+pEn7MnOSCAK1nkjeSwN/l685HjVpeqzhE7vFp9KORNQ1A+L9StDTcib6UlsXlwApii6ZLw7EUJ5NuQfgHeRP4uOl3kvCvcHd+Brk92I/1cMylJYoRk6k8/83BfWxixSm2QbUR/XDUolojk9qU8JnI61o7JvwyNexSUvR1iuRWIL0rpe/lsgqEg8FbEAejk39TZI5bIXHoHFk/qLsDlaLEAqj9hqELlYe6OxiomqLoQOx69YaIpUrUUHe70zAlKqB2E0+bhO9QmYGqcye8zWoNBAaqzt7SbGBloOo5Ja02qjQtyG42EFOoupcq18JSVQa+3WAgKFPsBogZiUprgewQG4PplRIlUDPCe4EkNcyU2lMXJQE9Fuq+BJK3gZ4ZJklQdTtCszStyADJ6+DaqPNC1e2KTg+y8wH0Y6ACwMfKgOwWVRzg8h6nOmfwSkfLSk9RgKKsqjI1AHMTUHU3Y1cZjMkCUyBUPbbZWDenqpDUQNVjG09dss8Ayu3VctT6LhYc8RvX9NbGGHq68xVy0zP708MXRk/2LhkRskByAeDcMJntoepsyd6C5GcqfiWphaqnQfZlzBdtukdxofAdUPV0yF7lRvJedVEWHo3BdvxnySenhnaFvCUL2+2S4kGbA8lrze/EJmq+fFgm59cNST43/G6oOl9e6HTBS64XY9IMqix8P1SdL9EnGgv3+NXXJcl6l0knVD3LSsyS293acsiw8WiMJPO3ksvJrzmVkfBoDPZkp5adOw4nPedWK5VLR7GJOi2/vgjtuVguzYSqsyd7qSGORSx3pTdPDFWPguzU+nod4iR1fPnc8zlC1dOzMKXPE9RZw1AHAJKa4ePRGPwtzMbnXUithbYBgexYT40InSpM6eOk1lEkEfqqXoB6QSxMz6bcqDHNsEjyoOpRkH3OpqgRVe0Tqh4j2bvgqq7xoKE2YhCh6mHGiFrkmNk+AZ0FW1NmIFJvVGy3CzNO4UrCGlnxgiGA2EQdB9HDNPo01ni3jEHEJur0VL229aV1bFUWqHp2ql6YfiFVzesIQISqx0H21psgEdU8ClLg0RjREL3yUmrUUPNFRCBiu10c49Q5LzVSyRDTFI9N1NGME8Vmrn1M71WEIELV07OZE5d3UhcpiFD19ASpon54lwMBoOpRjNHMiX0hfnDU5Tc8GiPJpPQ3Pt5d+dxHwjk8j8fjfawADue+HV4awp88gnZB4pXw3pnKnUM1/0UCaoFN1GnNvGuVD6QU6euEgMQm6nTsS6/yYesclQ2qHsUYLa2MD9G2dAkCCVVPx74s/y8ZpbRQXxPE8oXw3hLb7YIUDlQLH6Ut2zJNVDWw3Y73+NQmZcbTh/S5N06wiTqp8Sl/sy7yP1UbP6+pAjlMj6vhZUf4ky+gn9doKPby0p2yyN22QNWTs5cbk4pDDzDh1SPx6f2lqkuZu20xqMDMsN3Oa7wrvm9ymm3vzvy5arxlAubXkXopS5B8Dv55iy3hvdNzRZ86+pKYk1JB8mfCnzxB1b2NTaNL9HvFP9rLon0uAVWPX9XvdRQ9J5JD1XnHjiBAZKK/ZQjoV8J7BagVOMgqIT3mn3fEGvA2NzSlqq8oqg4OsktIj4pOIfouU1D/Jry3wHY7L6G8q01UFe9G6vXzUWaJ6Pl174iqjmUB7sekoSo6bAtUPfU4KrpyaTFzBYGqR+zT70bqKxa3wJVUaix8/Aw8fLp9ov8DX0h+NAYqMDziQRAdv5TszquX2ETNw7r8QfjQBrj+yPYH8gosVAkslpTugJyTKFwQHfGrqpeEwSgAWdi4AwR6qo4ZDkTPJV4AQTQxAdH1VX01yndJRGwxBdHNvToC1iX5uAcEUUQDoptFBQiiiDcQXTNkex+7ifiHaCp9BdH14zMgiILkf4rNM2gY6ak5dcscktbrIX4qp1B4325EK+luB4K/ng8afiacTvQKT/GyhuXGB47CujSAW0uFVOMb4LIS302JrhqfgPWPH+Edqa9z2f8yfSIuhZfVs4LoqovXC4zJMSibKVaA62Z42fgjiP5u+YRSD8pmCqyH+f/ZkaLme1Ois5piIhgY1Zltl+tTExyo+RFPb8lo6j8AYDkJfQaPb4ZqrnPanG5EdMoH5O7TK8J7kYTeDtUCh/HMeEe8U7JVdGLLvzFVoExClU/GWN4R75iHjAeF0vJHEmqX6O++iZ6lomu0/GFbbmNaEt6+tUV08m/CZBak2rl8Ai/CnmhaIzrlg8oMBwUtf/uhaoP3VvMdLFK6iktBwKYHf5VxVf2V8rWN7ztvGDUOpvHsbAsorERyYVtUK1jvtomu+rMtk8waR2j5hxWPxvZdVhKm6GUuykPApAN/lXFtfVvBn4pO/AWBXBJStPwd5DyEiktjnegyVOu/00zKjBXhvaid27ct31wR/ZsjEsSoPGj5h58lG1eDW8CT/sSiJmBRgb/Wc56WS6JwSPUh96Llf6AFNqWo4bokYGr15+YvbbyglMgeEx0TtPxTz3mgZqRHMBzwg1zKmFZsbMvZSa1zfd4LWv4sLHHl66RmuQ628IZonFnHlNKM7L26BFFVybHqQLxu/KKffStY+z65RW6lRrT8g6u5f/EgetUkVD1k+QtqfoxNqJOsc1I4whrpQ6Y7rVyreRXqRKmqvoh4UGbslSdtNe9iOlm/GXO42asCja0KR3hMNaafOsJBQcs/bPWq43LSG9aZs/n14cH+4Sp2fGZIYtnNXws3zI2Mlr/dvK7jdgF1iokpWv7BZ/+KIyH61CwMWv7BsORbvdLwXm0Eg4OWvz17SxXCknN1oiNezJL54KDlbwfLNqnqnEa5kW0Ch5Z/EBzj6bcQ16ufLqxgeB1o+ZtjWKUifNcsDNWPtZzuYrT8g/nyTWwXOdO4k2tG54+Wv/98rY9yZtSwMCzq62j5W8GvTdayWLqrgyskWv5eZ8M0cNRYHhCc7Gj5eyd5m8SsqNERC9YwQMvfO8n7pBptsYCAlr/X8U0vkTdIUHqf9gAtf68kX6YKiM7CL293Plr+Xkm+Th2YKVeyo+VPnqF1SZ5G8qkA0uygH0uH54WWv1sbepDWcJITWJUB2de2wULLnzQjd5rj1meZ1xiSvbUJGlr+yuPVg+T+yW6lIoOWv/X8BSR3QPaDVOOJp+/P7ZexpwZ+HCR3QPZOt5NKXHw2y2hM5gZWBSR3SPaDnGInhO9Ey/8yJhvDcQDJb4BcGqoISd3R8v8Nj4UF/NsDli4r+8Legrqvb9W7if5zmrjAdBYw34Dk9EpIe7ATF+0MWv4/MdjYwhnM1R+I2tIg9HJanmiWzOaJ4VpYxrYCW+0kqb1lwhfEqbpIyKLYIvgpH0LSyaiea+Q7ExGLjWVc1vDj7nz7MgDRq0jxKuTs1VnGA1YlsgpBci1/KQaVw9lvc8DDmpJUd/YDK23d3LG161Negz+OQd2HF1HWcp0QbYejGY438e/xeLwLSWx5vQ/DIa7f9Y24Go6/hmveg+gMki1JeF82YyePN3kT7AciNLZnLUlocXySr6VHWLeS4M0o8RjHdLKSGGJ6ffJI+EtxIoZQwHfC3z3I14mHGerWTfz3QPBVLlZ4HONJS0/9ZThQGaDFXir4KrcLH8d88meEnwVWeO5xVPDheE3ZhydLdIaWhlsID/6co4InSfQLSeuj56SOWwhiv+SQZGZL9A+25knamiIT9X4RJM/VnmRJ9A+kF2T/nKCXP9X+n0PW/UF0nqSfnlmbGFflCWJ/k4klyA2iKyexgvAPjIkviC2aVg08N4huk/wnwt9Lb+8rqd1LKyIO0YQSyxC2GBEQ3bfyT0e/djQ/XUhyP3Y8myu++p8zYo+g1O7jXwEGAPdSo6KkqXZPAAAAAElFTkSuQmCC"/>
</defs>
</svg>
</div>
@@ -302,13 +281,13 @@
<div class="stat-card">
<p class="stat-label">API 이용 건수</p>
<div class="stat-icon">
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect width="100" height="100" fill="url(#pattern0_2007_127)"/>
<svg width="90" height="90" viewBox="0 0 90 90" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect width="90" height="90" fill="url(#pattern0_892_81)"/>
<defs>
<pattern id="pattern0_2007_127" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0_2007_127" transform="translate(0 -0.00684932) scale(0.00684932)"/>
<pattern id="pattern0_892_81" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0_892_81" transform="scale(0.00543478)"/>
</pattern>
<image id="image0_2007_127" width="146" height="148" preserveAspectRatio="none" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJIAAACUCAYAAAB4InCTAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDM4MERCNkFCMUQwMTFGMDhFRkQ5RTJDMzIxRTM5RTEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDM4MERCNkJCMUQwMTFGMDhFRkQ5RTJDMzIxRTM5RTEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo0MzgwREI2OEIxRDAxMUYwOEVGRDlFMkMzMjFFMzlFMSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo0MzgwREI2OUIxRDAxMUYwOEVGRDlFMkMzMjFFMzlFMSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PlF4bcgAAAgiSURBVHja7J0LiBVVGMdnrqVppJFJgVpWi/TQiMoUM9s0JMJSKaIySioRrSgq0ITshWmJFSZakKVRaiqBlo/KtIflg4RKy4jNaDEy7OEavja36f91z63htnfv+ebOjPP4/+BjVpnzuGd+d+bMmXPmup7nOYTUikuRCEUiFIlQJEL+j4hkohr3sbVIJX8KbAoSBhSJUCRCkQhFIoQiEYpEKBKhSIRQJEKRCEUiFIkQikQoEqFIhFAkQpEIRSLpwfM8mdf/MmJstR05Z5u0JdFcnwNTKvlDkUhbIs1oxYPnEe0oErGV6Ik2XFiCaE+RSDWJJln4sBbRmSKRShLd5dnzMUUirUl0p0KiZsRVFImUSzQa0WIp0RHEtewjkXKJRigkkv1G866NlEt0tblM2XIHx5FIuURDEAcUEk1oJQ+KlHOJ+iP2KySaVCEfipRjiS5CNCkkeryNvChSTiXqi9ijkOipKvlRpBxKVIfYrZBIHti6FIn4D3gvxC6FRPOrSUSR8idRD0SDQqKF/if8FInIce2G2KGQaEXpyT5FIqWDfBJim0Ki9zQS+UXiVNvsStQFm9WIPpZJPkCMcF23OWiBPCNlT6JOZoqHLZuNeIH9oUjZk6gDYp1Coi+DSsRLW3Ylkv7NUsQVlkm+QgzF5awpjMJ5RsqGRO3MXGpbZDigR1j+UKRsSCTLhl5TSLQrDIkoUvYkmquUqC7E8ilSRkSaqZDoL8SwkMtnZzsDEk3F5n5FEnl29m0UdaFI6ZVIJppNTkp9KFI6JboHm2lJqlMhJQ3XFfEkYhNiK+LFMDuMKZNoHDazklixRHe2Ue45iMZW6iPzjW/JmUSatWeV6BWFP4kWyXJa6HOIY3Ig0agQJMqfSMq5xR8iTsmwRMOVa89iF6mQ0Ibrj80GxMmWSQYjPjPpsibREGyWII5Ncj0LCZXoXURnZVIZ8v+ofCVoyiUagM3biI6WSZZRpP++fUEkKiFPv18yd3XtUy5RP2zeUUj0KGJl7u/aAiwfrsZGRPeUSqRdezbdpBuT6z6SvGdHeQq3QS4LMuY0KGUSyfjY+4r+4Quu607KfR/JvGdnRcgSlZA7ufVmJDgNEp3pFOdOd7NM8gpiQu472/JeHtNBjPKORMaYZpkFfx0TLFEP0z+0vRwvRIzF2cjLtUjmZU1vxnhbe5sMKaDc0xMoUTdzOTvLMslbiDGQqCXXt/9GoldDKv+QYt8LTb9paIIkkr7QOkRvyyRy1roeEv2ZpC9D4Sg03LgQJRImIhoU+3eVg4F6PGiztj3ituhibtlt156tR4wKvPYsKyKh4cbLXYai3CUW+zQi+inHUKT8GYjFqNPxR0miTtisQlximWQLYiQkOpDEPl4hxoaTcag5iiQinNVtLRp3LzZy9/eYslo3IDbFPSXFdPpluGOgZZIvEMPwOfcl+ZYz8gFJyzfJ+5ltJrX3sth3ZPlwgvJtZMLv8lLOmNq7vXlRgy3bTGfcJu8xmX36j3STlQd1ui+tWiSTrjfia2W5MjH+4Sj7TWbt2TLl2rPuivyzKZJIEVSiWkQyaU9QHrQSy+U3NiKQqIB4XVGPRu3as0yKFECiya3kEVgkk14ujw8FmBD2DeLcEBs7lrVnmRLJNNps5YGr9OrdmkTy5TMM8ZuyTn/IrMSQGvtZRbnysPa8gOVkQ6QA37w28w1LJJPXGYjPA1zqptq+Bq9CudMUZclNQt8aykq/SKYjOU95kMZXyTM0kUpjN8p+SonV8vaziO9Wm2qd4Zl6kYxEmgPUYka4nThF8o9pmV/40bATcb6ijHsVecs8rPoQDmp6RTLjIlqJRltWMhKRTN71iJ+VMskSqJttRvAVecqk/itDOqjpFCnA4Jq1RFGLZPLvidgS4FI3s9ISKOXas2YzH8vJrUgBJGo284+cpIhkyugQoG8nrC8fcca/r/N0v3t2U8gHNV1TbX0PHK+xTCJTHmTqw/KkPSJCnQ4jZOXJeFNPW6RPs9VM0i/N9FykeH55O8pd5GSEQkCJ5IGj7Zyeg/JAFY22IskNgfrJQ+LLET8pkvVEyNtj5YUOix37SXoTUN4CJ0MUlBLJ/Jk1jv3LLkWi4Wi0NWloDNRzo1Oc/PaJIlkHpzhLwXYa70SUM9fJGBqRjnOKs/Mus9x/n5FoXZoaBPXdjY2sr5sdQfZTkP/TThZRdLZ/iHNwLa7OtkXn9VBI6+ymx3AsU9HZPk1xJpJJWJvT/iXDZ5iPzaVOcRZmLcxJwtqzRHW2q/ALYlAWJPLJtBWbi53iBP0gzEPc7WScQsgSDUHDb8taI+Ez7ZGzLOIZZVJZezYuKWvP0iDSj4jBWZTIJ1ML4gH8eSPisEWSTxG3JmntWdJFEonq0WA78tBg+JxvOHbLpB/Ji0RhiPS9kajByRc7LfY5kqcGqeXdi98ZiXY5JPcEPSM1UCJSq0jbEQMpEalFpO3mFn8Pm44EFWmtuZxRIlKTSCsh0a9sMhLF7T8hFIlQJEKRCEUihCIRikQoEqFIhFAkQpEIRSIUiRCKRCgSoUiEUCRCkQhFIhSJEIpEKBKhSIQiEUKRCEUiFIlQJEIoEqFIhCIRQpEIRSIUiVAkQigSiR7Nm//rPM+rj7l+p1rs0wf12htzvS6w2eefnzGPl7Mt9hlQ44//HXFdd0P5f7q+D+vxe0UsaIJIJ/4rjfGHIhGKRCgSybBIB53iT7ITohbJf/u/im1EguI/I9UhNiG6sllI4DOS+TnRfoiliP1sLxL0jEQIRSIUiVAkQigSCZG/BRgAQhLk0gXj9U4AAAAASUVORK5CYII="/>
<image id="image0_892_81" width="184" height="184" preserveAspectRatio="none" xlink:href="data:image/png;base64,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"/>
</defs>
</svg>
</div>
@@ -320,13 +299,13 @@
<div class="stat-card">
<p class="stat-label">API 활용 기업</p>
<div class="stat-icon">
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect width="100" height="100" fill="url(#pattern0_2007_161)"/>
<svg width="90" height="90" viewBox="0 0 90 90" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect width="90" height="90" fill="url(#pattern0_892_93)"/>
<defs>
<pattern id="pattern0_2007_161" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0_2007_161" transform="translate(0 -0.00684932) scale(0.00684932)"/>
<pattern id="pattern0_892_93" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0_892_93" transform="scale(0.00540541)"/>
</pattern>
<image id="image0_2007_161" width="146" height="148" preserveAspectRatio="none" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJIAAACUCAYAAAB4InCTAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NkU5Mzk4MDhCMUQwMTFGMDlFMzFEOEYxRTE4RTBGOUIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NkU5Mzk4MDlCMUQwMTFGMDlFMzFEOEYxRTE4RTBGOUIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo2RTkzOTgwNkIxRDAxMUYwOUUzMUQ4RjFFMThFMEY5QiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo2RTkzOTgwN0IxRDAxMUYwOUUzMUQ4RjFFMThFMEY5QiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PvePPbQAAAbISURBVHja7J1biFVV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"/>
<image id="image0_892_93" width="185" height="185" preserveAspectRatio="none" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALkAAAC5CAYAAAB0rZ5cAAAACXBIWXMAABcSAAAXEgFnn9JSAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA55JREFUeNrs3eFt01AUgFEbdYCyQUbICM4ITNAyAhPUmaBigqYTwAYJE5ARvEGzweOZGKlUQraT28QJ50imPzB59PqrZVpil0UnpVTlDw95qwqmoMnbsizLlVEcp+wCv88fnoxjklY59M/GcETk3Rl8bRST1p7Ra2M4PPK1S5TJ2+XIPxrD4ZEnY7gIixz6xhjG+2AEiBxEDiIHkYPI4Qg3I/f/kretsYWY5+3RGKYX+db3amP48YTLFRA5iBxEjshB5CByEDmIHEQOIgeRI3IQOYgcRA4iB5GDyBE5iBxEDiKH/yvylFKd4tQD16wC1/RkDpGDyEHkIHIQOYgckYPIQeQgchA5iBxEDjdGcDHu2v9JaQyDtE8p/C7yy3NvBMPlE0KTP3zKsW9drnCtZnlb59jnIuea3ebtSeRcu3mZxj3/euGx42HXjO0/Ir2N7gScyRE5iBxEDiIHkcMx/Fj/cvj27StjvvXtTI7LFRA5iBxEDiIHkYPIETmIHEQOIgeRg8hB5CByRA4ih0t0lre/pZRmxf6GjBGasiybAWu298WbB625a++WOtWDGnmL57633LU31Cz29xyM0N5yeXcVkRf72xA/BL3WMm/1gP3agxF1W7b2wC8mfPKKvP1c2fP7j3mL+qJadLN1uQIiB5EjchA5iBxEDiIHkYPIQeSIHEQOIgeRg8hB5NDnXO8Maoq4d4A0A/fbBa65nfhx3ZxwrchZ7N7lb5jGqZwXwuZemf1punW5gmtyEDmIHEQOIgeRg8gROYgcRA4iB5GDyEHkIHJEDiIHkYPIQeQgchA5vHGWmwt19xCpgl5u0/f8927NWbF/3HmEJq+5mupBzZ9rHfVa+fOse9ZqZzoLWm6V12uuIvIu8IfA19sM2GcWuGa73mQjD55t3xfMXeQJqxh+RzSXKyByRA4iB5GDyEHkIHIQOYgckYPIQeQgchA5iBxEjshB5HDJzvUez80ZXqvJ2zJozWbix3V5wrWe8/Zj0nNN41TOC2Fzr8z+NN26XME1OYgcRA4iB5GDyEHkiBxEDiIHkYPIQeQgchA5IgeRg8hB5CByEDmIHESOyEHkIHIQOYgcRA4iB5Ejct7f1ghOF3kzYv/GyGKUZbkbOU9fFAfOo4186PNlVvnAiDzW84jZ74zrL18H7rf5/WtKad3z2JWfebs113hmf9Tsnnpm95K32es/8PiPHb8Z8rsfLLM/fHZ1F/Nb6z+B/xJgACa/EiM9CgOoAAAAAElFTkSuQmCC"/>
</defs>
</svg>
</div>
@@ -24,7 +24,7 @@
<div class="org-form-input-wrapper">
<input type="text" name="organizationId" class="org-form-input input-readonly"
th:value="${user.portalOrg.orgCode}"
placeholder="k000000001"
placeholder="XXXXXX-01"
disabled="disabled">
</div>
</div>
@@ -1,32 +0,0 @@
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<aside layout:fragment="apiAside" class="lnb">
<nav th:with="services=${@apiServiceService.searchApiGroupsForLnb()}">
<ul class="lnb_list_type">
<li>
<a href="#none">공통안내</a>
<ul class="lnb_list_nav">
<li> <a th:href="@{/apis/common}">API 개발 공통</a></li>
<li> <a th:href="@{/apis/KAPAP004U3}">가상계좌 응답 코드</a></li>
<li> <a th:href="@{/apis/KAPAP004U4}">가상계좌 배치 설계</a></li>
<li> <a th:href="@{/apis/KAPAP004U5}">가상계좌 VAN사 코드</a></li>
<li> <a th:href="@{/apis/KAPAP004U6}">펌뱅킹 응답코드</a></li>
<li> <a th:href="@{/apis/KAPAP004U7}">펌뱅킹 배치 설계</a></li>
<li> <a th:href="@{/apis/KAPAP004U8}">대출금리 응답코드</a></li>
<li> <a th:href="@{/apis/KAPAP004U9}">케이뱅크 페이 응답코드</a></li>
<li> <a th:href="@{/apis/KAPAP004U10}">케이뱅크 페이 복합과세 예제</a></li>
<li> <a th:href="@{/apis/token-spec}">케이뱅크 OAuth 2.0 토큰 발급</a></li>
</ul>
</li>
<li th:each="apiService : ${services}">
<a href="#none" th:text="${apiService.groupName}">API Group Name</a>
<ul class="lnb_list_nav">
<li th:each="api : ${apiService.apiGroupApiList}">
<a th:href="@{/apis/detail(id=${api.apiId})}" th:text="${api.apiDesc}">API Description</a>
</li>
</ul>
</li>
</ul>
</nav>
</aside>
@@ -1,29 +0,0 @@
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org"
>
<body>
<footer th:fragment="footerFragment" id="footer">
<div class="container">
<div class="policy-site">
<div class="ft_logo">
<a href="#none">
<img th:src="@{/img/logo/img_mlogo2.png}" alt="">
</a>
</div>
<div class="f-info">
<a th:href="@{/agreements/terms(tab='terms')}">이용약관</a><span>/</span><a th:href="@{/agreements/terms(tab='privacy')}" class="bold-link" >개인정보처리방침</a>
</div>
<div class="family-sites">
<h3 id="family-sites-toggle">케이뱅크 관련사이트</h3>
<ul id="family-sites-list" class="collapsed">
<li><a target="_blank" href="//www.kbanknow.com">케이뱅크 홈페이지</a></li>
<li><a target="_blank" href="//biz.kbanknow.com">케이뱅크 기업뱅킹</a></li>
</ul>
</div>
</div>
<p class="f-info2">고객센터 : 1522-1000 / 해외 : +82-2-3778-9111</p>
</div>
</footer>
</body>
</html>
@@ -1,52 +0,0 @@
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head th:fragment="headFragment">
<title th:text="#{title.html}">개발자 포털</title>
<meta content="https://www.eactive.co.kr/" property="og:url"/>
<meta charset="UTF-8"/>
<meta content="max-age=0, public" http-equiv="Cache-Control"/>
<meta content="index, follow" name="robots"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<meta http-equiv="Cache-Control" content="no-cache">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<link rel="stylesheet" type="text/css" th:href="@{/css/style2.css}">
<link rel="stylesheet" type="text/css" th:href="@{/css/common2.css}">
<link rel="stylesheet" type="text/css" th:href="@{/css/slick.css}">
<link rel="stylesheet" type="text/css" th:href="@{/css/daterangepicker.css}">
<link rel="stylesheet" th:href="@{/plugins/codemirror/codemirror.css}" type="text/css"/>
<link rel="stylesheet" th:href="@{/plugins/codemirror/theme/monokai.css}" type="text/css"/>
<link rel="stylesheet" th:href="@{/plugins/summernote/summernote-lite.css}" type="text/css" />
<link rel="stylesheet" th:href="@{/plugins/jquery-ui/jquery-ui.min.css}" />
<link rel="icon" type="image/ico" th:href="@{/favicon_16px.png}">
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<script th:src="@{/plugins/jquery/jquery-3.7.1.min.js}"></script>
<script th:src="@{/js/lodash.js}"></script>
<script th:src="@{/js/common.js}"></script>
<script th:src="@{/js/moment.min.js}"></script>
<script th:src="@{/js/daterangepicker.js}"></script>
<script th:src="@{/plugins/codemirror/codemirror.js}"></script>
<script th:src="@{/plugins/codemirror/mode/clike.js}"></script>
<script th:src="@{/plugins/codemirror/mode/javascript.js}"></script>
<script th:src="@{/plugins/codemirror/addon/display/fullscreen.js}"></script>
<script th:src="@{/plugins/codemirror/addon/display/placeholder.js}"></script>
<script th:src="@{/plugins/summernote/summernote-lite.min.js}"></script>
<script th:src="@{/plugins/summernote/summernote-cleaner.js}"></script>
<script th:src="@{/plugins/jquery-ui/jquery-ui.min.js}"></script>
<script th:src="@{/js/htmx.min.js}"></script>
</head>
</html>
@@ -1,50 +0,0 @@
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="utf-8">
</head>
<body>
<header th:fragment="headerFragment" id="header" class="header">
<div class="container" th:replace="fragment/kbank/header_container :: headerFragment"></div>
<div class="navigation" th:replace="fragment/kbank/header_nav :: headerFragment"></div>
</header>
<!-- header script -->
<th:block th:fragment="headerScript">
<script th:inline="javascript">
document.addEventListener('DOMContentLoaded', function() {
// Login button
const loginBtn = document.querySelector('.btn_loginbt');
if (loginBtn) {
loginBtn.addEventListener('click', function() {
window.location.href = '/login';
});
}
// Sign up button
const signupBtn = document.querySelector('.btn_memberbt');
if (signupBtn) {
signupBtn.addEventListener('click', function() {
window.location.href = '/signup';
});
}
// Logout button
const logoutBtn = document.querySelector('.btn_logout');
if (logoutBtn) {
logoutBtn.addEventListener('click', function() {
window.location.href = '/actionLogout.do';
});
}
});
</script>
</th:block>
</body>
</html>
@@ -1,143 +0,0 @@
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="utf-8">
</head>
<body>
<div th:fragment="headerFragment" class="header-container">
<!-- Unified Header -->
<header id="page-header">
<div class="header-content">
<!-- Logo -->
<h1 class="logo">
<a th:href="@{/}"><span class="blind">kbank</span></a>
</h1>
<!-- Mobile-only Subtitle -->
<div class="sub_title m-only">
<p th:text="${pageName}">API Portal</p>
</div>
<!-- Mobile Home Button -->
<div class="home m-only">
<a th:href="@{/}"><span class="blind">home</span></a>
</div>
<!-- Mobile Menu Toggle -->
<button type="button" class="btn-mobilemenu m-only">
<span>모바일메뉴 열기</span>
</button>
<!-- Unified Navigation -->
<nav class="main-nav m-nav">
<!-- Login/Profile Section (Mobile) -->
<div class="nav-header m-only">
<div class="login_btn_group">
<div class="header_btn_group">
<div class="login_wrap flex_no" sec:authorize="isAnonymous()">
<div class="login_prev">
<a th:href="@{/login}" class="btn btn_loginbt">로그인</a>
<a th:href="@{/signup}" class="btn btn_memberbt">회원가입</a>
</div>
</div>
<div class="login_wrap" sec:authorize="isAuthenticated()">
<div class="profile_type">
<a th:href="@{/mypage}">
<p>[[${#authentication.principal.userName}]]님</p>
<span>즐거운 하루 되세요!</span>
</a>
</div>
<a th:href="@{/actionLogout.do}" class="btn btn_logout">로그아웃</a>
</div>
</div>
</div>
</div>
<!-- Main Menu List -->
<ul class="nav-list">
<li class="nav-item has-submenu">
<a th:href="@{/intro}">안내</a>
<ul class="sub-menu">
<li><a th:href="@{/intro}">API Portal 소개</a></li>
<li><a th:href="@{/guide}">이용 안내</a></li>
</ul>
</li>
<li class="nav-item has-submenu">
<a th:href="@{/apis}">API</a>
<ul class="sub-menu">
<li><a th:href="@{/apis}">API 목록</a></li>
</ul>
</li>
<li class="nav-item has-submenu">
<a th:href="@{/apis/testbed}">테스트 베드</a>
<ul class="sub-menu">
<li><a th:href="@{/apis/testbed}">테스트베드</a></li>
</ul>
</li>
<li class="nav-item has-submenu">
<a href="#">고객지원</a>
<ul class="sub-menu">
<li><a th:href="@{/portalnotice}">공지사항</a></li>
<li><a th:href="@{/faq_list}">FAQ</a></li>
<li><a th:href="@{/inquiry}">Q&amp;A</a></li>
<li><a th:href="@{/partnership}">사업제휴</a></li>
</ul>
</li>
<li class="nav-item has-submenu" sec:authorize="isAuthenticated()">
<a href="#">마이페이지</a>
<ul class="sub-menu">
<li sec:authorize="hasRole('ROLE_DASHBOARD')"><a th:href="@{/dashboard}">대시보드</a></li>
<li sec:authorize="hasRole('ROLE_USER_MANAGER')"><a th:href="@{/users}">이용자 관리</a></li>
<li sec:authorize="hasRole('ROLE_APP')"><a th:href="@{/myapikey}">인증 키 관리</a></li>
<li sec:authorize="hasRole('ROLE_API_KEY_REQUEST_VIEW')"><a th:href="@{/myapikey/api_key_request/history}">인증키 신청 목록</a></li>
<li><a th:href="@{/mypage}">내 정보 관리</a></li>
<li><a th:href="@{/change_password}">비밀번호 변경</a></li>
</ul>
</li>
</ul>
<!-- Mobile Footer Info -->
<div class="info_wrap m-only">
<p class="tit1">
<a th:href="@{/agreements/terms(tab='terms')}">이용약관</a> <span>/</span> <a th:href="@{/agreements/terms(tab='privacy')}">개인정보처리방침</a>
</p>
<p class="tit2">
<span>고객센터 : 1522-1000</span>
<span>해외 : +82-2-3778-9111</span>
</p>
</div>
</nav>
<!-- Login/Profile Section (Desktop) -->
<div id="login_group" class="pc-only">
<ul sec:authorize="isAnonymous()" class="login_type">
<li><a th:href="@{/login}">로그인</a></li>
</ul>
<ul sec:authorize="isAuthenticated()" class="logout_type">
<li class="icon line"><a th:href="@{/mypage}">[[${#authentication.principal.userName}]]님</a></li>
<li><span>|</span></li>
<li><a th:href="@{/actionLogout.do}">로그아웃</a></li>
<li><span>|</span></li>
<li class="mp_arrow">
<a th:href="@{/dashboard}">마이페이지</a>
<div class="mp_type">
<ul class="mp_list">
<li sec:authorize="hasRole('ROLE_DASHBOARD')"><a th:href="@{/dashboard}">대시보드</a></li>
<li sec:authorize="hasRole('ROLE_USER_MANAGER')"><a th:href="@{/users}">이용자 관리</a></li>
<li sec:authorize="hasRole('ROLE_APP')"><a th:href="@{/myapikey}">인증키 관리</a></li>
<li sec:authorize="hasRole('ROLE_API_KEY_REQUEST_VIEW')"><a th:href="@{/myapikey/api_key_request/history}">인증키 신청 목록</a></li>
<li><a th:href="@{/mypage}">내 정보 관리</a></li>
<li><a th:href="@{/change_password}">비밀번호 변경</a></li>
</ul>
</div>
</li>
</ul>
</div>
</div>
</header>
</div>
</body>
</html>
@@ -1,19 +0,0 @@
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="utf-8">
</head>
<body>
<div th:fragment="headerFragment" class="navigation" id="navigation">
<ul class="navigation-area">
<li th:each="crumb : ${breadcrumb}">
<a th:href="${crumb.path}" class="navigation-button" th:text="${crumb.name}">Unknown Page</a>
</li>
</ul>
</div>
</body>
</html>
@@ -1,46 +0,0 @@
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="utf-8">
</head>
<body>
<header th:fragment="headerFragment" id="header" class="header">
<div class="container" th:replace="fragment/kbank/header_container :: headerFragment"></div>
</header>
<!-- header script -->
<th:block th:fragment="headerScript">
<script th:inline="javascript">
document.addEventListener('DOMContentLoaded', function() {
// Login button
const loginBtn = document.querySelector('.btn_loginbt');
if (loginBtn) {
loginBtn.addEventListener('click', function() {
window.location.href = '/login';
});
}
// Sign up button
const signupBtn = document.querySelector('.btn_memberbt');
if (signupBtn) {
signupBtn.addEventListener('click', function() {
window.location.href = '/signup';
});
}
// Logout button
const logoutBtn = document.querySelector('.btn_logout');
if (logoutBtn) {
logoutBtn.addEventListener('click', function() {
window.location.href = '/actionLogout.do';
});
}
});
</script>
</th:block>
</body>
</html>
@@ -1,24 +0,0 @@
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="utf-8">
</head>
<body>
<aside class="sidebar" th:fragment="menuFragment">
</aside>
<!-- menu script -->
<th:block th:fragment="menuScript">
<script th:inline="javascript">
$(function () {
});
</script>
</th:block>
</body>
</html>
@@ -1,74 +0,0 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/kbank_base_layout}">
<body>
<section layout:fragment="contentFragment" class="content">
<div class="content_wrap">
<div class="sub_title2">
<h2 class="title" th:classappend="${isTermsOfUse ? 'add3' : 'add4'}"
th:text="${agreementTitle}">이용약관</h2>
</div>
<div class="inner cs2">
<div class="tab-wrap">
<div class="tabs">
<ul class="tab_nav tab_bt">
<li th:class="${isTermsOfUse ? 'active' : ''}">
<a th:href="@{/agreements/terms(tab='terms')}">이용약관</a>
</li>
<li th:class="${isPrivacyPolicy ? 'active' : ''}">
<a th:href="@{/agreements/terms(tab='privacy')}">개인정보처리방침</a>
</li>
</ul>
<div class="tab active" th:id="${isTermsOfUse ? 'tab1' : 'tab2'}">
<div class="form_type">
<div class="info1 ts_top">
<div class="info_line ts_left">
<div class="info_box1 info_add3 info_top">
<span class="input">
<form id="agreementForm" th:action="@{/agreements/terms}" method="get">
<input type="hidden" name="tab"
th:value="${currentTab}">
<div class="custom_select" id="agreement_select">
<div class="select_selected">
시행일자 : <span th:text="${#temporals.format(selectedAgreement.publishedOn, 'yyyy.MM.dd')}"></span>
</div>
<ul class="common_selecttype select_items select_hide">
<li th:each="agreement : ${agreementsList}"
th:data-id="${#temporals.format(agreement.publishedOn, 'yyyy-MM-dd')}"
th:text="'시행일자 : ' + ${#temporals.format(agreement.publishedOn, 'yyyy.MM.dd')}"
th:class="${#temporals.format(agreement.publishedOn, 'yyyy-MM-dd') == selectedDate ? 'selected' : ''}">
</li>
</ul>
</div>
<input type="hidden" name="publishedOn" id="publishedOnInput">
</form>
</span>
</div>
</div>
</div>
</div>
<div class="terms_contents m_termst" th:utext="${selectedAgreement.contents}"
style="white-space: pre-wrap; word-wrap: break-word;">
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<script layout:fragment="contentScript">
document.addEventListener('DOMContentLoaded', function () {
const selects = document.getElementById('agreement_select');
const form = document.getElementById('agreementForm');
const publishedOnInput = document.getElementById('publishedOnInput');
createCustomSelect(selects, {
onSelect: (item) => {
publishedOnInput.value = item;
form.submit();
}
});
});
</script>
</body>
</html>
@@ -29,6 +29,7 @@
<li><a th:href="@{/portalnotice}">공지사항</a></li>
<li><a th:href="@{/faq_list}">FAQ</a></li>
<li><a th:href="@{/inquiry}">Q&amp;A</a></li>
<li><a th:href="@{/partnership}">사업 제휴 문의</a></li>
</ul>
</li>
</ul>
@@ -1,43 +0,0 @@
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head th:replace="fragment/kbank/head :: headFragment">
<meta charset="utf-8">
</head>
<body>
<div class="loading-overlay" style="display: none;">
<div class="loading-spinner"></div>
</div>
<ul class="skip">
<li>
<a href="#skipCt">본문 바로가기</a>
</li>
<li>
<a href="#gnb">대 메뉴 바로가기</a>
</li>
</ul>
<div id="wrap">
<!-- header -->
<header th:replace="fragment/kbank/header :: headerFragment"></header>
<th:block th:replace="fragment/kbank/header :: headerScript"></th:block>
<aside layout:replace="fragment/kbank/api_lnb :: apiAside"></aside>
<section layout:fragment="contentFragment">
</section>
<th:block layout:fragment="contentScript">
</th:block>
<footer th:replace="fragment/kbank/footer :: footerFragment"></footer>
<script th:src="@{/js/front2.js}"></script>
</div>
<section th:replace="fragment/popup/apiKeyRequestPopup :: apiKeyRequestPopup"></section>
<div th:replace="fragment/popup/customPopups :: customPopups"></div>
<script th:src="@{/js/popup/custom-popups.js}"></script>
</body>
</html>
@@ -1,42 +0,0 @@
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head th:replace="fragment/kbank/head :: headFragment">
<meta charset="utf-8">
</head>
<body>
<div class="loading-overlay" style="display: none;">
<div class="loading-spinner"></div>
</div>
<ul class="skip">
<li>
<a href="#skipCt">본문 바로가기</a>
</li>
<li>
<a href="#gnb">대 메뉴 바로가기</a>
</li>
</ul>
<div id="wrap">
<!-- header -->
<header th:replace="fragment/kbank/header :: headerFragment"></header>
<th:block th:replace="fragment/kbank/header :: headerScript"></th:block>
<section layout:fragment="contentFragment">
</section>
<th:block layout:fragment="contentScript">
</th:block>
<footer th:replace="fragment/kbank/footer :: footerFragment"></footer>
<script th:src="@{/js/front2.js}"></script>
</div>
<section layout:fragment="pagePopups">
</section>
<section th:replace="fragment/popup/customPopups :: customPopups"></section>
<script th:src="@{/js/popup/custom-popups.js}"></script>
</body>
</html>
@@ -1,40 +0,0 @@
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head th:replace="fragment/kbank/head :: headFragment">
<meta charset="utf-8">
</head>
<body>
<div class="loading-overlay" style="display: none;">
<div class="loading-spinner"></div>
</div>
<ul class="skip">
<li>
<a href="#skipCt">본문 바로가기</a>
</li>
<li>
<a href="#gnb">대 메뉴 바로가기</a>
</li>
</ul>
<div id="wrap">
<!-- header -->
<header th:replace="fragment/kbank/header :: headerFragment"></header>
<th:block th:replace="fragment/kbank/header :: headerScript"></th:block>
<section layout:fragment="contentFragment">
</section>
<th:block layout:fragment="contentScript">
</th:block>
<footer th:replace="fragment/kbank/footer :: footerFragment"></footer>
<script th:src="@{/js/daterangepicker.js}"></script>
<script th:src="@{/js/front2.js}"></script>
</div>
<section th:replace="fragment/popup/customPopups :: customPopups"></section>
<section layout:fragment="pagePopups"></section>
<script th:src="@{/js/popup/custom-popups.js}"></script>
</body>
</html>
@@ -1,39 +0,0 @@
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head th:replace="fragment/kbank/head :: headFragment">
<meta charset="utf-8">
</head>
<body>
<ul class="skip">
<li>
<a href="#skipCt">본문 바로가기</a>
</li>
<li>
<a href="#gnb">대 메뉴 바로가기</a>
</li>
</ul>
<div id="wrap">
<!-- header -->
<header th:replace="fragment/kbank/index_header :: headerFragment"></header>
<th:block th:replace="fragment/kbank/index_header :: headerScript"></th:block>
<section layout:fragment="contentFragment">
</section>
<th:block layout:fragment="contentScript">
</th:block>
<footer th:replace="fragment/kbank/footer :: footerFragment"></footer>
<script th:src="@{/js/slick.min.js}"></script>
<script th:src="@{/js/front2.js}"></script>
</div>
<section th:replace="fragment/popup/customPopups :: customPopups"></section>
<script th:src="@{/js/popup/custom-popups.js}"></script>
</body>
</html>
@@ -1,39 +0,0 @@
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head th:replace="fragment/kbank/head :: headFragment">
<meta charset="utf-8">
</head>
<body>
<ul class="skip">
<li>
<a href="#skipCt">본문 바로가기</a>
</li>
<li>
<a href="#gnb">대 메뉴 바로가기</a>
</li>
</ul>
<div id="wrap">
<!-- header -->
<header th:replace="fragment/kbank/header :: headerFragment"></header>
<th:block th:replace="fragment/kbank/header :: headerScript"></th:block>
<section layout:fragment="contentFragment">
</section>
<th:block layout:fragment="contentScript">
</th:block>
<footer th:replace="fragment/kbank/footer :: footerFragment"></footer>
<script th:src="@{/js/daterangepicker.js}"></script>
<script th:src="@{/js/front2.js}"></script>
</div>
<section th:replace="fragment/popup/customPopups :: customPopups"></section>
<section layout:fragment="pagePopups"></section>
<script th:src="@{/js/popup/custom-popups.js}"></script>
<section th:replace="fragment/popup/withdrawalPopup :: withdrawalPopup"></section>
</body>
</html>
@@ -1,19 +0,0 @@
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head th:replace="fragment/kbank/head :: headFragment">
<meta charset="utf-8">
</head>
<body>
<div id="wrap">
<section layout:fragment="contentFragment">
</section>
<th:block layout:fragment="contentScript">
</th:block>
</div>
</body>
</html>