인증키 요청 조회, 인증키 상세 조회 디자인 반영
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
+1
-1
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: 13 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
@@ -7,25 +7,6 @@
|
|||||||
@include card;
|
@include card;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
// Card hover effect
|
|
||||||
&::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: -50%;
|
|
||||||
left: -50%;
|
|
||||||
width: 200%;
|
|
||||||
height: 200%;
|
|
||||||
background: #3BA4ED;
|
|
||||||
transform: rotate(45deg);
|
|
||||||
transition: $transition-slow;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover::before {
|
|
||||||
animation: shimmer 0.6s ease;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// API showcase cards
|
// API showcase cards
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
//background-color: #C3DFEA;
|
//background-color: #C3DFEA;
|
||||||
background-color: #E5F8FF;
|
background-color: #ffffff;
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
-webkit-backdrop-filter: blur(10px);
|
-webkit-backdrop-filter: blur(10px);
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
@import 'components/buttons';
|
@import 'components/buttons';
|
||||||
@import 'components/badges';
|
@import 'components/badges';
|
||||||
@import 'components/cards';
|
@import 'components/cards';
|
||||||
|
@import 'components/apikey-common';
|
||||||
@import 'components/forms';
|
@import 'components/forms';
|
||||||
@import 'components/modals';
|
@import 'components/modals';
|
||||||
@import 'components/navigation';
|
@import 'components/navigation';
|
||||||
@@ -47,6 +48,7 @@
|
|||||||
@import 'pages/mypage';
|
@import 'pages/mypage';
|
||||||
@import 'pages/apikey-register';
|
@import 'pages/apikey-register';
|
||||||
@import 'pages/apikey-detail';
|
@import 'pages/apikey-detail';
|
||||||
|
@import 'pages/apikey-list';
|
||||||
@import 'pages/notice';
|
@import 'pages/notice';
|
||||||
@import 'pages/inquiry';
|
@import 'pages/inquiry';
|
||||||
@import 'pages/org-register';
|
@import 'pages/org-register';
|
||||||
|
|||||||
@@ -3,17 +3,16 @@
|
|||||||
// ====================================
|
// ====================================
|
||||||
// API Key Detail Pages Styles
|
// API Key Detail Pages Styles
|
||||||
// For appRequestDetail.html and credentialDetail.html
|
// For appRequestDetail.html and credentialDetail.html
|
||||||
|
// Uses common styles from components/_apikey-common.scss
|
||||||
// ====================================
|
// ====================================
|
||||||
|
|
||||||
// Container Styles
|
// Container Styles - extends common
|
||||||
.apikey-detail-container {
|
.apikey-detail-container {
|
||||||
max-width: 1200px;
|
@extend .apikey-container;
|
||||||
margin: 0 auto;
|
padding: $spacing-2xl $spacing-lg;
|
||||||
padding: $spacing-4xl;
|
|
||||||
padding-top: 0;
|
|
||||||
|
|
||||||
@media (max-width: $breakpoint-sm) {
|
@media (max-width: $breakpoint-sm) {
|
||||||
padding: $spacing-md;
|
padding: $spacing-lg $spacing-md;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,12 +39,9 @@
|
|||||||
margin-bottom: $spacing-4xl;
|
margin-bottom: $spacing-4xl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Status Badge - extends common with light style
|
||||||
.status-badge {
|
.status-badge {
|
||||||
display: inline-block;
|
@extend .apikey-status-badge;
|
||||||
padding: $spacing-sm $spacing-lg;
|
|
||||||
border-radius: $border-radius-full;
|
|
||||||
font-weight: $font-weight-semibold;
|
|
||||||
font-size: $font-size-sm;
|
|
||||||
|
|
||||||
&.badge-pending {
|
&.badge-pending {
|
||||||
background-color: #FFF3CD;
|
background-color: #FFF3CD;
|
||||||
@@ -63,17 +59,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detail Section
|
// Detail Section - extends common
|
||||||
.detail-section {
|
.detail-section {
|
||||||
background: $white;
|
@extend .apikey-detail-section;
|
||||||
border-radius: $border-radius-lg;
|
|
||||||
padding: $spacing-4xl;
|
|
||||||
margin-bottom: $spacing-lg;
|
|
||||||
box-shadow: $shadow-md;
|
|
||||||
|
|
||||||
@media (max-width: $breakpoint-sm) {
|
|
||||||
padding: $spacing-xl;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-title {
|
.section-title {
|
||||||
@@ -82,28 +70,15 @@
|
|||||||
color: $text-dark;
|
color: $text-dark;
|
||||||
margin-bottom: $spacing-lg;
|
margin-bottom: $spacing-lg;
|
||||||
padding-bottom: $spacing-md;
|
padding-bottom: $spacing-md;
|
||||||
//border-bottom: 2px solid $border-gray;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detail Grid
|
// Detail Grid - extends common
|
||||||
.detail-grid {
|
.detail-grid {
|
||||||
display: grid;
|
@extend .apikey-detail-grid;
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
gap: $spacing-lg;
|
|
||||||
|
|
||||||
@media (max-width: $breakpoint-sm) {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-item {
|
.detail-item {
|
||||||
display: flex;
|
@extend .apikey-detail-item;
|
||||||
flex-direction: column;
|
|
||||||
gap: $spacing-sm;
|
|
||||||
|
|
||||||
&.full-width {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-label {
|
.detail-label {
|
||||||
@@ -118,106 +93,46 @@
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
// App Icon
|
// App Icon - extends common
|
||||||
.app-icon-display {
|
.app-icon-display {
|
||||||
width: 120px;
|
@extend .apikey-icon-display;
|
||||||
height: 120px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-icon-image {
|
.app-icon-image {
|
||||||
width: 100%;
|
@extend .apikey-icon-image;
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: $border-radius-lg;
|
|
||||||
border: 2px solid $border-gray;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-icon-placeholder {
|
.app-icon-placeholder {
|
||||||
width: 100%;
|
@extend .apikey-icon-placeholder;
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background-color: $gray-bg;
|
|
||||||
border-radius: $border-radius-lg;
|
|
||||||
border: 2px dashed $text-light;
|
|
||||||
font-size: 48px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IP List
|
// IP List - extends common
|
||||||
.ip-list {
|
.ip-list {
|
||||||
display: flex;
|
@extend .apikey-ip-list;
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: $spacing-sm;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ip-tag {
|
.ip-tag {
|
||||||
display: inline-block;
|
@extend .apikey-ip-tag;
|
||||||
padding: $spacing-xs $spacing-md;
|
|
||||||
background-color: $light-bg;
|
|
||||||
color: $secondary-blue;
|
|
||||||
border-radius: $border-radius-sm;
|
|
||||||
font-size: $font-size-sm;
|
|
||||||
font-family: $font-family-mono;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Credential Code
|
// Credential Code - extends common
|
||||||
.credential-code {
|
.credential-code {
|
||||||
display: inline-block;
|
@extend .apikey-credential-code;
|
||||||
padding: $spacing-sm $spacing-md;
|
|
||||||
background-color: $gray-bg;
|
|
||||||
border: 1px solid $border-gray;
|
|
||||||
border-radius: $border-radius-sm;
|
|
||||||
font-family: $font-family-mono;
|
|
||||||
font-size: $font-size-sm;
|
|
||||||
color: $secondary-blue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Secret Box
|
// Secret Box - extends common
|
||||||
.secret-box {
|
.secret-box {
|
||||||
display: flex;
|
@extend .apikey-secret-box;
|
||||||
align-items: center;
|
|
||||||
gap: $spacing-md;
|
|
||||||
|
|
||||||
@media (max-width: $breakpoint-sm) {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: .btn-copy styles now in components/_buttons.scss
|
// Copy Button - extends common
|
||||||
// Keeping this for page-specific override if needed
|
|
||||||
.btn-copy {
|
.btn-copy {
|
||||||
svg {
|
@extend .apikey-btn-copy;
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: $breakpoint-sm) {
|
|
||||||
align-self: stretch;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status Indicator
|
// Status Indicator - extends common
|
||||||
.status-indicator {
|
.status-indicator {
|
||||||
display: inline-flex;
|
@extend .apikey-status-indicator;
|
||||||
align-items: center;
|
|
||||||
gap: $spacing-sm;
|
|
||||||
padding: $spacing-xs $spacing-md;
|
|
||||||
border-radius: $border-radius-xl;
|
|
||||||
font-size: $font-size-sm;
|
|
||||||
font-weight: $font-weight-semibold;
|
|
||||||
|
|
||||||
&.status-active {
|
|
||||||
background-color: #D4EDDA;
|
|
||||||
color: #155724;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.status-inactive {
|
|
||||||
background-color: #F8D7DA;
|
|
||||||
color: #721C24;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-dot {
|
.status-dot {
|
||||||
@@ -227,31 +142,20 @@
|
|||||||
background-color: currentColor;
|
background-color: currentColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
// API Detail List
|
// API Detail List - extends common
|
||||||
.api-detail-list {
|
.api-detail-list {
|
||||||
display: flex;
|
@extend .apikey-api-list;
|
||||||
flex-direction: column;
|
|
||||||
gap: $spacing-sm;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.api-list-item {
|
.api-list-item {
|
||||||
display: flex;
|
@extend .apikey-api-item;
|
||||||
align-items: center;
|
|
||||||
gap: $spacing-md;
|
|
||||||
padding: $spacing-md $spacing-lg;
|
|
||||||
background: $white;
|
|
||||||
border: 1px solid $border-gray;
|
|
||||||
border-radius: $border-radius-md;
|
|
||||||
transition: all 0.2s;
|
|
||||||
|
|
||||||
&:hover {
|
.api-name {
|
||||||
border-color: $primary-blue;
|
flex: 1;
|
||||||
box-shadow: $shadow-sm;
|
font-size: $font-size-base;
|
||||||
}
|
font-weight: $font-weight-medium;
|
||||||
|
color: $text-dark;
|
||||||
@media (max-width: $breakpoint-sm) {
|
margin: 0;
|
||||||
padding: $spacing-md;
|
|
||||||
gap: $spacing-sm;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,14 +195,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.api-list-item .api-name {
|
|
||||||
flex: 1;
|
|
||||||
font-size: $font-size-base;
|
|
||||||
font-weight: $font-weight-medium;
|
|
||||||
color: $text-dark;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.api-status {
|
.api-status {
|
||||||
padding: $spacing-xs $spacing-md;
|
padding: $spacing-xs $spacing-md;
|
||||||
border-radius: $border-radius-lg;
|
border-radius: $border-radius-lg;
|
||||||
@@ -350,36 +246,16 @@
|
|||||||
font-weight: $font-weight-semibold;
|
font-weight: $font-weight-semibold;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty State
|
// Empty State - extends common
|
||||||
.empty-state {
|
.empty-state {
|
||||||
|
@extend .apikey-empty-state;
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
text-align: center;
|
|
||||||
padding: 48px $spacing-lg;
|
padding: 48px $spacing-lg;
|
||||||
|
|
||||||
.empty-icon {
|
|
||||||
font-size: 64px;
|
|
||||||
margin-bottom: $spacing-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: $font-size-base;
|
|
||||||
color: $text-gray;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Action Buttons
|
// Action Buttons - extends common
|
||||||
.detail-actions {
|
.detail-actions {
|
||||||
display: flex;
|
@extend .apikey-form-actions;
|
||||||
justify-content: center;
|
|
||||||
gap: $spacing-md;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-top: $spacing-4xl;
|
|
||||||
padding-top: $spacing-4xl;
|
|
||||||
border-top: 1px solid $border-gray;
|
|
||||||
|
|
||||||
@media (max-width: $breakpoint-sm) {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: Button styles now in components/_buttons.scss
|
// Note: Button styles now in components/_buttons.scss
|
||||||
@@ -391,3 +267,353 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ====================================
|
||||||
|
// App Info Card (Figma Design)
|
||||||
|
// For credentialDetail.html and appRequestDetail.html
|
||||||
|
// ====================================
|
||||||
|
|
||||||
|
.app-info-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: $spacing-xl;
|
||||||
|
padding: 0;
|
||||||
|
padding-top: 27px;
|
||||||
|
margin-bottom: $spacing-4xl;
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-sm) {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
padding: $spacing-lg 0;
|
||||||
|
gap: $spacing-lg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-info-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 160px;
|
||||||
|
height: 160px;
|
||||||
|
border-radius: $border-radius-xl;
|
||||||
|
overflow: hidden;
|
||||||
|
background: $gray-bg;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-left: 87px;
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-sm) {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-icon-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-icon-placeholder {
|
||||||
|
color: $text-light;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-info-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $spacing-md;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-status-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: $spacing-xs $spacing-lg;
|
||||||
|
border-radius: $border-radius-full;
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
font-weight: $font-weight-semibold;
|
||||||
|
width: fit-content;
|
||||||
|
|
||||||
|
&.status-approved {
|
||||||
|
background-color: #0049b4;
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.status-pending {
|
||||||
|
background-color: #FFF3CD;
|
||||||
|
color: #856404;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.status-inactive {
|
||||||
|
background-color: $border-gray;
|
||||||
|
color: $text-gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.status-rejected {
|
||||||
|
background-color: #F8D7DA;
|
||||||
|
color: #721C24;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-info-name {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: $font-weight-bold;
|
||||||
|
color: $text-dark;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-sm) {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-info-description {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #515151;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-sm) {
|
||||||
|
font-size: $font-size-base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================================
|
||||||
|
// API Key Info Section
|
||||||
|
// For credential details display
|
||||||
|
// Figma Design: node 985-1365
|
||||||
|
// ====================================
|
||||||
|
|
||||||
|
.apikey-info-section {
|
||||||
|
background: #F6F9FB;
|
||||||
|
border-radius: $border-radius-lg;
|
||||||
|
padding: $spacing-3xl $spacing-2xl;
|
||||||
|
margin-bottom: $spacing-2xl;
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-sm) {
|
||||||
|
padding: $spacing-lg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: $spacing-xl;
|
||||||
|
margin-bottom: $spacing-xl;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-sm) {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: $spacing-sm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-row-vertical {
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-sm) {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 140px;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: $font-weight-bold;
|
||||||
|
color: #212529;
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-sm) {
|
||||||
|
width: 100%;
|
||||||
|
font-size: $font-size-base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
flex: 1;
|
||||||
|
font-size: $font-size-base;
|
||||||
|
color: $text-dark;
|
||||||
|
word-break: break-word;
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-sm) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info Value Box Styles (Figma)
|
||||||
|
.info-value-box {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 60px;
|
||||||
|
padding: 0 $spacing-lg;
|
||||||
|
background: #DDDDDD;
|
||||||
|
border-radius: $border-radius-lg;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #515151;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-sm) {
|
||||||
|
height: 50px;
|
||||||
|
font-size: $font-size-base;
|
||||||
|
padding: 0 $spacing-md;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.with-copy {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client ID Row with Copy Button
|
||||||
|
.info-row-with-action {
|
||||||
|
.info-value {
|
||||||
|
display: flex;
|
||||||
|
gap: $spacing-md;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-copy-action {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: $spacing-xs;
|
||||||
|
height: 60px;
|
||||||
|
width: 158px;
|
||||||
|
padding: 0 $spacing-lg;
|
||||||
|
background: #A4D6EA;
|
||||||
|
border: none;
|
||||||
|
border-radius: $border-radius-lg;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: $font-weight-bold;
|
||||||
|
color: $white;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: darken(#A4D6EA, 10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-sm) {
|
||||||
|
height: 50px;
|
||||||
|
font-size: $font-size-base;
|
||||||
|
padding: 0 $spacing-md;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client Secret View Button
|
||||||
|
.btn-view-secret {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: $spacing-sm;
|
||||||
|
width: 100%;
|
||||||
|
height: 60px;
|
||||||
|
background: #3BA4ED;
|
||||||
|
border: none;
|
||||||
|
border-radius: $border-radius-lg;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: $font-weight-bold;
|
||||||
|
color: $white;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: darken(#3BA4ED, 10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-sm) {
|
||||||
|
max-width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
font-size: $font-size-base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API List Box (White background)
|
||||||
|
.api-list-box {
|
||||||
|
width: 100%;
|
||||||
|
background: $white;
|
||||||
|
border: 1px solid #DADADA;
|
||||||
|
border-radius: $border-radius-lg;
|
||||||
|
padding: $spacing-lg;
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-sm) {
|
||||||
|
max-width: 100%;
|
||||||
|
padding: $spacing-md;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API List Item (Figma style)
|
||||||
|
.api-list-item-figma {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: $spacing-md 0;
|
||||||
|
border-bottom: 1px solid #DADADA;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-item-name {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000;
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-sm) {
|
||||||
|
font-size: $font-size-base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-item-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 100px;
|
||||||
|
height: 30px;
|
||||||
|
padding: 0 $spacing-lg;
|
||||||
|
background: #ECEFF4;
|
||||||
|
border-radius: $border-radius-full;
|
||||||
|
font-size: $font-size-base;
|
||||||
|
color: #515151;
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-sm) {
|
||||||
|
min-width: 80px;
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
padding: 0 $spacing-md;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================================
|
||||||
|
// Form Actions Center
|
||||||
|
// ====================================
|
||||||
|
|
||||||
|
.form-actions-center {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: $spacing-md;
|
||||||
|
margin-top: $spacing-2xl;
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-sm) {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// API Key Registration Wizard Styles
|
// API Key Registration Wizard Styles
|
||||||
// Reference: apps/mypage/apiKeyRegisterStep1.html, Step2, Step3
|
// Reference: apps/mypage/apiKeyRegisterStep1.html, Step2, Step3
|
||||||
|
// Uses common styles from components/_apikey-common.scss
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// STEP 1: App Information Input (New Figma Design)
|
// STEP 1: App Information Input (New Figma Design)
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
// Title Bar
|
// Title Bar - extends common with flex layout
|
||||||
.register-title-bar {
|
.register-title-bar {
|
||||||
background-color: #f6f9fb;
|
@extend .apikey-title-bar;
|
||||||
border-radius: $border-radius-lg;
|
|
||||||
padding: 18px 45px;
|
padding: 18px 45px;
|
||||||
margin-bottom: 30px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
@@ -116,10 +115,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Form Container (Step 1)
|
// Form Container (Step 1) - extends common
|
||||||
.register-form-container {
|
.register-form-container {
|
||||||
background-color: #f6f9fb;
|
@extend .apikey-section-box;
|
||||||
border-radius: $border-radius-lg;
|
|
||||||
padding: 50px 45px;
|
padding: 50px 45px;
|
||||||
|
|
||||||
@include respond-to('md') {
|
@include respond-to('md') {
|
||||||
@@ -483,39 +481,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Submit Button - Step 1 Style
|
// Submit Button - Step 1 Style - extends common
|
||||||
.btn-submit {
|
.btn-submit {
|
||||||
width: 220px;
|
@extend .apikey-action-btn;
|
||||||
height: 60px;
|
|
||||||
border: none;
|
|
||||||
border-radius: $border-radius-lg;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: $font-weight-bold;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.2s ease, transform 0.2s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@include respond-to('md') {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 300px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// STEP 2 & STEP 3: Original Styles
|
// STEP 2 & STEP 3: Original Styles
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
// Container with sidebar layout (Step 2)
|
// Container with sidebar layout (Step 2) - extends common
|
||||||
.apikey-register-container {
|
.apikey-register-container {
|
||||||
max-width: $container-max-width;
|
@extend .apikey-container;
|
||||||
margin: 0 auto;
|
|
||||||
padding: $spacing-2xl $spacing-lg;
|
padding: $spacing-2xl $spacing-lg;
|
||||||
|
|
||||||
@media (max-width: $breakpoint-sm) {
|
@media (max-width: $breakpoint-sm) {
|
||||||
@@ -1167,29 +1144,10 @@ input[type="checkbox"]:checked + .custom-checkbox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty state for API selection
|
// Empty state for API selection - extends common
|
||||||
.empty-api-state {
|
.empty-api-state {
|
||||||
|
@extend .apikey-empty-state;
|
||||||
padding: $spacing-3xl;
|
padding: $spacing-3xl;
|
||||||
text-align: center;
|
|
||||||
color: $text-gray;
|
|
||||||
|
|
||||||
.empty-icon {
|
|
||||||
font-size: 60px;
|
|
||||||
margin-bottom: $spacing-lg;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: $font-size-lg;
|
|
||||||
font-weight: $font-weight-semibold;
|
|
||||||
color: $text-dark;
|
|
||||||
margin-bottom: $spacing-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: $font-size-base;
|
|
||||||
color: $text-gray;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loading state for API selection
|
// Loading state for API selection
|
||||||
@@ -1563,17 +1521,16 @@ input[type="checkbox"]:checked + .custom-checkbox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Form Actions
|
// Form Actions - extends common
|
||||||
.form-actions {
|
.form-actions {
|
||||||
display: flex;
|
@extend .apikey-form-actions;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
|
||||||
margin-top: $spacing-2xl;
|
|
||||||
padding: 0 $spacing-lg $spacing-lg;
|
padding: 0 $spacing-lg $spacing-lg;
|
||||||
gap: $spacing-md;
|
margin-top: $spacing-2xl;
|
||||||
|
border-top: none;
|
||||||
|
padding-top: 0;
|
||||||
|
|
||||||
@media (max-width: $breakpoint-sm) {
|
@media (max-width: $breakpoint-sm) {
|
||||||
flex-direction: column;
|
|
||||||
padding: 0 $spacing-md $spacing-md;
|
padding: 0 $spacing-md $spacing-md;
|
||||||
gap: $spacing-sm;
|
gap: $spacing-sm;
|
||||||
}
|
}
|
||||||
@@ -1602,16 +1559,21 @@ input[type="checkbox"]:checked + .custom-checkbox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Result Page (Step 3)
|
// Result Page (Step 3) - extends common section box
|
||||||
.register-result {
|
.register-result {
|
||||||
|
@extend .apikey-section-box;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
height: 1045px;
|
||||||
|
padding-top: 262px;
|
||||||
|
|
||||||
&.success {
|
&.success {
|
||||||
.result-icon {
|
.result-icon {
|
||||||
background: rgba($accent-green, 0.1);
|
|
||||||
color: $accent-green;
|
img {
|
||||||
|
width: 250px;
|
||||||
|
height: 250px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1626,9 +1588,6 @@ input[type="checkbox"]:checked + .custom-checkbox {
|
|||||||
margin-bottom: $spacing-xl;
|
margin-bottom: $spacing-xl;
|
||||||
|
|
||||||
.result-icon {
|
.result-icon {
|
||||||
width: 80px;
|
|
||||||
height: 80px;
|
|
||||||
border-radius: $border-radius-circle;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -1641,16 +1600,15 @@ input[type="checkbox"]:checked + .custom-checkbox {
|
|||||||
margin-bottom: $spacing-2xl;
|
margin-bottom: $spacing-2xl;
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: $font-size-2xl;
|
font-size: $font-size-xl;
|
||||||
font-weight: $font-weight-bold;
|
font-weight: $font-weight-bold;
|
||||||
color: $text-dark;
|
color: $primary-blue;
|
||||||
margin-bottom: $spacing-md;
|
margin-bottom: $spacing-md;
|
||||||
}
|
}
|
||||||
|
|
||||||
.result-description {
|
.result-description {
|
||||||
font-size: $font-size-base;
|
font-size: $font-size-base;
|
||||||
color: $text-gray;
|
color: $text-gray;
|
||||||
max-width: 500px;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -241,21 +241,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Animation for form elements
|
|
||||||
@keyframes fadeInUp {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(20px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.partnership-card {
|
|
||||||
animation: fadeInUp 0.6s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Focus states for better accessibility
|
// Focus states for better accessibility
|
||||||
.partnership-card {
|
.partnership-card {
|
||||||
|
|||||||
@@ -126,273 +126,6 @@
|
|||||||
</th:block>
|
</th:block>
|
||||||
|
|
||||||
<th:block layout:fragment="contentScript">
|
<th:block layout:fragment="contentScript">
|
||||||
<style>
|
|
||||||
/* App Management Container */
|
|
||||||
.app-management-container {
|
|
||||||
max-width: 1228px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 60px 20px 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* App List Title Section */
|
|
||||||
.app-list-title-section {
|
|
||||||
background-color: #f6f9fb;
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 14px 45px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-list-title {
|
|
||||||
font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
|
||||||
font-size: 28px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #212529;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* App List Container */
|
|
||||||
.app-list-container {
|
|
||||||
background-color: #f6f9fb;
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 30px 45px;
|
|
||||||
min-height: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* App List Item */
|
|
||||||
.app-list-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 20px;
|
|
||||||
padding: 40px 0;
|
|
||||||
border-bottom: 1px solid #e0e0e0;
|
|
||||||
text-decoration: none;
|
|
||||||
color: inherit;
|
|
||||||
transition: background-color 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-list-item:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-list-item:hover {
|
|
||||||
background-color: rgba(255, 255, 255, 0.5);
|
|
||||||
margin: 0 -20px;
|
|
||||||
padding-left: 20px;
|
|
||||||
padding-right: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* App Icon */
|
|
||||||
.app-list-icon {
|
|
||||||
width: 70px;
|
|
||||||
height: 70px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-list-icon .app-icon-image {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-list-icon .app-icon-placeholder {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background-color: #e9ecef;
|
|
||||||
border-radius: 12px;
|
|
||||||
color: #868e96;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* App Info */
|
|
||||||
.app-list-info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 14px;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-list-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Status Badge */
|
|
||||||
.app-status-badge {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 5px 14px;
|
|
||||||
border-radius: 40px;
|
|
||||||
font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #ffffff;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-status-badge.status-pending {
|
|
||||||
background-color: #8c959f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-status-badge.status-approved {
|
|
||||||
background-color: #0049b4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-status-badge.status-changing {
|
|
||||||
background-color: #88a5f3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-status-badge.status-inactive {
|
|
||||||
background-color: #dc3545;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* App Name */
|
|
||||||
.app-list-name {
|
|
||||||
font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #212529;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* App Description */
|
|
||||||
.app-list-description {
|
|
||||||
font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 400;
|
|
||||||
color: #6e7780;
|
|
||||||
margin: 0;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Empty State */
|
|
||||||
.app-list-empty {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 80px 20px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-list-empty .empty-icon {
|
|
||||||
font-size: 64px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-list-empty h3 {
|
|
||||||
font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #212529;
|
|
||||||
margin: 0 0 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-list-empty p {
|
|
||||||
font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
|
||||||
font-size: 16px;
|
|
||||||
color: #6e7780;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Create App Button Wrapper */
|
|
||||||
.app-create-button-wrapper {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Create App Button */
|
|
||||||
.btn-app-create {
|
|
||||||
width: 220px;
|
|
||||||
height: 60px;
|
|
||||||
background-color: #0049b4;
|
|
||||||
color: #ffffff;
|
|
||||||
border: none;
|
|
||||||
border-radius: 12px;
|
|
||||||
font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 700;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.2s ease, transform 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-app-create:hover {
|
|
||||||
background-color: #003a91;
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-app-create:active {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive */
|
|
||||||
@media screen and (max-width: 768px) {
|
|
||||||
.app-management-container {
|
|
||||||
padding: 40px 16px 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-list-title-section {
|
|
||||||
padding: 12px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-list-title {
|
|
||||||
font-size: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-list-container {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-list-item {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
padding: 30px 0;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-list-item:hover {
|
|
||||||
margin: 0 -10px;
|
|
||||||
padding-left: 10px;
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-list-icon {
|
|
||||||
width: 60px;
|
|
||||||
height: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-list-header {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-list-name {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-list-description {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-create-button-wrapper {
|
|
||||||
margin-top: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-app-create {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 300px;
|
|
||||||
height: 54px;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script th:inline="javascript">
|
<script th:inline="javascript">
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
||||||
|
|||||||
@@ -2,206 +2,237 @@
|
|||||||
<html xmlns:th="http://www.thymeleaf.org"
|
<html xmlns:th="http://www.thymeleaf.org"
|
||||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||||
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
|
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
|
||||||
layout:decorate="~{layout/kjbank_base_layout}">
|
layout:decorate="~{layout/kjbank_title_layout}">
|
||||||
<head>
|
<head>
|
||||||
<title>API 키 신청 상세</title>
|
<title>API 키 신청 상세</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<th:block layout:fragment="contentFragment">
|
<section layout:fragment="title">
|
||||||
|
<div class="page-title-banner">
|
||||||
<!-- Header -->
|
<img th:src="@{/img/img_title_bg.png}" class="title-image">
|
||||||
<div class="register-header">
|
<h1>앱관리</h1>
|
||||||
<h1>API Key 신청 상세</h1>
|
|
||||||
<p class="header-description">
|
|
||||||
신청하신 API Key의 상세 정보를 확인할 수 있습니다.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
<th:block layout:fragment="contentFragment">
|
||||||
|
<section class="apikey-detail-container">
|
||||||
|
|
||||||
<section class="apikey-detail-container">
|
<!-- Title Bar -->
|
||||||
|
<div class="register-title-bar">
|
||||||
|
<h2 class="register-title">API Key 신청 상세</h2>
|
||||||
|
<span class="register-subtitle">신청하신 API Key의 상세 정보를 확인할 수 있습니다.</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Section: 기본 정보 (From Step 1) -->
|
<!-- App Basic Info Card (Figma Design) -->
|
||||||
<div class="detail-section">
|
<div class="app-info-card">
|
||||||
<h2 class="section-title">기본 정보</h2>
|
<!-- App Icon -->
|
||||||
<div class="detail-grid">
|
<div class="app-info-icon">
|
||||||
|
<img th:if="${appRequest.appIconFileId != null}"
|
||||||
<!-- App Icon -->
|
th:src="@{/file/download(fileSn=1,fileId=${appRequest.appIconFileId})}"
|
||||||
<div class="detail-item full-width">
|
alt="앱 아이콘"
|
||||||
<label class="detail-label">앱 아이콘</label>
|
class="app-icon-image">
|
||||||
<div class="detail-value">
|
<div th:unless="${appRequest.appIconFileId != null}" class="app-icon-placeholder">
|
||||||
<div class="app-icon-display" th:if="${appRequest.appIconFileId != null}">
|
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||||
<img th:src="@{/file/download(fileSn=1,fileId=${appRequest.appIconFileId})}"
|
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
||||||
alt="앱 아이콘"
|
<circle cx="8.5" cy="8.5" r="1.5"></circle>
|
||||||
class="app-icon-image">
|
<polyline points="21 15 16 10 5 21"></polyline>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="app-icon-display" th:unless="${appRequest.appIconFileId != null}">
|
<!-- App Info -->
|
||||||
<div class="app-icon-placeholder">
|
<div class="app-info-content">
|
||||||
<span>📱</span>
|
<!-- Status Badge -->
|
||||||
</div>
|
<span class="app-status-badge"
|
||||||
|
th:if="${appRequest.approval != null}"
|
||||||
|
th:classappend="${appRequest.approval.approvalStatus.toString() == 'APPROVED' ? 'status-approved' :
|
||||||
|
(appRequest.approval.approvalStatus.toString() == 'REJECTED' ? 'status-rejected' : 'status-pending')}"
|
||||||
|
th:text="${appRequest.approval.approvalStatus.description}">
|
||||||
|
대기중
|
||||||
|
</span>
|
||||||
|
<!-- App Name -->
|
||||||
|
<h3 class="app-info-name" th:text="${appRequest.clientName}">앱 이름</h3>
|
||||||
|
<!-- App Description -->
|
||||||
|
<p class="app-info-description"
|
||||||
|
th:text="${appRequest.appDescription != null ? appRequest.appDescription : '앱 설명이 없습니다.'}">
|
||||||
|
여기에 입력한 앱 설명이 표시됩니다.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- App Name -->
|
<!-- Request Info Section (Figma Design: node 985-1365) -->
|
||||||
<div class="detail-item">
|
<div class="apikey-info-section">
|
||||||
<label class="detail-label">앱 이름</label>
|
|
||||||
<div class="detail-value" th:text="${appRequest.clientName}">
|
|
||||||
My Application
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Client ID (if exists) -->
|
<!-- Client ID (if exists) -->
|
||||||
<div class="detail-item" th:if="${appRequest.clientId != null}">
|
<div class="info-row info-row-with-action" th:if="${appRequest.clientId != null}">
|
||||||
<label class="detail-label">Client ID</label>
|
<label class="info-label">Client ID</label>
|
||||||
<div class="detail-value" th:text="${appRequest.clientId}">
|
<div class="info-value">
|
||||||
client-id-12345
|
<div class="info-value-box with-copy" th:text="${appRequest.clientId}">K00000001</div>
|
||||||
</div>
|
<button type="button" class="btn-copy-action" th:data-secret="${appRequest.clientId}" onclick="copyToClipboardFromButton(this)">
|
||||||
</div>
|
+ 복사
|
||||||
|
</button>
|
||||||
<!-- App Description -->
|
</div>
|
||||||
<div class="detail-item full-width">
|
|
||||||
<label class="detail-label">앱 설명</label>
|
|
||||||
<div class="detail-value" th:text="${appRequest.appDescription}">
|
|
||||||
This is a sample app description.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Callback URL -->
|
|
||||||
<div class="detail-item full-width">
|
|
||||||
<label class="detail-label">Callback URL</label>
|
|
||||||
<div class="detail-value" th:text="${appRequest.callbackUrl}">
|
|
||||||
https://example.com/callback
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- IP Whitelist -->
|
|
||||||
<div class="detail-item full-width" th:if="${appRequest.ipWhitelist != null and !appRequest.ipWhitelist.isEmpty()}">
|
|
||||||
<label class="detail-label">IP 화이트리스트</label>
|
|
||||||
<div class="detail-value">
|
|
||||||
<div class="ip-list">
|
|
||||||
<span class="ip-tag" th:each="ip : ${#strings.arraySplit(appRequest.ipWhitelist, ',')}">
|
|
||||||
[[${ip.trim()}]]
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<!-- Callback URL -->
|
||||||
|
<div class="info-row">
|
||||||
|
<label class="info-label">Callback URL</label>
|
||||||
|
<div class="info-value">
|
||||||
|
<div class="info-value-box" th:text="${appRequest.callbackUrl != null ? appRequest.callbackUrl : '설정되지 않음'}">
|
||||||
|
http://www.abcd.co.kr
|
||||||
|
</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="${appRequest.ipWhitelist != null and !appRequest.ipWhitelist.isEmpty() ? appRequest.ipWhitelist : '설정되지 않음'}">
|
||||||
|
10.0.0.1, 10.0.0.2, 192.168.123.1
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Selected 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-figma" th:each="api : ${appRequest.apiSpecList}">
|
||||||
|
<span class="api-item-name" th:text="${api.apiName}">추가된 API 1</span>
|
||||||
|
<span class="api-item-status">승인대기</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Empty State -->
|
||||||
|
<div class="empty-state" th:if="${appRequest.apiSpecList == null or appRequest.apiSpecList.isEmpty()}" style="padding: 24px;">
|
||||||
|
<p>선택된 API가 없습니다.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Created Date -->
|
||||||
|
<div class="info-row">
|
||||||
|
<label class="info-label">신청일시</label>
|
||||||
|
<div class="info-value">
|
||||||
|
<div class="info-value-box" th:text="${#temporals.format(appRequest.createdDate, 'yyyy.MM.dd HH:mm')}">
|
||||||
|
2025.11.22 12:00
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Approval Date (if approved) -->
|
||||||
|
<div class="info-row" th:if="${appRequest.approval != null and appRequest.approval.approvalDate != null}">
|
||||||
|
<label class="info-label">승인일시</label>
|
||||||
|
<div class="info-value">
|
||||||
|
<div class="info-value-box" th:text="${#temporals.format(appRequest.approval.approvalDate, 'yyyy.MM.dd HH:mm')}">
|
||||||
|
2025.11.22 12:00
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Reason (if exists) -->
|
||||||
|
<div class="info-row" th:if="${appRequest.approval != null and appRequest.reason != null and !appRequest.reason.isEmpty()}">
|
||||||
|
<label class="info-label">사유</label>
|
||||||
|
<div class="info-value">
|
||||||
|
<div class="info-value-box" th:text="${appRequest.reason}">
|
||||||
|
Approval reason
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Created Date -->
|
<!-- Action Buttons -->
|
||||||
<div class="detail-item">
|
<div class="form-actions-center">
|
||||||
<label class="detail-label">신청일시</label>
|
<button type="button"
|
||||||
<div class="detail-value" th:text="${#temporals.format(appRequest.createdDate, 'yyyy.MM.dd HH:mm')}">
|
sec:authorize="hasRole('ROLE_API_KEY_REQUEST')"
|
||||||
2024.01.01 12:00
|
class="btn btn-submit btn-danger"
|
||||||
</div>
|
th:if="${appRequest.approval != null and appRequest.approval.approvalStatus != null and
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="detail-item" th:if="${appRequest.approval != null}">
|
|
||||||
<label class="detail-label">승인 상태</label>
|
|
||||||
<div class="detail-value" th:text="${appRequest.approval.approvalStatus.description}">
|
|
||||||
승인 대기
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="detail-item" th:if="${appRequest.approval != null and appRequest.approval.approvalDate != null}">
|
|
||||||
<label class="detail-label">승인일시</label>
|
|
||||||
<div class="detail-value" th:text="${#temporals.format(appRequest.approval.approvalDate, 'yyyy.MM.dd HH:mm')}">
|
|
||||||
2024.01.01 12:00
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="detail-item full-width" th:if="${appRequest.approval != null and appRequest.reason != null and !appRequest.reason.isEmpty()}">
|
|
||||||
<label class="detail-label">사유</label>
|
|
||||||
<div class="detail-value" th:text="${appRequest.reason}">
|
|
||||||
Approval reason
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Section: API 정보 (From Step 2) -->
|
|
||||||
<div class="detail-section">
|
|
||||||
<h2 class="section-title">선택된 API</h2>
|
|
||||||
|
|
||||||
<!-- API List -->
|
|
||||||
<div class="api-detail-list">
|
|
||||||
<div class="api-list-item" th:each="api : ${appRequest.apiSpecList}">
|
|
||||||
<span class="api-method"
|
|
||||||
th:classappend="${api.apiMethod == 'GET' ? 'method-get' :
|
|
||||||
(api.apiMethod == 'POST' ? 'method-post' :
|
|
||||||
(api.apiMethod == 'PUT' ? 'method-put' :
|
|
||||||
(api.apiMethod == 'DELETE' ? 'method-delete' : 'method-other')))}">
|
|
||||||
[[${api.apiMethod}]]
|
|
||||||
</span>
|
|
||||||
<span class="api-name" th:text="${api.apiName}">API Name</span>
|
|
||||||
<span class="api-status status-pending">대기중</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Empty State -->
|
|
||||||
<div class="empty-state" th:if="${appRequest.apiSpecList == null or appRequest.apiSpecList.isEmpty()}">
|
|
||||||
<div class="empty-icon">📦</div>
|
|
||||||
<p>선택된 API가 없습니다.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Action Buttons -->
|
|
||||||
<div class="detail-actions">
|
|
||||||
<button type="button"
|
|
||||||
sec:authorize="hasRole('ROLE_API_KEY_REQUEST')"
|
|
||||||
class="btn btn-secondary"
|
|
||||||
th:if="${appRequest.approval != null and appRequest.approval.approvalStatus != null and
|
|
||||||
(appRequest.approval.approvalStatus.toString() == 'PENDING' or appRequest.approval.approvalStatus.toString() == 'REQUESTED')}"
|
(appRequest.approval.approvalStatus.toString() == 'PENDING' or appRequest.approval.approvalStatus.toString() == 'REQUESTED')}"
|
||||||
th:data-request-id="${appRequest.id}"
|
th:data-request-id="${appRequest.id}"
|
||||||
onclick="cancelRequestById(this)">
|
onclick="cancelRequestById(this)">
|
||||||
신청 취소
|
신청 취소
|
||||||
</button>
|
</button>
|
||||||
<a th:href="@{/myapikey}" class="btn btn-primary">
|
<a th:href="@{/myapikey}" class="btn btn-submit btn-secondary">
|
||||||
목록으로
|
목록
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</th:block>
|
</th:block>
|
||||||
|
|
||||||
<th:block layout:fragment="contentScript">
|
<th:block layout:fragment="contentScript">
|
||||||
<script th:inline="javascript">
|
<script th:inline="javascript">
|
||||||
/*<![CDATA[*/
|
/*<![CDATA[*/
|
||||||
|
|
||||||
// Cancel request function - called from button with data-request-id attribute
|
// Copy to clipboard function
|
||||||
function cancelRequestById(button) {
|
function copyToClipboardFromButton(button) {
|
||||||
var requestId = $(button).data('request-id');
|
var text = $(button).data('secret');
|
||||||
|
|
||||||
if (!confirm('정말로 이 신청을 취소하시겠습니까?')) {
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||||
return;
|
navigator.clipboard.writeText(text).then(function() {
|
||||||
}
|
alert('클립보드에 복사되었습니다.');
|
||||||
|
}).catch(function(err) {
|
||||||
$('.loading-overlay').show();
|
console.error('Failed to copy:', err);
|
||||||
|
fallbackCopy(text);
|
||||||
$.ajax({
|
});
|
||||||
url: '/myapikey/api_key_request/cancel',
|
} else {
|
||||||
type: 'POST',
|
fallbackCopy(text);
|
||||||
data: {id: requestId},
|
}
|
||||||
dataType: 'json',
|
|
||||||
headers: {
|
|
||||||
'X-XSRF-TOKEN': /*[[${_csrf.token}]]*/ 'token'
|
|
||||||
}
|
}
|
||||||
}).done(function(response) {
|
|
||||||
if (response.success) {
|
|
||||||
alert(response.message || '신청이 취소되었습니다.');
|
|
||||||
window.location.href = /*[[@{/myapikey}]]*/ '/myapikey';
|
|
||||||
} else {
|
|
||||||
alert(response.message || '신청 취소 중 오류가 발생했습니다.');
|
|
||||||
$('.loading-overlay').hide();
|
|
||||||
}
|
|
||||||
}).fail(function(xhr, status, error) {
|
|
||||||
alert('신청 취소 중 오류가 발생했습니다.');
|
|
||||||
console.error('Cancel error:', error);
|
|
||||||
$('.loading-overlay').hide();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/*]]>*/
|
function fallbackCopy(text) {
|
||||||
</script>
|
const textArea = document.createElement('textarea');
|
||||||
|
textArea.value = text;
|
||||||
|
textArea.style.position = 'fixed';
|
||||||
|
textArea.style.left = '-999999px';
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.select();
|
||||||
|
try {
|
||||||
|
document.execCommand('copy');
|
||||||
|
alert('클립보드에 복사되었습니다.');
|
||||||
|
} catch (err) {
|
||||||
|
alert('복사에 실패했습니다.');
|
||||||
|
}
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel request function - called from button with data-request-id attribute
|
||||||
|
function cancelRequestById(button) {
|
||||||
|
var requestId = $(button).data('request-id');
|
||||||
|
|
||||||
|
if (!confirm('정말로 이 신청을 취소하시겠습니까?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.loading-overlay').show();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/myapikey/api_key_request/cancel',
|
||||||
|
type: 'POST',
|
||||||
|
data: {id: requestId},
|
||||||
|
dataType: 'json',
|
||||||
|
headers: {
|
||||||
|
'X-XSRF-TOKEN': /*[[${_csrf.token}]]*/ 'token'
|
||||||
|
}
|
||||||
|
}).done(function(response) {
|
||||||
|
if (response.success) {
|
||||||
|
alert(response.message || '신청이 취소되었습니다.');
|
||||||
|
window.location.href = /*[[@{/myapikey}]]*/ '/myapikey';
|
||||||
|
} else {
|
||||||
|
alert(response.message || '신청 취소 중 오류가 발생했습니다.');
|
||||||
|
$('.loading-overlay').hide();
|
||||||
|
}
|
||||||
|
}).fail(function(xhr, status, error) {
|
||||||
|
alert('신청 취소 중 오류가 발생했습니다.');
|
||||||
|
console.error('Cancel error:', error);
|
||||||
|
$('.loading-overlay').hide();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*]]>*/
|
||||||
|
</script>
|
||||||
</th:block>
|
</th:block>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -2,313 +2,281 @@
|
|||||||
<html xmlns:th="http://www.thymeleaf.org"
|
<html xmlns:th="http://www.thymeleaf.org"
|
||||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||||
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
|
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
|
||||||
layout:decorate="~{layout/kjbank_base_layout}">
|
layout:decorate="~{layout/kjbank_title_layout}">
|
||||||
<head>
|
<head>
|
||||||
<title>API 키 상세</title>
|
<title>API 키 상세</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<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">
|
<th:block layout:fragment="contentFragment">
|
||||||
|
<section class="apikey-detail-container">
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Title Bar -->
|
||||||
<div class="register-header">
|
<div class="register-title-bar">
|
||||||
<h1>API Key 상세</h1>
|
<h2 class="register-title">앱 정보</h2>
|
||||||
<p class="header-description">
|
<span class="register-subtitle">등록된 앱의 상세 정보를 확인할 수 있습니다.</span>
|
||||||
발급된 API Key의 상세 정보를 확인할 수 있습니다.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="apikey-detail-container">
|
<!-- App Basic Info Card (Figma Design) -->
|
||||||
|
<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>
|
||||||
|
|
||||||
<!-- Section 1: 앱 기본 정보 -->
|
<!-- Credential Info Section (Figma Design: node 985-1365) -->
|
||||||
<div class="detail-section">
|
<div class="apikey-info-section">
|
||||||
<h2 class="section-title">앱 정보</h2>
|
|
||||||
<div class="detail-grid">
|
|
||||||
|
|
||||||
<!-- App Icon -->
|
<!-- Client ID -->
|
||||||
<div class="detail-item full-width">
|
<div class="info-row info-row-with-action">
|
||||||
<label class="detail-label">앱 아이콘</label>
|
<label class="info-label">Client ID</label>
|
||||||
<div class="detail-value">
|
<div class="info-value">
|
||||||
<div class="app-icon-display" th:if="${apiKey.appIconFileId != null}">
|
<div class="info-value-box with-copy" th:text="${apiKey.clientid}">K00000001</div>
|
||||||
<img th:src="@{/file/download(fileSn=1,fileId=${apiKey.appIconFileId})}"
|
<button type="button" class="btn-copy-action" th:data-secret="${apiKey.clientid}" onclick="copyToClipboardFromButton(this)">
|
||||||
alt="앱 아이콘"
|
+ 복사
|
||||||
class="app-icon-image">
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="app-icon-display" th:unless="${apiKey.appIconFileId != null}">
|
</div>
|
||||||
<div class="app-icon-placeholder">
|
|
||||||
<span>📱</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- App Name -->
|
|
||||||
<div class="detail-item">
|
|
||||||
<label class="detail-label">앱 이름</label>
|
|
||||||
<div class="detail-value" th:text="${apiKey.clientname}">
|
|
||||||
My Application
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- App Status -->
|
|
||||||
<div class="detail-item">
|
|
||||||
<label class="detail-label">앱 상태</label>
|
|
||||||
<div class="detail-value">
|
|
||||||
<span class="status-indicator"
|
|
||||||
th:classappend="${apiKey.appstatus == '1' ? 'status-active' : 'status-inactive'}">
|
|
||||||
<span class="status-dot"></span>
|
|
||||||
<span th:text="${apiKey.appstatus == '1' ? '활성화' : '비활성화'}">활성화</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- App Description -->
|
|
||||||
<div class="detail-item full-width">
|
|
||||||
<label class="detail-label">앱 설명</label>
|
|
||||||
<div class="detail-value" th:text="${apiKey.appDescription != null ? apiKey.appDescription : '설명이 없습니다.'}">
|
|
||||||
This is a sample app description.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<!-- Client Secret -->
|
||||||
|
<div class="info-row">
|
||||||
|
<label class="info-label">Client Secret</label>
|
||||||
|
<div class="info-value">
|
||||||
|
<!-- 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) -->
|
||||||
|
<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" th:text="${apiKey.clientsecret}">secret-key-67890</div>
|
||||||
|
<button type="button" class="btn-copy-action" th:data-secret="${apiKey.clientsecret}" onclick="copyToClipboardFromButton(this)">
|
||||||
|
+ 복사
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Section 2: 인증 상세 정보 -->
|
<!-- Callback URL -->
|
||||||
<div class="detail-section">
|
<div class="info-row">
|
||||||
<h2 class="section-title">인증 정보</h2>
|
<label class="info-label">Callback URL</label>
|
||||||
<div class="detail-grid">
|
<div class="info-value">
|
||||||
|
<div class="info-value-box" th:text="${apiKey.redirecturi != null ? apiKey.redirecturi : '설정되지 않음'}">
|
||||||
|
http://www.abcd.co.kr
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Client ID -->
|
<!-- IP Whitelist -->
|
||||||
<div class="detail-item">
|
<div class="info-row">
|
||||||
<label class="detail-label">Client ID</label>
|
<label class="info-label">화이트리스트</label>
|
||||||
<div class="detail-value">
|
<div class="info-value">
|
||||||
<div class="secret-box">
|
<div class="info-value-box" th:text="${apiKey.allowedips != null ? apiKey.allowedips : '설정되지 않음'}">
|
||||||
<code class="credential-code" th:text="${apiKey.clientid}">client-id-12345</code>
|
10.0.0.1, 10.0.0.2, 192.168.123.1
|
||||||
<button type="button" class="btn-copy" th:data-secret="${apiKey.clientid}" onclick="copyToClipboardFromButton(this)">
|
</div>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
</div>
|
||||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
</div>
|
||||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
|
||||||
</svg>
|
|
||||||
복사
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Client Secret -->
|
|
||||||
<div class="detail-item">
|
|
||||||
<label class="detail-label">Client Secret</label>
|
|
||||||
<div class="detail-value">
|
|
||||||
<!-- Hidden Secret (shown initially) -->
|
|
||||||
<div class="secret-box" id="hiddenSecretBox">
|
|
||||||
<code class="credential-code">••••••••••••••••••••</code>
|
|
||||||
<button type="button" class="btn-copy" onclick="showPasswordPrompt()">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
|
|
||||||
<circle cx="12" cy="12" r="3"></circle>
|
|
||||||
</svg>
|
|
||||||
보기
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<!-- Actual Secret (hidden initially) -->
|
|
||||||
<div class="secret-box" id="revealedSecretBox" style="display: none;">
|
|
||||||
<code class="credential-code" th:text="${apiKey.clientsecret}">secret-key-67890</code>
|
|
||||||
<button type="button" class="btn-copy" th:data-secret="${apiKey.clientsecret}" onclick="copyToClipboardFromButton(this)">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<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>
|
|
||||||
복사
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Callback URL -->
|
|
||||||
<div class="detail-item full-width">
|
|
||||||
<label class="detail-label">Callback URL</label>
|
|
||||||
<div class="detail-value" th:text="${apiKey.redirecturi != null ? apiKey.redirecturi : '설정되지 않음'}">
|
|
||||||
https://example.com/callback
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- IP Whitelist -->
|
|
||||||
<div class="detail-item full-width" th:if="${apiKey.allowedips != null}">
|
|
||||||
<label class="detail-label">IP 화이트리스트</label>
|
|
||||||
<div class="detail-value">
|
|
||||||
<div class="ip-list">
|
|
||||||
<span class="ip-tag" th:each="ip : ${#strings.arraySplit(apiKey.allowedips, ',')}">
|
|
||||||
[[${ip.trim()}]]
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 등록된 API -->
|
|
||||||
<div class="detail-item full-width">
|
|
||||||
<label class="detail-label">등록된 API</label>
|
|
||||||
<div class="detail-value">
|
|
||||||
<!-- API List -->
|
|
||||||
<div class="api-detail-list">
|
|
||||||
<div class="api-list-item" th:each="api : ${apiKey.apiList}">
|
|
||||||
<span class="api-method method-other">
|
|
||||||
API
|
|
||||||
</span>
|
|
||||||
<span class="api-name" th:text="${api.apiDesc}">API Name</span>
|
|
||||||
<span class="api-status status-active">활성</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Empty State -->
|
|
||||||
<div class="empty-state" th:if="${apiKey.apiList == null or apiKey.apiList.isEmpty()}">
|
|
||||||
<div class="empty-icon">📦</div>
|
|
||||||
<p>등록된 API가 없습니다.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Modified Date -->
|
|
||||||
<div class="detail-item">
|
|
||||||
<label class="detail-label">최종 수정일자</label>
|
|
||||||
<div class="detail-value" th:text="${#temporals.format(apiKey.modifiedon, 'yyyy.MM.dd HH:mm')}">
|
|
||||||
2024.01.01 12:00
|
|
||||||
</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-figma" 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>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Action Buttons -->
|
<!-- Empty State -->
|
||||||
<div class="form-actions">
|
<div class="empty-state" th:if="${apiKey.apiList == null or apiKey.apiList.isEmpty()}" style="padding: 24px;">
|
||||||
<a sec:authorize="hasRole('ROLE_API_KEY_REQUEST')"
|
<p>등록된 API가 없습니다.</p>
|
||||||
class="btn btn-primary"
|
</div>
|
||||||
th:href="@{/myapikey/modify/step1(clientId=${apiKey.clientid})}">
|
</div>
|
||||||
수정
|
|
||||||
</a>
|
|
||||||
<button type="button"
|
|
||||||
sec:authorize="hasRole('ROLE_API_KEY_REQUEST')"
|
|
||||||
class="btn btn-danger"
|
|
||||||
th:data-client-id="${apiKey.clientid}"
|
|
||||||
onclick="deleteApiKeyFromButton(this)">
|
|
||||||
인증키 삭제
|
|
||||||
</button>
|
|
||||||
<a th:href="@{/myapikey}" class="btn btn-secondary">
|
|
||||||
목록으로
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</section>
|
<!-- 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>
|
||||||
|
|
||||||
|
<!-- 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"
|
||||||
|
th:href="@{/myapikey/modify/step1(clientId=${apiKey.clientid})}">
|
||||||
|
수정
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
</th:block>
|
</th:block>
|
||||||
|
|
||||||
<th:block layout:fragment="contentScript">
|
<th:block layout:fragment="contentScript">
|
||||||
<script th:inline="javascript">
|
<script th:inline="javascript">
|
||||||
/*<![CDATA[*/
|
/*<![CDATA[*/
|
||||||
|
|
||||||
// Show password prompt for viewing client secret
|
// Show password prompt for viewing client secret
|
||||||
function showPasswordPrompt() {
|
function showPasswordPrompt() {
|
||||||
customPopups.showPasswordInput({
|
customPopups.showPasswordInput({
|
||||||
title: 'Client Secret 조회',
|
title: 'Client Secret 조회',
|
||||||
message: '보안을 위해 비밀번호를 입력해주세요.',
|
message: '보안을 위해 비밀번호를 입력해주세요.',
|
||||||
onConfirm: function(password) {
|
onConfirm: function(password) {
|
||||||
// Verify password via AJAX
|
// Verify password via AJAX
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: /*[[@{/myapikey/verify-password}]]*/ '/myapikey/verify-password',
|
url: /*[[@{/myapikey/verify-password}]]*/ '/myapikey/verify-password',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: { password: password },
|
data: {password: password},
|
||||||
headers: {
|
headers: {
|
||||||
'X-XSRF-TOKEN': /*[[${_csrf.token}]]*/ 'token'
|
'X-XSRF-TOKEN': /*[[${_csrf.token}]]*/ 'token'
|
||||||
},
|
},
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
// Hide password popup
|
// Hide password popup
|
||||||
customPopups.hidePasswordInput();
|
customPopups.hidePasswordInput();
|
||||||
|
|
||||||
// Reveal the client secret
|
// Reveal the client secret
|
||||||
$('#hiddenSecretBox').hide();
|
$('#hiddenSecretBox').hide();
|
||||||
$('#revealedSecretBox').fadeIn(300);
|
$('#revealedSecretBox').fadeIn(300);
|
||||||
} else {
|
} else {
|
||||||
// Show error message
|
// Show error message
|
||||||
customPopups.showPasswordError(response.message || '비밀번호가 일치하지 않습니다.');
|
customPopups.showPasswordError(response.message || '비밀번호가 일치하지 않습니다.');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function() {
|
error: function() {
|
||||||
customPopups.showPasswordError('오류가 발생했습니다. 다시 시도해주세요.');
|
customPopups.showPasswordError('오류가 발생했습니다. 다시 시도해주세요.');
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onCancel: function() {
|
|
||||||
// User cancelled - do nothing
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy to clipboard function - called from button with data-secret attribute
|
|
||||||
function copyToClipboardFromButton(button) {
|
|
||||||
var text = $(button).data('secret');
|
|
||||||
|
|
||||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
||||||
navigator.clipboard.writeText(text).then(function() {
|
|
||||||
alert('클립보드에 복사되었습니다.');
|
|
||||||
}).catch(function(err) {
|
|
||||||
console.error('Failed to copy:', err);
|
|
||||||
fallbackCopy(text);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
fallbackCopy(text);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onCancel: function() {
|
||||||
|
// User cancelled - do nothing
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function fallbackCopy(text) {
|
// Copy to clipboard function - called from button with data-secret attribute
|
||||||
const textArea = document.createElement('textarea');
|
function copyToClipboardFromButton(button) {
|
||||||
textArea.value = text;
|
var text = $(button).data('secret');
|
||||||
textArea.style.position = 'fixed';
|
|
||||||
textArea.style.left = '-999999px';
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||||
document.body.appendChild(textArea);
|
navigator.clipboard.writeText(text).then(function() {
|
||||||
textArea.select();
|
alert('클립보드에 복사되었습니다.');
|
||||||
try {
|
}).catch(function(err) {
|
||||||
document.execCommand('copy');
|
console.error('Failed to copy:', err);
|
||||||
alert('클립보드에 복사되었습니다.');
|
fallbackCopy(text);
|
||||||
} catch (err) {
|
});
|
||||||
alert('복사에 실패했습니다.');
|
} else {
|
||||||
}
|
fallbackCopy(text);
|
||||||
document.body.removeChild(textArea);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fallbackCopy(text) {
|
||||||
|
const textArea = document.createElement('textarea');
|
||||||
|
textArea.value = text;
|
||||||
|
textArea.style.position = 'fixed';
|
||||||
|
textArea.style.left = '-999999px';
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.select();
|
||||||
|
try {
|
||||||
|
document.execCommand('copy');
|
||||||
|
alert('클립보드에 복사되었습니다.');
|
||||||
|
} catch (err) {
|
||||||
|
alert('복사에 실패했습니다.');
|
||||||
|
}
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete API Key function - called from button with data-client-id attribute
|
||||||
|
function deleteApiKeyFromButton(button) {
|
||||||
|
var clientId = $(button).data('client-id');
|
||||||
|
|
||||||
|
if (!confirm('정말로 이 인증키를 삭제하시겠습니까?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.loading-overlay').show();
|
||||||
|
|
||||||
|
const requestData = {
|
||||||
|
clientId: clientId
|
||||||
|
};
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/myapikey/api_key_delete',
|
||||||
|
type: 'POST',
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify(requestData),
|
||||||
|
headers: {
|
||||||
|
'X-XSRF-TOKEN': /*[[${_csrf.token}]]*/ 'token'
|
||||||
}
|
}
|
||||||
|
}).done(function(response) {
|
||||||
|
alert(response.msg || 'API Key가 삭제되었습니다.');
|
||||||
|
window.location.href = /*[[@{/myapikey}]]*/ '/myapikey';
|
||||||
|
}).fail(function(jqXHR, textStatus, errorThrown) {
|
||||||
|
alert('API 삭제 요청 중 오류가 발생했습니다: ' + errorThrown);
|
||||||
|
}).always(function() {
|
||||||
|
$('.loading-overlay').hide();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Delete API Key function - called from button with data-client-id attribute
|
/*]]>*/
|
||||||
function deleteApiKeyFromButton(button) {
|
</script>
|
||||||
var clientId = $(button).data('client-id');
|
|
||||||
|
|
||||||
if (!confirm('정말로 이 인증키를 삭제하시겠습니까?')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$('.loading-overlay').show();
|
|
||||||
|
|
||||||
const requestData = {
|
|
||||||
clientId: clientId
|
|
||||||
};
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: '/myapikey/api_key_delete',
|
|
||||||
type: 'POST',
|
|
||||||
contentType: 'application/json',
|
|
||||||
data: JSON.stringify(requestData),
|
|
||||||
headers: {
|
|
||||||
'X-XSRF-TOKEN': /*[[${_csrf.token}]]*/ 'token'
|
|
||||||
}
|
|
||||||
}).done(function (response) {
|
|
||||||
alert(response.msg || 'API Key가 삭제되었습니다.');
|
|
||||||
window.location.href = /*[[@{/myapikey}]]*/ '/myapikey';
|
|
||||||
}).fail(function (jqXHR, textStatus, errorThrown) {
|
|
||||||
alert('API 삭제 요청 중 오류가 발생했습니다: ' + errorThrown);
|
|
||||||
}).always(function () {
|
|
||||||
$('.loading-overlay').hide();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*]]>*/
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</th:block>
|
</th:block>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,278 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org"
|
||||||
|
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
|
||||||
|
<body>
|
||||||
|
<th:block th:fragment="headerFragment">
|
||||||
|
<!-- Global Header Container -->
|
||||||
|
<header class="global-header" style="background-color: #E5F8FF;">
|
||||||
|
<div class="container">
|
||||||
|
<div class="header-content">
|
||||||
|
|
||||||
|
<!-- Desktop Header Layout -->
|
||||||
|
<div class="desktop-header">
|
||||||
|
<div class="header-left">
|
||||||
|
<div class="logo-wrapper">
|
||||||
|
<a th:href="@{/}" class="logo-link">
|
||||||
|
<img src="/img/logo/logo.png" alt="광주은행" class="logo" width="114" height="22">
|
||||||
|
</a>
|
||||||
|
<a th:href="@{/}" class="logo-text">API Portal</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav class="header-center">
|
||||||
|
<ul class="nav-menu">
|
||||||
|
<li><a href="#docs" class="nav-link">서비스 소개</a></li>
|
||||||
|
<li><a href="/apis" class="nav-link">오픈 API</a></li>
|
||||||
|
<li>
|
||||||
|
<a href="#playground" class="nav-link">고객지원</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&A</a></li>
|
||||||
|
<li><a th:href="@{/partnership}">사업 제휴 문의</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="header-right">
|
||||||
|
|
||||||
|
<!-- Login State (Anonymous) -->
|
||||||
|
<div class="auth-group" sec:authorize="isAnonymous()">
|
||||||
|
|
||||||
|
<a th:href="@{/login}" class="login-btn login-btn-box">
|
||||||
|
<img th:src="@{/img/user_icon.svg}" alt="User" class="user-icon">로그인</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Logout State (Authenticated) -->
|
||||||
|
<div class="auth-group authenticated" sec:authorize="isAuthenticated()">
|
||||||
|
<div class="header-user-info">
|
||||||
|
<div class="user-identity">
|
||||||
|
<img th:src="@{/img/user_icon.svg}" alt="User" class="user-icon">
|
||||||
|
<span class="user-name">[[${#authentication.principal.userName}]]님</span>
|
||||||
|
</div>
|
||||||
|
<span class="divider">•</span>
|
||||||
|
<a th:href="@{/actionLogout.do}" class="header-link">로그아웃</a>
|
||||||
|
<span class="divider">•</span>
|
||||||
|
<div class="mypage-dropdown">
|
||||||
|
<button class="header-link mypage-toggle" type="button">마이페이지</button>
|
||||||
|
<div class="mypage-dropdown-menu">
|
||||||
|
<ul class="mypage-menu-list">
|
||||||
|
<li sec:authorize="hasRole('ROLE_USER_MANAGER')">
|
||||||
|
<a th:href="@{/users}"><i class="fas fa-users"></i>이용자 관리</a>
|
||||||
|
</li>
|
||||||
|
<li sec:authorize="hasRole('ROLE_APP')">
|
||||||
|
<a th:href="@{/myapikey}"><i class="fas fa-key"></i>인증 키 관리</a>
|
||||||
|
<a th:href="@{/commission/manage}"><i class="fas fa-money-bill"></i>과금 관리</a>
|
||||||
|
</li>
|
||||||
|
<li><a th:href="@{/mypage}"><i class="fas fa-user-circle"></i>내 정보 관리</a></li>
|
||||||
|
<li><a th:href="@{/change_password}"><i class="fas fa-lock"></i>비밀번호 변경</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile Header Layout -->
|
||||||
|
<div class="mobile-header">
|
||||||
|
<div class="mobile-left">
|
||||||
|
<a th:href="@{/}" class="mobile-logo-link">
|
||||||
|
<img src="/img/logo/logo.png" alt="광주은행" class="mobile-logo">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mobile-center">
|
||||||
|
<h1 class="mobile-title">개발자 포털</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mobile-right">
|
||||||
|
<button class="mobile-menu-btn" id="mobileMenuToggle">
|
||||||
|
<span class="hamburger-line"></span>
|
||||||
|
<span class="hamburger-line"></span>
|
||||||
|
<span class="hamburger-line"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Mobile Drawer/Modal -->
|
||||||
|
<div class="mobile-drawer" id="mobileDrawer">
|
||||||
|
<div class="drawer-overlay" id="drawerOverlay"></div>
|
||||||
|
<div class="drawer-content">
|
||||||
|
<div class="drawer-header">
|
||||||
|
<div class="drawer-logo">
|
||||||
|
<a th:href="@{/}" class="drawer-logo-link">
|
||||||
|
<img src="/img/logo/logo.png" alt="광주은행" class="logo">
|
||||||
|
</a>
|
||||||
|
<a th:href="@{/}" class="logo-text">개발자 포털</a>
|
||||||
|
</div>
|
||||||
|
<button class="drawer-close" id="drawerClose">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Authenticated User Info (Mobile) -->
|
||||||
|
<div class="drawer-user-info" sec:authorize="isAuthenticated()">
|
||||||
|
<div class="drawer-profile">
|
||||||
|
<div class="profile-avatar">
|
||||||
|
<i class="fas fa-user-circle"></i>
|
||||||
|
</div>
|
||||||
|
<div class="profile-info">
|
||||||
|
<p class="profile-name">[[${#authentication.principal.userName}]]님</p>
|
||||||
|
<span class="profile-greeting">즐거운 하루 되세요!</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav class="drawer-nav">
|
||||||
|
<ul class="drawer-menu">
|
||||||
|
<li><a href="#docs" class="drawer-link">서비스 소개</a></li>
|
||||||
|
<li><a href="/apis" class="drawer-link">오픈 API</a></li>
|
||||||
|
<li><a href="#playground" class="drawer-link">고객지원</a></li>
|
||||||
|
|
||||||
|
<!-- Authenticated Menu Items -->
|
||||||
|
<li sec:authorize="isAuthenticated()">
|
||||||
|
<a href="#community" class="drawer-link">커뮤니티</a>
|
||||||
|
</li>
|
||||||
|
<li sec:authorize="hasRole('ROLE_USER_MANAGER')">
|
||||||
|
<a th:href="@{/users}" class="drawer-link">
|
||||||
|
<i class="fas fa-users"></i> 이용자 관리
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li sec:authorize="hasRole('ROLE_APP')">
|
||||||
|
<a th:href="@{/myapikey}" class="drawer-link">
|
||||||
|
<i class="fas fa-key"></i> 인증 키 관리
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li sec:authorize="isAuthenticated()">
|
||||||
|
<a th:href="@{/mypage}" class="drawer-link">
|
||||||
|
<i class="fas fa-user-circle"></i> 내 정보 관리
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li sec:authorize="isAuthenticated()">
|
||||||
|
<a th:href="@{/change_password}" class="drawer-link">
|
||||||
|
<i class="fas fa-lock"></i> 비밀번호 변경
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="drawer-actions">
|
||||||
|
<!-- Anonymous User Actions -->
|
||||||
|
<div sec:authorize="isAnonymous()">
|
||||||
|
<a th:href="@{/login}" class="drawer-login-btn">로그인</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Authenticated User Actions -->
|
||||||
|
<div sec:authorize="isAuthenticated()">
|
||||||
|
<a th:href="@{/actionLogout.do}" class="drawer-logout-btn">
|
||||||
|
<i class="fas fa-sign-out-alt"></i>
|
||||||
|
로그아웃
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th:block>
|
||||||
|
|
||||||
|
<!-- Mobile Menu & Dropdown JavaScript -->
|
||||||
|
<th:block th:fragment="headerScript">
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Mobile Drawer
|
||||||
|
const menuToggle = document.getElementById('mobileMenuToggle');
|
||||||
|
const drawer = document.getElementById('mobileDrawer');
|
||||||
|
const drawerClose = document.getElementById('drawerClose');
|
||||||
|
const drawerOverlay = document.getElementById('drawerOverlay');
|
||||||
|
|
||||||
|
function openDrawer() {
|
||||||
|
drawer.classList.add('active');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDrawer() {
|
||||||
|
drawer.classList.remove('active');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
menuToggle?.addEventListener('click', openDrawer);
|
||||||
|
drawerClose?.addEventListener('click', closeDrawer);
|
||||||
|
drawerOverlay?.addEventListener('click', closeDrawer);
|
||||||
|
|
||||||
|
// Close drawer when clicking on nav links
|
||||||
|
document.querySelectorAll('.drawer-link').forEach(link => {
|
||||||
|
link.addEventListener('click', closeDrawer);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mypage Dropdown
|
||||||
|
const mypageToggle = document.querySelector('.mypage-toggle');
|
||||||
|
const mypageDropdown = document.querySelector('.mypage-dropdown');
|
||||||
|
|
||||||
|
if (mypageToggle && mypageDropdown) {
|
||||||
|
mypageToggle.addEventListener('click', function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
mypageDropdown.classList.toggle('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close dropdown when clicking outside
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
if (!mypageDropdown.contains(e.target)) {
|
||||||
|
mypageDropdown.classList.remove('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prevent dropdown from closing when clicking inside
|
||||||
|
mypageDropdown.addEventListener('click', function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Desktop Navigation Submenu Toggle (Click-based for mobile-like behavior on desktop)
|
||||||
|
const navItems = document.querySelectorAll('.header-center .nav-menu > li');
|
||||||
|
|
||||||
|
navItems.forEach(item => {
|
||||||
|
const link = item.querySelector(':scope > a');
|
||||||
|
const submenu = item.querySelector('.sub-menu');
|
||||||
|
|
||||||
|
if (submenu) {
|
||||||
|
// Add click handler for toggle
|
||||||
|
link.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// Close other submenus
|
||||||
|
navItems.forEach(otherItem => {
|
||||||
|
if (otherItem !== item && otherItem.classList.contains('submenu-open')) {
|
||||||
|
otherItem.classList.remove('submenu-open');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toggle current submenu
|
||||||
|
item.classList.toggle('submenu-open');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close submenu when clicking outside
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
if (!item.contains(e.target)) {
|
||||||
|
item.classList.remove('submenu-open');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prevent submenu from closing when clicking inside
|
||||||
|
submenu.addEventListener('click', function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</th:block>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<section layout:fragment="title">
|
<section layout:fragment="title">
|
||||||
<div class="page-title-banner">
|
<div class="page-title-banner">
|
||||||
<img th:src="@{/img/user_register_title.png}" class="title-image">
|
<img th:src="@{/img/img_title_bg.png}" class="title-image">
|
||||||
<h1 th:text="${agreementTitle}">비밀번호 변경</h1>
|
<h1 th:text="${agreementTitle}">비밀번호 변경</h1>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
<!-- header -->
|
<!-- header -->
|
||||||
<th:block th:replace="fragment/kjbank/header_container :: headerFragment"></th:block>
|
<th:block th:replace="fragment/kjbank/index_header_container :: headerFragment"></th:block>
|
||||||
<th:block th:replace="fragment/kjbank/header_container :: headerScript"></th:block>
|
<th:block th:replace="fragment/kjbank/header_container :: headerScript"></th:block>
|
||||||
|
|
||||||
<th:block layout:fragment="contentFragment">
|
<th:block layout:fragment="contentFragment">
|
||||||
|
|||||||
Reference in New Issue
Block a user