design merge

This commit is contained in:
Rinjae
2026-07-20 16:27:38 +09:00
parent 1cb9606602
commit 923bf56e21
66 changed files with 20056 additions and 9241 deletions
@@ -14,191 +14,180 @@
</div>
</section>
<th:block layout:fragment="contentFragment">
<section class="apikey-register-container">
<div class="signup-guide-v2 figma-register-wrapper">
<div class="service-main app-management-layout">
<!-- Title Bar -->
<div class="common-title-bar">
<h2 class="common-title">인증키 정보</h2>
<span class="common-subtitle">등록된 앱의 상세 정보를 확인할 수 있습니다.</span>
</div>
<!-- Left Sidebar (Service Sidebar) -->
<th:block th:replace="~{fragment/djbank/service_sidebar :: sidebar('apiKey')}"></th:block>
<!-- App Basic Info Card -->
<div class="app-info-card">
<!-- App Icon -->
<div class="app-info-icon">
<img th:if="${apiKey.appIconFileId != null}"
th:src="@{/file/download(fileSn=1,fileId=${apiKey.appIconFileId})}"
alt="앱 아이콘"
class="app-icon-image">
<div th:unless="${apiKey.appIconFileId != null}" class="app-icon-placeholder">
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
<circle cx="8.5" cy="8.5" r="1.5"></circle>
<polyline points="21 15 16 10 5 21"></polyline>
</svg>
</div>
</div>
<!-- App Info -->
<div class="app-info-content">
<!-- Status Badge -->
<span class="app-status-badge"
th:classappend="${apiKey.appstatus == '1' ? 'status-approved' : 'status-inactive'}"
th:text="${apiKey.appstatus == '1' ? '승인' : '비활성화'}">
승인
</span>
<!-- App Name -->
<h3 class="app-info-name" th:text="${apiKey.clientname}">앱 이름</h3>
<!-- App Description -->
<p class="app-info-description"
th:text="${apiKey.appDescription != null ? apiKey.appDescription : '앱 설명이 없습니다.'}">
여기에 입력한 앱 설명이 표시됩니다.
</p>
</div>
</div>
<!-- Content Area -->
<div class="app-management-content">
<div class="detail-wrap">
<!-- Title Bar -->
<div class="common-title-bar">
<h2 class="common-title">인증키 정보</h2>
<span class="common-subtitle">등록된 앱의 상세 정보를 확인할 수 있습니다.</span>
</div>
<!-- Credential Info Section -->
<div class="apikey-info-section">
<!-- App Basic Info Card -->
<div class="dt-app-card">
<div class="dt-app-icon-box">
<img th:if="${apiKey.appIconFileId != null}" th:src="@{/file/download(fileSn=1,fileId=${apiKey.appIconFileId})}" alt="앱 아이콘">
<svg th:unless="${apiKey.appIconFileId != null}" width="46" height="46" viewBox="0 0 24 24" fill="none" stroke="#64748b" stroke-width="1.5">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
<circle cx="8.5" cy="8.5" r="1.5"></circle>
<polyline points="21 15 16 10 5 21"></polyline>
</svg>
</div>
<div class="dt-app-info">
<div class="dt-app-status-row">
<span th:classappend="${apiKey.appstatus == '1' ? 'status-approved' : 'status-inactive'}" class="dt-status-badge" th:text="${apiKey.appstatus == '1' ? '승인' : '비활성화'}">승인</span>
</div>
<h3 class="dt-app-name" th:text="${apiKey.clientname}">앱 이름</h3>
<p class="dt-app-desc" th:text="${apiKey.appDescription != null ? apiKey.appDescription : '앱 설명이 없습니다.'}">앱 설명이 표시됩니다.</p>
</div>
</div>
<!-- 인증 방식 -->
<div class="info-row">
<label class="info-label">인증 방식</label>
<div class="info-value">
<div class="info-value-box" th:text="${authType}">OAuth2</div>
</div>
</div>
<!-- Credential Info Section Card -->
<div class="dt-info-container">
<!-- Client ID -->
<div class="info-row info-row-with-action">
<label class="info-label">Client ID</label>
<div class="info-value">
<div class="info-value-box with-copy" th:text="${apiKey.clientid}">K00000001</div>
<button type="button" class="btn-copy-action" th:data-secret="${apiKey.clientid}" onclick="copyToClipboardFromButton(this)">
+ 복사
</button>
</div>
</div>
<!-- 인증 방식 -->
<div class="dt-row">
<div class="dt-col">
<label class="dt-label">인증 방식</label>
<div class="dt-input-group">
<div class="dt-input-box" th:text="${authType}">OAuth2</div>
</div>
</div>
</div>
<!-- Client Secret -->
<div class="info-row">
<label class="info-label">
Client Secret
<button type="button" class="btn-guide-info" onclick="showLostKeyGuide()" title="비밀정보 안내" aria-label="비밀정보 안내"
style="margin-left:6px;width:18px;height:18px;border-radius:50%;border:1px solid #bbb;background:#f5f5f5;color:#666;font-size:12px;line-height:1;cursor:pointer;padding:0;vertical-align:middle;">?</button>
</label>
<div class="info-value">
<!-- Client ID -->
<div class="dt-row">
<div class="dt-col">
<label class="dt-label">Client ID</label>
<div class="dt-input-group">
<div class="dt-input-box" th:text="${apiKey.clientid}">K00000001</div>
<button type="button" class="dt-btn-copy" th:data-secret="${apiKey.clientid}" onclick="copyToClipboardFromButton(this)">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
<span>복사</span>
</button>
</div>
</div>
</div>
<!-- secret이 아직 DB에 존재: 최초 1회 조회 가능 -->
<th:block th:if="${secretAvailable}">
<!-- View Button (shown initially) -->
<button type="button" class="btn-view-secret" id="hiddenSecretBox" onclick="showPasswordPrompt()">
<span>조회</span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.35-4.35"></path>
</svg>
</button>
<!-- Actual Secret (hidden initially, 값은 서버 응답으로 JS가 주입) -->
<div id="revealedSecretBox" style="display: none;">
<div class="info-row info-row-with-action" style="margin-bottom: 0;">
<div class="info-value" style="display: flex; gap: 16px; align-items: center;">
<div class="info-value-box with-copy" id="revealedSecretValue"></div>
<button type="button" class="btn-copy-action" id="revealedSecretCopyBtn" onclick="copyToClipboardFromButton(this)">
+ 복사
</button>
</div>
</div>
<p class="secret-warning" style="margin:10px 0 0;padding:10px 12px;background:#fdecea;border:1px solid #f5c6c2;border-radius:6px;color:#c0392b;font-size:13px;line-height:1.6;">
⚠ 이 값은 <strong>지금 한 번만</strong> 표시되며, 이후에는 다시 조회할 수 없습니다.<br>
반드시 <strong>안전한 곳에 복사하여 보관</strong>해 주세요.
</p>
</div>
</th:block>
<!-- Client Secret -->
<div class="dt-row">
<div class="dt-col">
<label class="dt-label">
Client Secret
<button type="button" class="btn-guide-info" onclick="showLostKeyGuide()" title="비밀정보 안내" aria-label="비밀정보 안내"
style="margin-left:6px;width:18px;height:18px;border-radius:50%;border:1px solid #bbb;background:#f5f5f5;color:#666;font-size:12px;line-height:1;cursor:pointer;padding:0;vertical-align:middle;">?</button>
</label>
<!-- secret이 이미 노출·삭제됨 -->
<div th:unless="${secretAvailable}" class="secret-revealed-notice" style="display:flex;align-items:center;gap:12px;">
<span class="secret-revealed-text" style="color:#888;font-size:14px;">이미 1회 노출되어 삭제된 인증정보입니다.</span>
<button type="button" class="btn-guide-link" onclick="showLostKeyGuide()"
style="background:none;border:none;color:#2b6cb0;text-decoration:underline;cursor:pointer;font-size:13px;padding:0;">분실 시 안내</button>
</div>
<!-- secret이 아직 DB에 존재: 최초 1회 조회 가능 -->
<th:block th:if="${secretAvailable}">
<!-- View Button (shown initially) -->
<div class="dt-input-group" id="hiddenSecretBox">
<button type="button" style="width: 100%; border-radius: 10px; background-color: #2a69de; color: #fff; height: 47px; display: flex; justify-content: center; align-items: center; border: none; font-size: 16px; font-weight: 500; cursor: pointer; transition: background-color 0.2s;" onmouseover="this.style.backgroundColor='#1d56c0'" onmouseout="this.style.backgroundColor='#2a69de'" onclick="showPasswordPrompt()">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 8px;">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.35-4.35"></path>
</svg>
<span>조회</span>
</button>
</div>
</div>
</div>
<!-- Actual Secret (hidden initially, 값은 서버 응답으로 JS가 주입) -->
<div id="revealedSecretBox" style="display: none; width: 100%;">
<div class="dt-input-group">
<div class="dt-input-box" id="revealedSecretValue" style="flex: 1;"></div>
<button type="button" class="dt-btn-copy" id="revealedSecretCopyBtn" onclick="copyToClipboardFromButton(this)">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
<span>복사</span>
</button>
</div>
<p class="secret-warning" style="margin:10px 0 0;padding:10px 12px;background:#fdecea;border:1px solid #f5c6c2;border-radius:6px;color:#c0392b;font-size:13px;line-height:1.6;">
⚠ 이 값은 <strong>지금 한 번만</strong> 표시되며, 이후에는 다시 조회할 수 없습니다.<br>
반드시 <strong>안전한 곳에 복사하여 보관</strong>해 주세요.
</p>
</div>
</th:block>
<!-- Callback URL (주석 처리) -->
<!--/*
<div class="info-row">
<label class="info-label">Callback URL</label>
<div class="info-value">
<div class="info-value-box" th:text="${apiKey.redirecturi != null ? apiKey.redirecturi : '설정되지 않음'}">
http://www.abcd.co.kr
</div>
</div>
</div>
*/-->
<!-- secret이 이미 노출·삭제됨 -->
<div th:unless="${secretAvailable}" class="secret-revealed-notice" style="display:flex;align-items:center;gap:12px;">
<span class="secret-revealed-text" style="color:#888;font-size:14px;">이미 1회 노출되어 삭제된 인증정보입니다.</span>
<button type="button" class="btn-guide-link" onclick="showLostKeyGuide()"
style="background:none;border:none;color:#2b6cb0;text-decoration:underline;cursor:pointer;font-size:13px;padding:0;">분실 시 안내</button>
</div>
</div>
</div>
<!-- IP Whitelist -->
<div class="info-row">
<label class="info-label">화이트리스트</label>
<div class="info-value">
<div class="info-value-box" th:text="${apiKey.allowedips != null ? apiKey.allowedips : '설정되지 않음'}">
10.0.0.1, 10.0.0.2, 192.168.123.1
</div>
</div>
</div>
<!-- Callback URL (기능본에서 비활성 처리됨 — 렌더링되지 않음) -->
<!--/*
<div class="dt-row">
<div class="dt-col">
<label class="dt-label">Callback URL</label>
<div class="dt-input-group">
<div class="dt-input-box" th:text="${apiKey.redirecturi != null ? apiKey.redirecturi : '설정되지 않음'}">http://www.abcd.co.kr</div>
</div>
</div>
</div>
*/-->
<!-- Registered APIs -->
<div class="info-row info-row-vertical">
<label class="info-label">API 목록</label>
<div class="info-value">
<div class="api-list-box">
<div class="api-list-item" th:each="api : ${apiKey.apiList}">
<span class="api-item-name" th:text="${api.apiDesc}">추가된 API 1</span>
<span class="api-item-status">승인</span>
</div>
<!-- 화이트 리스트 -->
<div class="dt-row">
<div class="dt-col">
<label class="dt-label">화이트 리스트</label>
<div class="dt-input-group">
<div class="dt-input-box" th:text="${apiKey.allowedips != null ? apiKey.allowedips : '설정되지 않음'}">설정되지 않음</div>
</div>
</div>
</div>
<!-- Empty State -->
<div class="empty-state" th:if="${apiKey.apiList == null or apiKey.apiList.isEmpty()}" style="padding: 24px;">
<p>등록된 API가 없습니다.</p>
</div>
</div>
</div>
</div>
<!-- API 목록 -->
<div class="dt-row">
<div class="dt-col">
<label class="dt-label">API 목록</label>
<div class="dt-api-list-box">
<div class="dt-api-item" th:each="api : ${apiKey.apiList}">
<span class="dt-api-name" th:text="${api.apiDesc}">추가된 API 1</span>
<span class="dt-api-status-badge">승인</span>
</div>
<div class="dt-empty-message" th:if="${apiKey.apiList == null or apiKey.apiList.isEmpty()}">
등록된 API가 없습니다.
</div>
</div>
</div>
</div>
<!-- Modified Date -->
<div class="info-row">
<label class="info-label">최종 수정일자</label>
<div class="info-value">
<div class="info-value-box" th:text="${#temporals.format(apiKey.modifiedon, 'yyyy.MM.dd')}">
2025.11.22
</div>
</div>
</div>
<!-- 최종 수정일자 -->
<div class="dt-row">
<div class="dt-col">
<label class="dt-label">최종 수정일자</label>
<div class="dt-input-group">
<div class="dt-input-box" th:text="${#temporals.format(apiKey.modifiedon, 'yyyy.MM.dd')}">2025.11.22</div>
</div>
</div>
</div>
</div>
</div>
<!-- Bottom Navigation Actions -->
<div class="dt-actions" style="justify-content: flex-end; gap: 11px; margin-top: 30px;">
<a th:href="@{/myapikey}" class="dt-btn-gray">목록</a>
<button type="button" sec:authorize="hasRole('ROLE_API_KEY_REQUEST')" class="dt-btn-red" th:data-client-id="${apiKey.clientid}" onclick="deleteApiKeyFromButton(this)">인증키 삭제</button>
<a sec:authorize="hasRole('ROLE_API_KEY_REQUEST')" class="dt-btn-blue" th:href="@{/myapikey/modify/step1(clientId=${apiKey.clientid})}">수정</a>
</div>
<!-- Action Buttons -->
<div class="form-actions-center">
<a th:href="@{/myapikey}" class="btn btn-submit btn-secondary">
목록
</a>
<button type="button"
sec:authorize="hasRole('ROLE_API_KEY_REQUEST')"
class="btn btn-submit btn-danger"
th:data-client-id="${apiKey.clientid}"
onclick="deleteApiKeyFromButton(this)">
인증키 삭제
</button>
<a sec:authorize="hasRole('ROLE_API_KEY_REQUEST')"
class="btn btn-submit btn-primary app-modify-btn"
th:href="@{/myapikey/modify/step1(clientId=${apiKey.clientid})}">
수정
</a>
</div>
<p class="app-modify-pc-only-message" sec:authorize="hasRole('ROLE_API_KEY_REQUEST')">
앱 수정은 PC 환경에서 이용해 주세요.
</p>
</section>
</div><!-- /detail-wrap -->
</div><!-- /app-management-content -->
</div><!-- /service-main -->
</div><!-- /signup-guide-v2 -->
</th:block>