앱 관리

This commit is contained in:
현성필
2025-11-01 11:49:11 +09:00
parent 6841c9236d
commit 563a3f054c
46 changed files with 9740 additions and 2062 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -26,6 +26,133 @@ const customPopups = {
// 이벤트 리스너 제거
$(document).off('keydown.customAlert');
},
/**
* 비밀번호 입력 팝업 표시
* @param {Object} options - 팝업 옵션
* @param {string} options.title - 팝업 제목 (기본값: "비밀번호 입력")
* @param {string} options.message - 안내 메시지 (기본값: "계속하려면 비밀번호를 입력해주세요.")
* @param {Function} options.onConfirm - 확인 버튼 클릭 호출되는 콜백 (파라미터: password)
* @param {Function} options.onCancel - 취소 버튼 클릭 호출되는 콜백 (선택사항)
*/
showPasswordInput: function (options) {
options = options || {};
// 기본값 설정
const title = options.title || '비밀번호 입력';
const message = options.message || '계속하려면 비밀번호를 입력해주세요.';
const onConfirm = options.onConfirm;
const onCancel = options.onCancel;
// 팝업 내용 설정
$('#passwordPopupTitle').text(title);
$('#passwordPopupMessage').text(message);
// 입력 필드 및 에러 초기화
$('#passwordPopupInput').val('').removeClass('error');
$('#passwordPopupError').removeClass('show').text('');
// 팝업 표시 (modal 구조 사용)
$('#passwordInputPopup').show();
setTimeout(function() {
$('#passwordModalBackdrop').addClass('show');
$('#passwordModal').addClass('show');
}, 10);
// Body 스크롤 방지
$('body').css('overflow', 'hidden');
// 입력 필드에 포커스
setTimeout(function() {
$('#passwordPopupInput').focus();
}, 350);
// 확인 버튼 이벤트 (기존 이벤트 제거 후 재등록)
$('#passwordPopupConfirmButton').off('click').on('click', function () {
const password = $('#passwordPopupInput').val().trim();
if (!password) {
customPopups.showPasswordError('비밀번호를 입력해주세요.');
return;
}
if (typeof onConfirm === 'function') {
onConfirm(password);
}
});
// 취소 버튼 이벤트
$('#passwordPopupCancelButton').off('click').on('click', function () {
customPopups.hidePasswordInput();
if (typeof onCancel === 'function') {
onCancel();
}
});
// 닫기 버튼 이벤트
$('#passwordPopupCloseButton').off('click').on('click', function () {
customPopups.hidePasswordInput();
if (typeof onCancel === 'function') {
onCancel();
}
});
// Enter 키 이벤트
$('#passwordPopupInput').off('keypress').on('keypress', function (e) {
if (e.which === 13) {
$('#passwordPopupConfirmButton').click();
}
});
// 입력 시 에러 초기화
$('#passwordPopupInput').off('input').on('input', function () {
customPopups.clearPasswordError();
});
},
/**
* 비밀번호 입력 팝업 숨기기
*/
hidePasswordInput: function () {
// Modal 숨김 애니메이션
$('#passwordModalBackdrop').removeClass('show');
$('#passwordModal').removeClass('show');
// 애니메이션 완료 후 숨김
setTimeout(function() {
$('#passwordInputPopup').hide();
}, 300);
// Body 스크롤 복원
$('body').css('overflow', '');
// 입력 필드 초기화
$('#passwordPopupInput').val('').removeClass('error');
$('#passwordPopupError').removeClass('show').text('');
// 이벤트 리스너 제거
$('#passwordPopupConfirmButton').off('click');
$('#passwordPopupCancelButton').off('click');
$('#passwordPopupCloseButton').off('click');
$('#passwordPopupInput').off('keypress').off('input');
},
/**
* 비밀번호 입력 에러 메시지 표시
* @param {string} message - 에러 메시지
*/
showPasswordError: function (message) {
$('#passwordPopupError').text(message).addClass('show');
$('#passwordPopupInput').addClass('error');
},
/**
* 비밀번호 입력 에러 메시지 제거
*/
clearPasswordError: function () {
$('#passwordPopupError').removeClass('show');
$('#passwordPopupInput').removeClass('error');
},
showConfirm: function (message, callback) {
$('#customConfirmMessage').text(message);
$('#customConfirm').css('display', 'flex');
@@ -0,0 +1,73 @@
// ============================================
// Password Input Popup Component
// Extends base modal styles from _modals.scss
// ============================================
// Password popup specific styles (using modal structure)
#passwordInputPopup {
// Modal dialog size override
.modal-dialog {
max-width: 450px;
}
// Password input group
.pop_input_group {
text-align: left;
margin: 0;
}
.pop_input_field {
width: 100%;
padding: 12px $spacing-md;
border: 1px solid $border-gray;
border-radius: $border-radius-md;
font-size: $font-size-sm;
font-family: $font-family-primary;
transition: all 0.3s ease;
box-sizing: border-box;
&:focus {
outline: none;
border-color: $primary-blue;
box-shadow: 0 0 0 3px rgba($primary-blue, 0.1);
}
&::placeholder {
color: $text-light;
}
// Error state
&.error {
border-color: $accent-orange;
&:focus {
border-color: $accent-orange;
box-shadow: 0 0 0 3px rgba($accent-orange, 0.1);
}
}
}
.error-message {
color: $accent-orange;
font-size: $font-size-xs;
margin-top: $spacing-xs;
display: none;
animation: fadeInDown 0.3s ease;
&.show {
display: block;
}
}
}
// Animation for error message
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@@ -69,7 +69,7 @@
align-items: center;
justify-content: space-between;
height: 80px;
max-width: 1280px;
max-width: $container-max-width;
margin: 0 auto;
padding: 0 20px;
}
+4
View File
@@ -28,6 +28,7 @@
@import 'components/navigation';
@import 'components/partners';
@import 'components/cta';
@import 'components/password-popup';
// 5. Page-specific styles
@import 'pages/index';
@@ -36,6 +37,9 @@
@import 'pages/api-market';
@import 'pages/documentation';
@import 'pages/login';
@import 'pages/mypage';
@import 'pages/apikey-register';
@import 'pages/apikey-detail';
// 6. Themes
@import 'themes/dark';
@@ -0,0 +1,476 @@
@charset "utf-8";
// ====================================
// API Key Detail Pages Styles
// For appRequestDetail.html and credentialDetail.html
// ====================================
// Container Styles
.apikey-detail-container {
max-width: 1200px;
margin: 0 auto;
padding: $spacing-4xl;
padding-top: 0;
@media (max-width: $breakpoint-sm) {
padding: $spacing-md;
}
}
.register-header {
text-align: center;
margin-bottom: $spacing-5xl;
h1 {
font-size: $font-size-4xl;
font-weight: $font-weight-bold;
color: $text-dark;
margin-bottom: $spacing-sm;
}
.header-description {
font-size: $font-size-base;
color: $text-gray;
}
}
// Status Section
.status-section {
text-align: center;
margin-bottom: $spacing-4xl;
}
.status-badge {
display: inline-block;
padding: $spacing-sm $spacing-lg;
border-radius: $border-radius-full;
font-weight: $font-weight-semibold;
font-size: $font-size-sm;
&.badge-pending {
background-color: #FFF3CD;
color: #856404;
}
&.badge-approved {
background-color: #D4EDDA;
color: #155724;
}
&.badge-rejected {
background-color: #F8D7DA;
color: #721C24;
}
}
// Detail Section
.detail-section {
background: $white;
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 {
font-size: $font-size-xl;
font-weight: $font-weight-bold;
color: $text-dark;
margin-bottom: $spacing-lg;
padding-bottom: $spacing-md;
border-bottom: 2px solid $border-gray;
}
// Detail Grid
.detail-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: $spacing-lg;
@media (max-width: $breakpoint-sm) {
grid-template-columns: 1fr;
}
}
.detail-item {
display: flex;
flex-direction: column;
gap: $spacing-sm;
&.full-width {
grid-column: 1 / -1;
}
}
.detail-label {
font-size: $font-size-sm;
font-weight: $font-weight-semibold;
color: $text-gray;
}
.detail-value {
font-size: $font-size-base;
color: $text-dark;
word-break: break-word;
}
// App Icon
.app-icon-display {
width: 120px;
height: 120px;
}
.app-icon-image {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: $border-radius-lg;
border: 2px solid $border-gray;
}
.app-icon-placeholder {
width: 100%;
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 {
display: flex;
flex-wrap: wrap;
gap: $spacing-sm;
}
.ip-tag {
display: inline-block;
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 {
display: inline-block;
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 {
display: flex;
align-items: center;
gap: $spacing-md;
@media (max-width: $breakpoint-sm) {
flex-direction: column;
align-items: flex-start;
}
}
.btn-copy {
display: inline-flex;
align-items: center;
gap: $spacing-xs;
padding: $spacing-sm $spacing-md;
background-color: $primary-blue;
color: $white;
border: none;
border-radius: $border-radius-sm;
font-size: $font-size-sm;
font-weight: $font-weight-semibold;
cursor: pointer;
transition: all 0.2s;
&:hover {
background-color: $secondary-blue;
}
svg {
width: 16px;
height: 16px;
}
@media (max-width: $breakpoint-sm) {
align-self: stretch;
justify-content: center;
}
}
// Status Indicator
.status-indicator {
display: inline-flex;
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 {
width: 8px;
height: 8px;
border-radius: $border-radius-circle;
background-color: currentColor;
}
// API Detail List
.api-detail-list {
display: flex;
flex-direction: column;
gap: $spacing-sm;
}
.api-list-item {
display: flex;
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 {
border-color: $primary-blue;
box-shadow: $shadow-sm;
}
@media (max-width: $breakpoint-sm) {
padding: $spacing-md;
gap: $spacing-sm;
}
}
.api-method {
display: inline-block;
padding: $spacing-xs $spacing-md;
border-radius: $border-radius-sm;
font-size: $font-size-sm;
font-weight: $font-weight-bold;
font-family: $font-family-mono;
min-width: 80px;
text-align: center;
&.method-get {
background-color: #D1FAE5;
color: #065F46;
}
&.method-post {
background-color: #DBEAFE;
color: #1E40AF;
}
&.method-put {
background-color: #FEF3C7;
color: #92400E;
}
&.method-delete {
background-color: #FEE2E2;
color: #991B1B;
}
&.method-other {
background-color: $border-gray;
color: $text-dark;
}
}
.api-list-item .api-name {
flex: 1;
font-size: $font-size-base;
font-weight: $font-weight-medium;
color: $text-dark;
margin: 0;
}
.api-status {
padding: $spacing-xs $spacing-md;
border-radius: $border-radius-lg;
font-size: $font-size-sm;
font-weight: $font-weight-semibold;
&.status-pending {
background-color: #FFF3CD;
color: #856404;
}
&.status-active {
background-color: #D4EDDA;
color: #155724;
}
}
// History Table
.history-table-wrapper {
overflow-x: auto;
}
.history-table {
width: 100%;
border-collapse: collapse;
th {
background-color: $gray-bg;
padding: $spacing-md $spacing-md;
text-align: left;
font-weight: $font-weight-semibold;
color: $text-gray;
border-bottom: 2px solid $border-gray;
}
td {
padding: $spacing-md;
border-bottom: 1px solid $border-gray;
}
}
.history-status {
display: inline-block;
padding: $spacing-xs $spacing-md;
background-color: $light-bg;
color: $secondary-blue;
border-radius: $border-radius-lg;
font-size: $font-size-sm;
font-weight: $font-weight-semibold;
}
// Empty State
.empty-state {
grid-column: 1 / -1;
text-align: center;
padding: 48px $spacing-lg;
.empty-icon {
font-size: 64px;
margin-bottom: $spacing-md;
}
p {
font-size: $font-size-base;
color: $text-gray;
}
}
// Action Buttons
.detail-actions {
display: flex;
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;
}
}
.btn-action,
.btn-secondary {
padding: $spacing-md $spacing-4xl;
border-radius: $border-radius-md;
font-weight: $font-weight-semibold;
font-size: $font-size-base;
cursor: pointer;
transition: all 0.2s;
text-decoration: none;
display: inline-block;
border: none;
@media (max-width: $breakpoint-sm) {
width: 100%;
}
}
.btn-action {
&.btn-primary {
background-color: $primary-blue;
color: $white;
&:hover {
background-color: $secondary-blue;
}
}
&.btn-info {
background-color: $accent-cyan;
color: $white;
&:hover {
background-color: darken($accent-cyan, 10%);
}
}
&.btn-danger {
background-color: $accent-orange;
color: $white;
&:hover {
background-color: darken($accent-orange, 10%);
}
}
}
.btn-secondary,
.btn-cancel {
background-color: $gray-bg;
color: $text-dark;
border: 1px solid $border-gray;
&:hover {
background-color: $border-gray;
}
}
.btn-cancel {
padding: $spacing-md $spacing-4xl;
border-radius: $border-radius-md;
font-weight: $font-weight-semibold;
font-size: $font-size-base;
cursor: pointer;
transition: all 0.2s;
background-color: #FEE2E2;
color: #991B1B;
border: 1px solid #FCA5A5;
&:hover {
background-color: #FCA5A5;
color: #7F1D1D;
}
@media (max-width: $breakpoint-sm) {
width: 100%;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,343 @@
// -----------------------------------------------------------------------------
// MyPage - API Key List Card Layout
// Reference: apps/mypage/apiKeyList.html
// -----------------------------------------------------------------------------
// Container
.apikey-container {
max-width: $container-max-width;
margin: 0 auto;
padding: $spacing-2xl $spacing-lg;
@media (max-width: $breakpoint-sm) {
padding: $spacing-lg $spacing-md;
}
}
// Header Section
.apikey-header {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-bottom: $spacing-2xl;
padding-bottom: $spacing-lg;
border-bottom: 2px solid $border-gray;
@media (max-width: $breakpoint-sm) {
flex-direction: column;
align-items: flex-start;
gap: $spacing-lg;
}
}
.apikey-title {
h1 {
font-size: $font-size-sm;
color: $text-gray;
font-weight: $font-weight-regular;
margin-bottom: $spacing-xs;
}
h2 {
font-size: $font-size-2xl;
font-weight: $font-weight-bold;
color: $text-dark;
}
}
.apikey-actions {
.btn-create-app {
display: inline-flex;
align-items: center;
gap: $spacing-sm;
padding: 12px 24px;
background: $gradient-primary;
color: $white;
border: none;
border-radius: $border-radius-lg;
font-size: $font-size-base;
font-weight: $font-weight-semibold;
cursor: pointer;
transition: $transition-base;
box-shadow: $shadow-md;
&:hover {
transform: translateY(-2px);
box-shadow: $shadow-lg;
}
&:active {
transform: translateY(0);
}
.btn-icon {
font-size: 18px;
}
}
}
// Result Count
.apikey-result-count {
font-size: $font-size-base;
color: $text-gray;
margin-bottom: $spacing-xl;
strong {
color: $primary-blue;
font-weight: $font-weight-semibold;
}
}
// Card Grid - Single column layout (one card per row)
.apikey-card-grid {
display: grid;
grid-template-columns: 1fr;
gap: $spacing-lg;
margin-bottom: $spacing-2xl;
}
// API Key Card - Simplified horizontal layout
.apikey-card {
background: $white;
border: 1px solid $border-gray;
border-radius: $border-radius-lg;
box-shadow: $shadow-sm;
transition: $transition-base;
cursor: pointer;
display: flex;
flex-direction: row;
align-items: center;
gap: $spacing-lg;
padding: $spacing-lg;
position: relative;
overflow: hidden;
text-decoration: none;
color: inherit;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 4px;
background: $gradient-primary;
transform: scaleY(0);
transform-origin: top;
transition: transform 0.3s ease;
}
&:hover {
box-shadow: $shadow-lg;
transform: translateX(4px);
border-color: rgba($primary-blue, 0.3);
&::before {
transform: scaleY(1);
}
}
&:active {
transform: translateX(2px);
}
@media (max-width: $breakpoint-sm) {
flex-direction: column;
align-items: flex-start;
gap: $spacing-md;
}
}
// App Icon
.apikey-card-icon {
flex-shrink: 0;
width: 80px;
height: 80px;
border-radius: $border-radius-md;
overflow: hidden;
background: $gray-bg;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid $border-gray;
.app-icon-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.app-icon-placeholder {
color: $text-gray;
opacity: 0.5;
display: flex;
align-items: center;
justify-content: center;
}
@media (max-width: $breakpoint-sm) {
width: 60px;
height: 60px;
}
}
// App Content
.apikey-card-content {
flex: 1;
min-width: 0; // Allow text truncation
.apikey-card-title {
font-size: $font-size-lg;
font-weight: $font-weight-semibold;
color: $text-dark;
margin-bottom: $spacing-xs;
}
.apikey-card-description {
font-size: $font-size-sm;
color: $text-gray;
line-height: 1.5;
margin: 0;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
}
// Status Wrapper
.apikey-card-status-wrapper {
flex-shrink: 0;
.apikey-card-status {
display: inline-flex;
align-items: center;
padding: 6px 16px;
border-radius: $border-radius-full;
font-size: $font-size-xs;
font-weight: $font-weight-semibold;
text-transform: uppercase;
letter-spacing: 0.5px;
white-space: nowrap;
&.status-active {
background-color: rgba($accent-green, 0.15);
color: #4FA065;
}
&.status-inactive {
background-color: rgba($accent-orange, 0.15);
color: #D9534F;
}
&.status-pending {
background-color: rgba($accent-yellow, 0.15);
color: #F0AD4E;
}
}
}
// Empty State
.apikey-empty-state {
text-align: center;
padding: $spacing-5xl $spacing-lg;
background: $white;
border-radius: $border-radius-lg;
border: 2px dashed $border-gray;
.empty-icon {
font-size: 80px;
margin-bottom: $spacing-lg;
opacity: 0.6;
display: inline-block;
animation: bounce 2s infinite;
}
h3 {
font-size: $font-size-xl;
font-weight: $font-weight-semibold;
color: $text-dark;
margin-bottom: $spacing-md;
}
p {
font-size: $font-size-base;
color: $text-gray;
margin-bottom: $spacing-xl;
}
.btn-create-app-large {
display: inline-flex;
align-items: center;
gap: $spacing-sm;
padding: 14px 32px;
background: $gradient-primary;
color: $white;
border: none;
border-radius: $border-radius-lg;
font-size: $font-size-md;
font-weight: $font-weight-semibold;
cursor: pointer;
transition: $transition-base;
box-shadow: $shadow-lg;
&:hover {
transform: translateY(-3px);
box-shadow: $shadow-xl;
}
&:active {
transform: translateY(-1px);
}
.btn-icon {
font-size: 20px;
}
}
}
// Bounce animation for empty state icon
@keyframes bounce {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
}
// Loading State (if needed)
.apikey-loading {
display: flex;
justify-content: center;
align-items: center;
padding: $spacing-5xl;
.spinner {
width: 48px;
height: 48px;
border: 4px solid $border-gray;
border-top-color: $primary-blue;
border-radius: $border-radius-circle;
animation: spin 1s linear infinite;
}
}
// -----------------------------------------------------------------------------
// API Key Registration Wizard Styles - Moved to _apikey-register.scss
// -----------------------------------------------------------------------------
// All registration-related styles have been moved to _apikey-register.scss
// This file now contains only API Key List styles
// Responsive adjustments for smaller screens
@media (max-width: $breakpoint-sm) {
.apikey-empty-state {
padding: $spacing-3xl $spacing-md;
.empty-icon {
font-size: 60px;
}
}
}