API 신청 완료페이지 스타일 적용

This commit is contained in:
현성필
2025-12-01 13:42:38 +09:00
parent d94760c160
commit 45c8c8961f
4 changed files with 390 additions and 335 deletions
@@ -6,6 +6,7 @@
<body>
<section layout:fragment="title">
<div class="page-title-banner">
<img th:src="@{/img/img_title_bg.png}" class="title-image">
<h1>앱관리</h1>
</div>
</section>
File diff suppressed because one or more lines are too long
@@ -1,148 +1,175 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_title_layout}">
<body>
<section layout:fragment="title">
<div class="page-title-banner">
<img th:src="@{/img/img_title_bg.png}" class="title-image">
<h1>앱관리</h1>
</div>
</section>
<th:block layout:fragment="contentFragment">
<section class="apikey-register-container">
<!-- Progress Indicator -->
<div class="register-progress">
<div class="progress-steps">
<div class="progress-step completed">
<div class="step-number"></div>
<div class="step-label">기본 정보</div>
</div>
<div class="progress-line filled"></div>
<div class="progress-step active">
<div class="step-number">2</div>
<div class="step-label">API 선택</div>
</div>
<div class="progress-line"></div>
<div class="progress-step">
<div class="step-number">3</div>
<div class="step-label">완료</div>
<!-- Title Bar -->
<div class="register-title-bar">
<h2 class="register-title">앱 생성</h2>
<span class="register-subtitle">API를 이용하여 서비스할 앱 정보를 등록해주세요.</span>
</div>
<!-- Progress Indicator -->
<div class="register-progress">
<div class="progress-steps">
<!-- Step 1: 앱 정보입력 (Active) -->
<div class="progress-step-item">
<div class="step-icon-wrapper">
<div class="step-icon">
<img th:src="@{/img/apikey_step1.png}" alt="앱 정보입력" width="36" height="36">
</div>
</div>
<div class="step-label">앱 정보입력</div>
</div>
<!-- Dots between Step 1 and 2 -->
<div class="step-dots">
<span></span><span></span><span></span>
</div>
<!-- Step 2: API 선택 -->
<div class="progress-step-item active">
<div class="step-icon-wrapper">
<div class="step-icon">
<img th:src="@{/img/apikey_step2.png}" alt="API 선택" width="36" height="36">
</div>
</div>
<div class="step-label">API 선택</div>
</div>
<!-- Dots between Step 2 and 3 -->
<div class="step-dots">
<span></span><span></span><span></span>
</div>
<!-- Step 3: 앱 생성 완료 -->
<div class="progress-step-item">
<div class="step-icon-wrapper">
<div class="step-icon">
<img th:src="@{/img/apikey_step3.png}" alt="앱 생성 완료" width="36" height="36">
</div>
</div>
<div class="step-label">앱 생성 완료</div>
</div>
</div>
</div>
</div>
<!-- Header -->
<div class="register-header">
<h1>API Key 생성</h1>
<h2>Step 2: API 선택</h2>
<p class="header-description">
사용할 API를 선택해주세요. 나중에 추가하거나 변경할 수 있습니다.
</p>
</div>
<!-- Form Content with Sidebar Layout -->
<div class="register-form-container with-sidebar">
<!-- Main Container with Sidebar Layout -->
<section class="apikey-register-container with-sidebar">
<!-- Sidebar Navigation -->
<aside class="apikey-register-sidebar" id="apiSidebar">
<nav class="apikey-sidebar-nav">
<!-- Sidebar Title -->
<div class="sidebar-title">API 서비스 목록</div>
<!-- Sidebar Navigation -->
<aside class="apikey-register-sidebar" id="apiSidebar">
<nav class="apikey-sidebar-nav">
<!-- All APIs -->
<div class="menu-section">
<div class="menu-title active" data-group="">
전체
<span class="api-count" th:text="${totalApiCount ?: 0}">0</span>
</div>
</div>
<!-- Service Categories -->
<div class="menu-section" th:each="service : ${apiServices}">
<div class="menu-title"
th:data-group="${service.id}">
[[${service.groupName}]]
<span class="api-count" th:text="${service.apiGroupApiList.size()}">0</span>
</div>
</div>
</nav>
</aside>
<!-- Mobile Menu Toggle -->
<button class="apikey-mobile-toggle" id="mobileToggle" aria-label="메뉴 열기">
</button>
<!-- Mobile Overlay -->
<div class="apikey-mobile-overlay" id="mobileOverlay"></div>
<!-- Main Content -->
<main class="apikey-register-content">
<form id="registerStep2Form" method="post" th:action="@{/myapikey/register/step2}" class="register-form">
<!-- Search Box with Select All -->
<div class="api-filter-header">
<div class="select-all-wrapper" id="selectAllWrapper" style="display: none;">
<label class="select-all-label">
<input type="checkbox" id="selectAllCheckbox" class="select-all-checkbox">
<span id="selectAllText">전체 선택</span>
</label>
</div>
<div class="api-search-box">
<input type="text"
id="apiSearch"
class="search-input"
placeholder="API 검색...">
<span class="search-icon">🔍</span>
</div>
</div>
<!-- Result Count -->
<div class="api-result-count">
<strong id="visibleCount">0</strong>
</div>
<!-- API Cards Grid -->
<div class="api-selection-card-grid" id="apiCardGrid">
<!-- Loading state -->
<div class="loading-state" id="loadingState" style="grid-column: 1 / -1;">
<div class="loading-spinner"></div>
<p>API 목록을 불러오는 중...</p>
<!-- All APIs -->
<div class="menu-section">
<div class="menu-title active" data-group="">
<span class="menu-text">전체</span>
<span class="api-count"></span>
</div>
</div>
<!-- Empty state (initially hidden) -->
<div class="empty-api-state" id="emptyState" style="display: none; grid-column: 1 / -1;">
<div class="empty-icon">📦</div>
<h3>API를 선택해주세요</h3>
<p>왼쪽 메뉴에서 서비스를 선택하면 해당 API 목록이 표시됩니다.</p>
<!-- Service Categories -->
<div class="menu-section" th:each="service : ${apiServices}">
<div class="menu-title"
th:data-group="${service.id}">
<span class="menu-text" th:text="${service.groupName}">서비스명</span>
<span class="api-count"></span>
</div>
</div>
</nav>
</aside>
<!-- Mobile Menu Toggle -->
<button class="apikey-mobile-toggle" id="mobileToggle" aria-label="메뉴 열기">
</button>
<!-- Mobile Overlay -->
<div class="apikey-mobile-overlay" id="mobileOverlay"></div>
<!-- Main Content -->
<main class="apikey-register-content">
<form id="registerStep2Form" method="post" th:action="@{/myapikey/register/step2}" class="register-form">
<!-- Search Box with Select All -->
<div class="api-filter-header">
<div class="select-all-wrapper" id="selectAllWrapper" style="display: none;">
<label class="select-all-label">
<input type="checkbox" id="selectAllCheckbox" class="select-all-checkbox visually-hidden">
<span class="custom-checkbox"></span>
<span id="selectAllText">전체 선택</span>
</label>
</div>
<div class="api-search-box">
<input type="text"
id="apiSearch"
class="search-input"
placeholder="API 검색...">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.23047 0C14.3285 0 18.4618 4.0539 18.4619 9.05469C18.4619 11.1083 17.7634 13.0014 16.5889 14.5205C16.5913 14.5229 16.5942 14.5249 16.5967 14.5273L19.5498 17.4238C20.1502 18.0131 20.1501 18.9683 19.5498 19.5576C18.949 20.147 17.9748 20.147 17.374 19.5576L14.4209 16.6621C14.3966 16.6383 14.3749 16.6119 14.3525 16.5869C12.8868 17.5478 11.1257 18.1094 9.23047 18.1094C4.13258 18.1092 0 14.0554 0 9.05469C0.000110268 4.05404 4.13265 0.000222701 9.23047 0ZM9.23047 3.01855C5.83201 3.01878 3.07726 5.721 3.07715 9.05469C3.07715 12.3885 5.83194 15.0916 9.23047 15.0918C12.6292 15.0918 15.3848 12.3886 15.3848 9.05469C15.3847 5.72086 12.6291 3.01855 9.23047 3.01855Z" fill="#515961"/>
</svg>
</div>
</div>
<!-- API cards will be dynamically loaded here -->
<!-- API Cards Grid -->
<div class="api-selection-card-grid" id="apiCardGrid">
<!-- Loading state -->
<div class="loading-state" id="loadingState" style="grid-column: 1 / -1;">
<div class="loading-spinner"></div>
<p>API 목록을 불러오는 중...</p>
</div>
</div>
<!-- Empty state (initially hidden) -->
<div class="empty-api-state" id="emptyState" style="display: none; grid-column: 1 / -1;">
<div class="empty-icon">📦</div>
<h3>API를 선택해주세요</h3>
<p>왼쪽 메뉴에서 서비스를 선택하면 해당 API 목록이 표시됩니다.</p>
</div>
<!-- Form Actions -->
<div class="form-actions">
<button type="button" id="btnPrevStep" class="btn btn-secondary">
<span class="icon"></span>
이전 단계
</button>
<button type="submit" class="btn btn-primary">
저장
<span class="icon"></span>
</button>
</div>
<!-- API cards will be dynamically loaded here -->
</form>
</main>
</div>
</form>
</main>
</div>
<!-- Form Actions -->
<div class="form-actions">
<button type="button" id="btnPrevStep" class="btn-submit btn-secondary">
이전
</button>
<button type="submit" form="registerStep2Form" class="btn-submit btn-primary">
저장
</button>
</div>
</section>
<!-- Floating Cart Button -->
<!-- Floating Cart Button (Figma Design) -->
<button type="button" class="floating-cart-btn" id="floatingCartBtn" style="display: none;">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="9" cy="21" r="1"></circle>
<circle cx="20" cy="21" r="1"></circle>
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
</svg>
<span class="cart-text">선택된 API:</span>
<span class="cart-badge" id="cartBadge">0</span>
<span class="cart-label">선택된 API</span>
<span class="cart-badge" id="cartBadge">
<span class="cart-count">0</span>
<span class="cart-unit"></span>
</span>
</button>
<!-- Selected APIs Modal - Uses base modal component -->
<div class="selected-apis-modal" id="selectedApisModal" style="display: none;">
<div class="modal" id="selectedApisModal" style="display: none;">
<div class="modal-backdrop" id="modalOverlay"></div>
<div class="modal-dialog">
<div class="modal-header">
@@ -176,7 +203,6 @@
const mobileToggle = document.getElementById('mobileToggle');
const mobileOverlay = document.getElementById('mobileOverlay');
const menuTitles = document.querySelectorAll('.menu-title');
const visibleCount = document.getElementById('visibleCount');
const apiCardGrid = document.getElementById('apiCardGrid');
const loadingState = document.getElementById('loadingState');
const emptyState = document.getElementById('emptyState');
@@ -209,29 +235,24 @@
url += '?groupIds=' + encodeURIComponent(groupId);
}
fetch(url)
.then(response => response.json())
.then(apis => {
allApis = apis;
loadingState.style.display = 'none';
fetch(url).then(response => response.json()).then(apis => {
allApis = apis;
loadingState.style.display = 'none';
if (apis.length === 0) {
emptyState.style.display = 'block';
visibleCount.textContent = '0';
return;
}
renderApiCards(apis);
visibleCount.textContent = apis.length;
updateSelectAllUI();
})
.catch(error => {
console.error('Failed to load APIs:', error);
loadingState.style.display = 'none';
emptyState.querySelector('h3').textContent = 'API 로드 실패';
emptyState.querySelector('p').textContent = '다시 시도해주세요.';
if (apis.length === 0) {
emptyState.style.display = 'block';
});
return;
}
renderApiCards(apis);
updateSelectAllUI();
}).catch(error => {
console.error('Failed to load APIs:', error);
loadingState.style.display = 'none';
emptyState.querySelector('h3').textContent = 'API 로드 실패';
emptyState.querySelector('p').textContent = '다시 시도해주세요.';
emptyState.style.display = 'block';
});
}
// Render API cards
@@ -263,29 +284,32 @@
card.classList.add('selected');
}
card.innerHTML = `
<div class="checkbox-wrapper">
<input type="checkbox"
name="selectedApis"
value="${api.apiId}"
id="api-${api.apiId}"
class="api-checkbox"
${isSelected ? 'checked' : ''}>
</div>
const mainIconHtml = api.mainIcon
? `<img src="${api.mainIcon}" alt="${api.apiName}" onerror="this.style.display='none'; this.nextElementSibling.style.display='block'"><i class="fas fa-cube" style="display:none"></i>`
: `<i class="fas fa-cube"></i>`;
<label class="api-card-content" for="api-${api.apiId}">
card.innerHTML = `
<div class="api-card-content">
<div class="api-card-header">
<span class="api-card-badge">${api.service || '카테고리'}</span>
<span class="api-method">${api.apiMethod || 'GET'}</span>
<span class="api-card-category">${api.service || '카테고리'}</span>
<div class="checkbox-wrapper">
<input type="checkbox"
name="selectedApis"
value="${api.apiId}"
id="api-${api.apiId}"
class="api-checkbox visually-hidden"
${isSelected ? 'checked' : ''}>
<span class="custom-checkbox"></span>
</div>
</div>
<h3 class="api-name">${api.apiName || 'API 이름'}</h3>
<p class="api-description">${api.apiSimpleDescription || 'API 설명이 없습니다.'}</p>
<div class="api-endpoint">
<code>${api.apiUrl || '/api/v1/example'}</code>
<div class="api-card-icon">
${mainIconHtml}
</div>
</label>
</div>
`;
return card;
@@ -305,7 +329,8 @@
apiCards.forEach(card => {
card.addEventListener('click', function(e) {
if (e.target.classList.contains('api-checkbox') || e.target.tagName === 'LABEL') {
// Prevent double toggle if clicking directly on checkbox
if (e.target.classList.contains('api-checkbox')) {
return;
}
const checkbox = card.querySelector('.api-checkbox');
@@ -334,11 +359,11 @@
function updateSelectedCount() {
// Update floating cart button
const floatingCartBtn = document.getElementById('floatingCartBtn');
const cartBadge = document.getElementById('cartBadge');
const cartCount = document.querySelector('.cart-count');
if (selectedApis.size > 0) {
floatingCartBtn.style.display = 'flex';
cartBadge.textContent = selectedApis.size;
cartCount.textContent = selectedApis.size;
} else {
floatingCartBtn.style.display = 'none';
}
@@ -441,7 +466,6 @@
if (searchInput) {
searchInput.addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
let visibleCountNum = 0;
const apiCards = document.querySelectorAll('.api-selection-card');
apiCards.forEach(function(card) {
@@ -453,14 +477,11 @@
if (matchesSearch) {
card.style.display = '';
visibleCountNum++;
} else {
card.style.display = 'none';
}
});
visibleCount.textContent = visibleCountNum;
// Update select all checkbox state after search
updateSelectAllCheckboxState();
});
@@ -1,50 +1,82 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_title_layout}">
<body>
<section layout:fragment="title">
<div class="page-title-banner">
<img th:src="@{/img/img_title_bg.png}" class="title-image">
<h1>앱관리</h1>
</div>
</section>
<th:block layout:fragment="contentFragment">
<section class="apikey-register-container">
<!-- Progress Indicator -->
<!-- Title Bar -->
<div class="register-title-bar">
<h2 class="register-title">앱 생성</h2>
<span class="register-subtitle">API를 이용하여 서비스할 앱 정보를 등록해주세요.</span>
</div>
<div class="register-progress">
<div class="progress-steps">
<div class="progress-step completed">
<div class="step-number"></div>
<div class="step-label">기본 정보</div>
<!-- Step 1: 앱 정보입력 (Active) -->
<div class="progress-step-item">
<div class="step-icon-wrapper">
<div class="step-icon">
<img th:src="@{/img/apikey_step1.png}" alt="앱 정보입력" width="36" height="36">
</div>
</div>
<div class="step-label">앱 정보입력</div>
</div>
<div class="progress-line filled"></div>
<div class="progress-step completed">
<div class="step-number"></div>
<!-- Dots between Step 1 and 2 -->
<div class="step-dots">
<span></span><span></span><span></span>
</div>
<!-- Step 2: API 선택 -->
<div class="progress-step-item">
<div class="step-icon-wrapper">
<div class="step-icon">
<img th:src="@{/img/apikey_step2.png}" alt="API 선택" width="36" height="36">
</div>
</div>
<div class="step-label">API 선택</div>
</div>
<div class="progress-line filled"></div>
<div class="progress-step active completed">
<div class="step-number"></div>
<div class="step-label">완료</div>
<!-- Dots between Step 2 and 3 -->
<div class="step-dots">
<span></span><span></span><span></span>
</div>
<!-- Step 3: 앱 생성 완료 -->
<div class="progress-step-item active">
<div class="step-icon-wrapper">
<div class="step-icon">
<img th:src="@{/img/apikey_step3.png}" alt="앱 생성 완료" width="36" height="36">
</div>
</div>
<div class="step-label">앱 생성 완료</div>
</div>
</div>
</div>
<!-- Success Result -->
<div class="register-result success" th:if="${registrationComplete}">
<!-- Success Icon Animation -->
<div class="result-icon-wrapper">
<div class="result-icon success-icon">
<svg class="checkmark" viewBox="0 0 52 52">
<circle class="checkmark-circle" cx="26" cy="26" r="25" fill="none"/>
<path class="checkmark-check" fill="none" d="M14.1 27.2l7.1 7.2 16.7-16.8"/>
</svg>
<img th:src="@{/img/img_app_complete.png}">
</div>
</div>
<!-- Success Message -->
<div class="result-header">
<h1>API Key 생성 완료!</h1>
<h1>앱 생성 요청이 완료되었습니다.</h1>
<p class="result-description">
API Key가 성공적으로 생성되었습니다. 아래 정보를 안전한 곳에 보관해주세요.
담당자 앱 승인 후 [앱 정보]화면에서 Client ID와 Client Secret을 확인 할 수 있습니다.
</p>
</div>
@@ -53,9 +85,8 @@
<!-- Action Buttons -->
<div class="result-actions">
<a href="/myapikey" class="btn btn-secondary">
<span class="icon">📋</span>
API Key 목록으로
<a href="/myapikey" class="btn btn-primary">
인증키 관리
</a>
</div>
</div>