API 목록, API 상세 페이지 적용
This commit is contained in:
@@ -4060,6 +4060,694 @@ select.form-control {
|
||||
animation: countUp 0.6s ease-out forwards;
|
||||
}
|
||||
|
||||
.api-market-container {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
background-color: #F8FAFC;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.api-market-sidebar {
|
||||
width: 280px;
|
||||
background-color: #FFFFFF;
|
||||
border-right: 1px solid #E2E8F0;
|
||||
padding: 40px 24px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@media (max-width: 1024px) {
|
||||
.api-market-sidebar {
|
||||
width: 240px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.api-market-sidebar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 60px;
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.3s ease;
|
||||
box-shadow: 0 8px 24px rgba(75, 155, 255, 0.15);
|
||||
z-index: 500;
|
||||
height: calc(100vh - 60px);
|
||||
}
|
||||
.api-market-sidebar.mobile-open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.api-sidebar-nav .menu-section {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.api-sidebar-nav .menu-title {
|
||||
padding: 12px 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #2E7FF7;
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.api-sidebar-nav .menu-title:hover {
|
||||
background-color: #F8FAFC;
|
||||
}
|
||||
.api-sidebar-nav .menu-title.active {
|
||||
background-color: #EFF6FF;
|
||||
color: #2E7FF7;
|
||||
}
|
||||
.api-sidebar-nav .menu-title .api-count {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 24px;
|
||||
height: 24px;
|
||||
padding: 0 8px;
|
||||
background-color: rgba(75, 155, 255, 0.1);
|
||||
color: #4B9BFF;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
border-radius: 50px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.api-sidebar-nav .menu-title.active .api-count {
|
||||
background-color: #4B9BFF;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.api-sidebar-nav .api-list {
|
||||
padding-left: 16px;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.api-sidebar-nav .api-item {
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
color: #64748B;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.api-sidebar-nav .api-item::before {
|
||||
content: "•";
|
||||
margin-right: 8px;
|
||||
color: #4B9BFF;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.api-sidebar-nav .api-item:hover {
|
||||
background-color: rgba(75, 155, 255, 0.05);
|
||||
color: #1A1A2E;
|
||||
padding-left: 20px;
|
||||
}
|
||||
.api-sidebar-nav .api-item:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
.api-sidebar-nav .api-item.active {
|
||||
background-color: rgba(75, 155, 255, 0.1);
|
||||
color: #4B9BFF;
|
||||
font-weight: 500;
|
||||
}
|
||||
.api-sidebar-nav .api-item.active::before {
|
||||
opacity: 1;
|
||||
}
|
||||
.api-sidebar-nav .api-item .api-name {
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.api-market-content {
|
||||
flex: 1;
|
||||
padding: 40px 48px;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.api-market-content {
|
||||
padding: 24px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.api-mobile-toggle {
|
||||
display: none;
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
color: #FFFFFF;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 8px 24px rgba(75, 155, 255, 0.15);
|
||||
z-index: 501;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.api-mobile-toggle:hover {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 16px 40px rgba(75, 155, 255, 0.2);
|
||||
}
|
||||
.api-mobile-toggle:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.api-mobile-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.api-market-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.api-market-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.api-market-title h1 {
|
||||
font-size: 14px;
|
||||
color: #64748B;
|
||||
font-weight: 400;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.api-market-title h2 {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: #1A1A2E;
|
||||
}
|
||||
|
||||
.api-market-search {
|
||||
position: relative;
|
||||
width: 300px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.api-market-search {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.api-market-search form {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.api-market-search input {
|
||||
flex: 1;
|
||||
padding: 12px 48px 12px 16px;
|
||||
border: 1px solid #E2E8F0;
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.api-market-search input:focus {
|
||||
border-color: #4B9BFF;
|
||||
box-shadow: 0 0 0 3px rgba(75, 155, 255, 0.1);
|
||||
}
|
||||
.api-market-search input::placeholder {
|
||||
color: #94A3B8;
|
||||
}
|
||||
.api-market-search .search-submit-btn {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
color: #64748B;
|
||||
}
|
||||
.api-market-search .search-submit-btn:hover {
|
||||
background: #EFF6FF;
|
||||
color: #4B9BFF;
|
||||
transform: translateY(-50%) scale(1.05);
|
||||
}
|
||||
.api-market-search .search-submit-btn:active {
|
||||
transform: translateY(-50%) scale(0.95);
|
||||
}
|
||||
.api-market-search .search-submit-btn .search-icon {
|
||||
font-size: 18px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.api-result-count {
|
||||
font-size: 16px;
|
||||
color: #64748B;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.api-result-count strong {
|
||||
color: #4B9BFF;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.api-card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.api-card-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.api-card {
|
||||
background: #FFFFFF;
|
||||
border-radius: 12px;
|
||||
padding: 32px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
min-height: 220px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.api-card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);
|
||||
transform: scaleX(0);
|
||||
transform-origin: left;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
.api-card:hover {
|
||||
box-shadow: 0 8px 24px rgba(75, 155, 255, 0.15);
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
.api-card:hover::before {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
.api-card:hover .api-card-icon {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
.api-card:active {
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
.api-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.api-card-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 12px;
|
||||
background-color: #F8FAFC;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
color: #64748B;
|
||||
font-weight: 500;
|
||||
}
|
||||
.api-card-badge::before {
|
||||
content: "◉";
|
||||
color: #4B9BFF;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.api-card-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1A1A2E;
|
||||
margin-bottom: 12px;
|
||||
line-height: 1.2;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.api-card-description {
|
||||
font-size: 14px;
|
||||
color: #64748B;
|
||||
line-height: 1.6;
|
||||
flex-grow: 1;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.api-card-icon {
|
||||
text-align: center;
|
||||
margin-top: 24px;
|
||||
font-size: 48px;
|
||||
opacity: 0.8;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
.api-card-icon img {
|
||||
max-width: 60px;
|
||||
max-height: 60px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.api-card:nth-child(3n+1) .api-card-icon {
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
.api-card:nth-child(3n+2) .api-card-icon {
|
||||
color: #34d399;
|
||||
}
|
||||
|
||||
.api-card:nth-child(3n+3) .api-card-icon {
|
||||
color: #818cf8;
|
||||
}
|
||||
|
||||
.api-empty-state {
|
||||
text-align: center;
|
||||
padding: 80px 24px;
|
||||
color: #64748B;
|
||||
}
|
||||
.api-empty-state .empty-icon {
|
||||
font-size: 80px;
|
||||
margin-bottom: 24px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.api-empty-state h3 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #1A1A2E;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.api-empty-state p {
|
||||
font-size: 16px;
|
||||
color: #64748B;
|
||||
}
|
||||
|
||||
.api-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 80px;
|
||||
}
|
||||
.api-loading .spinner {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 4px solid #E2E8F0;
|
||||
border-top-color: #4B9BFF;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
.api-mobile-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 499;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.api-mobile-overlay {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.api-mobile-overlay.active {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.api-detail-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
.api-detail-tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
border-bottom: 2px solid #E2E8F0;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.api-detail-tabs .tab-button {
|
||||
padding: 16px 24px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 3px solid transparent;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #64748B;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
bottom: -2px;
|
||||
}
|
||||
.api-detail-tabs .tab-button:hover {
|
||||
color: #4B9BFF;
|
||||
background-color: rgba(75, 155, 255, 0.05);
|
||||
}
|
||||
.api-detail-tabs .tab-button.active {
|
||||
color: #4B9BFF;
|
||||
font-weight: 600;
|
||||
border-bottom-color: #4B9BFF;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
min-height: 200px;
|
||||
}
|
||||
.tab-content.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.api-overview-card {
|
||||
background: #FFFFFF;
|
||||
border-radius: 12px;
|
||||
padding: 32px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
.api-overview-card .api-overview-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.api-overview-card .api-overview-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
.api-overview-card .api-method-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
padding: 8px 16px;
|
||||
background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);
|
||||
color: #FFFFFF;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
border-radius: 6px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.api-overview-card .api-endpoint {
|
||||
flex: 1;
|
||||
}
|
||||
.api-overview-card .api-endpoint code {
|
||||
display: block;
|
||||
padding: 8px 16px;
|
||||
background-color: #F8FAFC;
|
||||
border: 1px solid #E2E8F0;
|
||||
border-radius: 8px;
|
||||
font-size: 18px;
|
||||
font-family: "Courier New", monospace;
|
||||
color: #1A1A2E;
|
||||
word-break: break-all;
|
||||
}
|
||||
.api-overview-card .api-simple-description {
|
||||
padding: 16px 0;
|
||||
border-bottom: 1px solid #E2E8F0;
|
||||
}
|
||||
.api-overview-card .api-simple-description p {
|
||||
font-size: 16px;
|
||||
color: #64748B;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
}
|
||||
.api-overview-card .api-overview-info h4 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1A1A2E;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.api-details-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.api-detail-card {
|
||||
background: #FFFFFF;
|
||||
border-radius: 12px;
|
||||
padding: 32px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.api-detail-card:hover {
|
||||
box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1);
|
||||
}
|
||||
.api-detail-card h3 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #1A1A2E;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid #EFF6FF;
|
||||
}
|
||||
.api-detail-card .detail-content {
|
||||
font-size: 14px;
|
||||
color: #64748B;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.api-detail-card .detail-content pre {
|
||||
background-color: #F8FAFC;
|
||||
border: 1px solid #E2E8F0;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
overflow-x: auto;
|
||||
margin: 0;
|
||||
}
|
||||
.api-detail-card .detail-content pre code {
|
||||
font-family: "Courier New", monospace;
|
||||
font-size: 14px;
|
||||
color: #1A1A2E;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
.api-detail-card .detail-content table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 16px 0;
|
||||
background-color: #FFFFFF;
|
||||
border: 1px solid #E2E8F0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.api-detail-card .detail-content table thead {
|
||||
background-color: #EFF6FF;
|
||||
}
|
||||
.api-detail-card .detail-content table th {
|
||||
text-align: left;
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1A1A2E;
|
||||
border-bottom: 2px solid #E2E8F0;
|
||||
background-color: rgba(75, 155, 255, 0.05);
|
||||
}
|
||||
.api-detail-card .detail-content table th:not(:last-child) {
|
||||
border-right: 1px solid #E2E8F0;
|
||||
}
|
||||
.api-detail-card .detail-content table td {
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
color: #64748B;
|
||||
border-bottom: 1px solid #E2E8F0;
|
||||
}
|
||||
.api-detail-card .detail-content table td:not(:last-child) {
|
||||
border-right: 1px solid #E2E8F0;
|
||||
}
|
||||
.api-detail-card .detail-content table tr {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.api-detail-card .detail-content table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
.api-detail-card .detail-content table tr:hover {
|
||||
background-color: rgba(75, 155, 255, 0.02);
|
||||
}
|
||||
.api-detail-card .detail-content table tbody tr:nth-child(even) {
|
||||
background-color: rgba(248, 250, 252, 0.3);
|
||||
}
|
||||
|
||||
.api-info-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.api-info-table tr {
|
||||
border-bottom: 1px solid #E2E8F0;
|
||||
}
|
||||
.api-info-table tr:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.api-info-table th {
|
||||
text-align: left;
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1A1A2E;
|
||||
width: 30%;
|
||||
background-color: #F8FAFC;
|
||||
}
|
||||
.api-info-table td {
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
color: #64748B;
|
||||
}
|
||||
.api-info-table td code {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
background-color: #F8FAFC;
|
||||
border-radius: 6px;
|
||||
font-family: "Courier New", monospace;
|
||||
font-size: 12px;
|
||||
color: #4B9BFF;
|
||||
}
|
||||
|
||||
.login-page {
|
||||
min-height: calc(100vh - 140px);
|
||||
display: flex;
|
||||
|
||||
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: 39 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
@@ -33,6 +33,7 @@
|
||||
@import 'pages/index';
|
||||
@import 'pages/home';
|
||||
@import 'pages/api-portal';
|
||||
@import 'pages/api-market';
|
||||
@import 'pages/documentation';
|
||||
@import 'pages/login';
|
||||
|
||||
|
||||
@@ -0,0 +1,780 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// API Market Page - Modern Sidebar + Card Grid Layout
|
||||
// Reference: design/api-market.html
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
.api-market-container {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
background-color: $gray-bg;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
// Sidebar Navigation
|
||||
.api-market-sidebar {
|
||||
width: 280px;
|
||||
background-color: $white;
|
||||
border-right: 1px solid $border-gray;
|
||||
padding: $spacing-2xl $spacing-lg;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
flex-shrink: 0;
|
||||
|
||||
@media (max-width: $breakpoint-md) {
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-sm) {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 60px; // Below mobile header
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.3s ease;
|
||||
box-shadow: $shadow-lg;
|
||||
z-index: $z-index-modal;
|
||||
height: calc(100vh - 60px); // Full height minus header
|
||||
|
||||
&.mobile-open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.api-sidebar-nav {
|
||||
.menu-section {
|
||||
margin-bottom: $spacing-xs;
|
||||
}
|
||||
|
||||
.menu-title {
|
||||
padding: 12px $spacing-md;
|
||||
font-size: $font-size-sm;
|
||||
font-weight: $font-weight-semibold;
|
||||
color: $secondary-blue;
|
||||
cursor: pointer;
|
||||
border-radius: $border-radius-md;
|
||||
transition: $transition-base;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
&:hover {
|
||||
background-color: $gray-bg;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: $light-bg;
|
||||
color: $secondary-blue;
|
||||
}
|
||||
|
||||
.api-count {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 24px;
|
||||
height: 24px;
|
||||
padding: 0 $spacing-sm;
|
||||
background-color: rgba($primary-blue, 0.1);
|
||||
color: $primary-blue;
|
||||
font-size: $font-size-xs;
|
||||
font-weight: $font-weight-semibold;
|
||||
border-radius: $border-radius-full;
|
||||
transition: $transition-base;
|
||||
}
|
||||
|
||||
&.active .api-count {
|
||||
background-color: $primary-blue;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
// Level 2: API List
|
||||
.api-list {
|
||||
padding-left: $spacing-md;
|
||||
margin-top: $spacing-xs;
|
||||
margin-bottom: $spacing-sm;
|
||||
}
|
||||
|
||||
.api-item {
|
||||
padding: 8px $spacing-md;
|
||||
font-size: $font-size-sm;
|
||||
color: $text-gray;
|
||||
cursor: pointer;
|
||||
border-radius: $border-radius-sm;
|
||||
transition: $transition-base;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
margin-bottom: 4px;
|
||||
|
||||
&::before {
|
||||
content: '•';
|
||||
margin-right: $spacing-sm;
|
||||
color: $primary-blue;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($primary-blue, 0.05);
|
||||
color: $text-dark;
|
||||
padding-left: calc($spacing-md + 4px);
|
||||
|
||||
&::before {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: rgba($primary-blue, 0.1);
|
||||
color: $primary-blue;
|
||||
font-weight: $font-weight-medium;
|
||||
|
||||
&::before {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.api-name {
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Main Content Area
|
||||
.api-market-content {
|
||||
flex: 1;
|
||||
padding: $spacing-2xl $spacing-3xl;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@media (max-width: $breakpoint-sm) {
|
||||
padding: $spacing-lg $spacing-md;
|
||||
}
|
||||
}
|
||||
|
||||
// Mobile Menu Toggle
|
||||
.api-mobile-toggle {
|
||||
display: none;
|
||||
position: fixed;
|
||||
bottom: $spacing-lg;
|
||||
right: $spacing-lg;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
background: $gradient-primary;
|
||||
border-radius: $border-radius-circle;
|
||||
border: none;
|
||||
color: $white;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
box-shadow: $shadow-lg;
|
||||
z-index: $z-index-modal + 1;
|
||||
transition: $transition-base;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
box-shadow: $shadow-xl;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-sm) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
// Header Section
|
||||
.api-market-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: $spacing-2xl;
|
||||
|
||||
@media (max-width: $breakpoint-sm) {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: $spacing-lg;
|
||||
}
|
||||
}
|
||||
|
||||
.api-market-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;
|
||||
}
|
||||
}
|
||||
|
||||
// Search Box
|
||||
.api-market-search {
|
||||
position: relative;
|
||||
width: 300px;
|
||||
|
||||
@media (max-width: $breakpoint-sm) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
form {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
padding: 12px 48px 12px $spacing-md;
|
||||
border: 1px solid $border-gray;
|
||||
border-radius: $border-radius-lg;
|
||||
font-size: $font-size-sm;
|
||||
outline: none;
|
||||
transition: $transition-base;
|
||||
|
||||
&:focus {
|
||||
border-color: $primary-blue;
|
||||
box-shadow: 0 0 0 3px rgba($primary-blue, 0.1);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: $text-light;
|
||||
}
|
||||
}
|
||||
|
||||
.search-submit-btn {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: $border-radius-md;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: $transition-base;
|
||||
color: $text-gray;
|
||||
|
||||
&:hover {
|
||||
background: $light-bg;
|
||||
color: $primary-blue;
|
||||
transform: translateY(-50%) scale(1.05);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(-50%) scale(0.95);
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
font-size: 18px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Result Count
|
||||
.api-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
|
||||
.api-card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: $spacing-lg;
|
||||
|
||||
@media (max-width: $breakpoint-sm) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
// API Card
|
||||
.api-card {
|
||||
background: $white;
|
||||
border-radius: $border-radius-lg;
|
||||
padding: $spacing-xl;
|
||||
box-shadow: $shadow-sm;
|
||||
transition: $transition-base;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
min-height: 220px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: $gradient-primary;
|
||||
transform: scaleX(0);
|
||||
transform-origin: left;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: $shadow-lg;
|
||||
transform: translateY(-5px);
|
||||
|
||||
&::before {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
.api-card-icon {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
}
|
||||
|
||||
.api-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-sm;
|
||||
margin-bottom: $spacing-md;
|
||||
}
|
||||
|
||||
.api-card-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 12px;
|
||||
background-color: $gray-bg;
|
||||
border-radius: $border-radius-sm;
|
||||
font-size: $font-size-xs;
|
||||
color: $text-gray;
|
||||
font-weight: $font-weight-medium;
|
||||
|
||||
&::before {
|
||||
content: "◉";
|
||||
color: $primary-blue;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.api-card-title {
|
||||
font-size: $font-size-md;
|
||||
font-weight: $font-weight-semibold;
|
||||
color: $text-dark;
|
||||
margin-bottom: 12px;
|
||||
line-height: $line-height-tight;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.api-card-description {
|
||||
font-size: $font-size-sm;
|
||||
color: $text-gray;
|
||||
line-height: $line-height-normal;
|
||||
flex-grow: 1;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.api-card-icon {
|
||||
text-align: center;
|
||||
margin-top: $spacing-lg;
|
||||
font-size: 48px;
|
||||
opacity: 0.8;
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
img {
|
||||
max-width: 60px;
|
||||
max-height: 60px;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
// Icon color variations
|
||||
.api-card:nth-child(3n+1) .api-card-icon {
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
.api-card:nth-child(3n+2) .api-card-icon {
|
||||
color: #34d399;
|
||||
}
|
||||
|
||||
.api-card:nth-child(3n+3) .api-card-icon {
|
||||
color: #818cf8;
|
||||
}
|
||||
|
||||
// Empty State
|
||||
.api-empty-state {
|
||||
text-align: center;
|
||||
padding: $spacing-5xl $spacing-lg;
|
||||
color: $text-gray;
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80px;
|
||||
margin-bottom: $spacing-lg;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Loading State
|
||||
.api-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;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
// Mobile Overlay
|
||||
.api-mobile-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 60px; // Below mobile header
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba($black, 0.5);
|
||||
z-index: $z-index-modal - 1;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
pointer-events: none;
|
||||
|
||||
@media (max-width: $breakpoint-sm) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
// API Detail Content
|
||||
.api-detail-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $spacing-xl;
|
||||
}
|
||||
|
||||
// Tab Navigation
|
||||
.api-detail-tabs {
|
||||
display: flex;
|
||||
gap: $spacing-sm;
|
||||
border-bottom: 2px solid $border-gray;
|
||||
margin-bottom: $spacing-xl;
|
||||
|
||||
.tab-button {
|
||||
padding: $spacing-md $spacing-lg;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 3px solid transparent;
|
||||
font-size: $font-size-base;
|
||||
font-weight: $font-weight-medium;
|
||||
color: $text-gray;
|
||||
cursor: pointer;
|
||||
transition: $transition-base;
|
||||
position: relative;
|
||||
bottom: -2px;
|
||||
|
||||
&:hover {
|
||||
color: $primary-blue;
|
||||
background-color: rgba($primary-blue, 0.05);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: $primary-blue;
|
||||
font-weight: $font-weight-semibold;
|
||||
border-bottom-color: $primary-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tab Content
|
||||
.tab-content {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: $spacing-xl;
|
||||
min-height: 200px;
|
||||
|
||||
&.active {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
// API Overview Card
|
||||
.api-overview-card {
|
||||
background: $white;
|
||||
border-radius: $border-radius-lg;
|
||||
padding: $spacing-xl;
|
||||
box-shadow: $shadow-sm;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $spacing-lg;
|
||||
|
||||
.api-overview-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: $spacing-md;
|
||||
|
||||
@media (max-width: $breakpoint-sm) {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.api-method-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
padding: 8px 16px;
|
||||
background: $gradient-primary;
|
||||
color: $white;
|
||||
font-size: $font-size-sm;
|
||||
font-weight: $font-weight-semibold;
|
||||
border-radius: $border-radius-sm;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.api-endpoint {
|
||||
flex: 1;
|
||||
|
||||
code {
|
||||
display: block;
|
||||
padding: $spacing-sm $spacing-md;
|
||||
background-color: $gray-bg;
|
||||
border: 1px solid $border-gray;
|
||||
border-radius: $border-radius-md;
|
||||
font-size: $font-size-md;
|
||||
font-family: 'Courier New', monospace;
|
||||
color: $text-dark;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
|
||||
.api-simple-description {
|
||||
padding: $spacing-md 0;
|
||||
border-bottom: 1px solid $border-gray;
|
||||
|
||||
p {
|
||||
font-size: $font-size-base;
|
||||
color: $text-gray;
|
||||
line-height: $line-height-normal;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.api-overview-info {
|
||||
h4 {
|
||||
font-size: $font-size-md;
|
||||
font-weight: $font-weight-semibold;
|
||||
color: $text-dark;
|
||||
margin-bottom: $spacing-md;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// API Details Grid
|
||||
.api-details-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $spacing-lg;
|
||||
}
|
||||
|
||||
// API Detail Card
|
||||
.api-detail-card {
|
||||
background: $white;
|
||||
border-radius: $border-radius-lg;
|
||||
padding: $spacing-xl;
|
||||
box-shadow: $shadow-sm;
|
||||
transition: $transition-base;
|
||||
|
||||
&:hover {
|
||||
box-shadow: $shadow-md;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: $font-size-lg;
|
||||
font-weight: $font-weight-semibold;
|
||||
color: $text-dark;
|
||||
margin-bottom: $spacing-md;
|
||||
padding-bottom: $spacing-sm;
|
||||
border-bottom: 2px solid $light-bg;
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
font-size: $font-size-sm;
|
||||
color: $text-gray;
|
||||
line-height: $line-height-normal;
|
||||
|
||||
pre {
|
||||
background-color: $gray-bg;
|
||||
border: 1px solid $border-gray;
|
||||
border-radius: $border-radius-md;
|
||||
padding: $spacing-md;
|
||||
overflow-x: auto;
|
||||
margin: 0;
|
||||
|
||||
code {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: $font-size-sm;
|
||||
color: $text-dark;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
// Table styling for HTML tables in specifications
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: $spacing-md 0;
|
||||
background-color: $white;
|
||||
border: 1px solid $border-gray;
|
||||
border-radius: $border-radius-md;
|
||||
overflow: hidden;
|
||||
|
||||
thead {
|
||||
background-color: $light-bg;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
padding: $spacing-sm $spacing-md;
|
||||
font-size: $font-size-sm;
|
||||
font-weight: $font-weight-semibold;
|
||||
color: $text-dark;
|
||||
border-bottom: 2px solid $border-gray;
|
||||
background-color: rgba($primary-blue, 0.05);
|
||||
|
||||
&:not(:last-child) {
|
||||
border-right: 1px solid $border-gray;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
padding: $spacing-sm $spacing-md;
|
||||
font-size: $font-size-sm;
|
||||
color: $text-gray;
|
||||
border-bottom: 1px solid $border-gray;
|
||||
|
||||
&:not(:last-child) {
|
||||
border-right: 1px solid $border-gray;
|
||||
}
|
||||
}
|
||||
|
||||
tr {
|
||||
transition: $transition-base;
|
||||
|
||||
&:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($primary-blue, 0.02);
|
||||
}
|
||||
}
|
||||
|
||||
tbody tr:nth-child(even) {
|
||||
background-color: rgba($gray-bg, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// API Info Table
|
||||
.api-info-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
||||
tr {
|
||||
border-bottom: 1px solid $border-gray;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
padding: $spacing-sm $spacing-md;
|
||||
font-size: $font-size-sm;
|
||||
font-weight: $font-weight-semibold;
|
||||
color: $text-dark;
|
||||
width: 30%;
|
||||
background-color: $gray-bg;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: $spacing-sm $spacing-md;
|
||||
font-size: $font-size-sm;
|
||||
color: $text-gray;
|
||||
|
||||
code {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
background-color: $gray-bg;
|
||||
border-radius: $border-radius-sm;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: $font-size-xs;
|
||||
color: $primary-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,204 +1,454 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_api_layout}">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout/kjbank_base_layout}">
|
||||
|
||||
<body>
|
||||
|
||||
<section layout:fragment="contentFragment" class="content api_con">
|
||||
<style>
|
||||
/* Custom dark theme for CodeMirror */
|
||||
.cm-s-custom-dark.CodeMirror {
|
||||
background-color: #252B37;
|
||||
color: white;
|
||||
}
|
||||
<th:block layout:fragment="contentFragment">
|
||||
<section class="api-market-container">
|
||||
|
||||
.cm-s-custom-dark .CodeMirror-gutters {
|
||||
background-color: #1a1a1a;
|
||||
border-right: 1px solid #3a3a3a;
|
||||
}
|
||||
|
||||
.cm-s-custom-dark .CodeMirror-linenumber {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.cm-s-custom-dark .CodeMirror-cursor {
|
||||
border-left: 1px solid white;
|
||||
}
|
||||
|
||||
.cm-s-custom-dark .cm-string {
|
||||
color: #a6e22e;
|
||||
}
|
||||
|
||||
.cm-s-custom-dark .cm-number {
|
||||
color: #ae81ff;
|
||||
}
|
||||
|
||||
.cm-s-custom-dark .cm-atom {
|
||||
color: #f92672;
|
||||
}
|
||||
|
||||
.cm-s-custom-dark .cm-keyword {
|
||||
color: #66d9ef;
|
||||
}
|
||||
|
||||
.cm-s-custom-dark .cm-property {
|
||||
color: #ccffee;
|
||||
}
|
||||
|
||||
.cm-s-custom-dark .cm-punctuation {
|
||||
color: #f8f8f2;
|
||||
}
|
||||
</style>
|
||||
<div class="content_wrap w_api">
|
||||
<div class="sub_title4">
|
||||
<h2 class="title" th:text="${apiSpecInfo.apiName}"></h2>
|
||||
<p class="infotxt" th:text="${apiSpecInfo.apiSimpleDescription}"></p>
|
||||
</div>
|
||||
|
||||
<div class="pc-only">
|
||||
<div class="inner i_cs h_inner2">
|
||||
<div class="api_stit">
|
||||
<table class="api_table" cellspacing="0" cellpadding="0" width="100%">
|
||||
<colgroup>
|
||||
<col width="200px">
|
||||
<col width="auto">
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>API 명 (URI)</th>
|
||||
<td th:text="${apiSpecInfo.apiUrl}"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Method</th>
|
||||
<td>[[${apiSpecInfo.apiMethod}]]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Content-Type</th>
|
||||
<td>[[${apiSpecInfo.apiContentType}]]</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="api_stit api_dot" th:utext="${apiSpecInfo.description}">
|
||||
<!-- Sidebar Navigation -->
|
||||
<aside class="api-market-sidebar" id="apiSidebar">
|
||||
<nav class="api-sidebar-nav">
|
||||
<!-- All APIs -->
|
||||
<div class="menu-section">
|
||||
<div class="menu-title" th:classappend="${selected == '-1'} ? 'active'" data-group="">
|
||||
전체
|
||||
<span class="api-count" th:text="${totalApiCount}">0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inner i_cs h_inner2">
|
||||
<div class="api_stit m-only">
|
||||
<table class="api_table" cellspacing="0" cellpadding="0" width="100%">
|
||||
<colgroup>
|
||||
<col width="110px">
|
||||
<col width="auto">
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>API 명 (URI)</th>
|
||||
<td th:text="${apiSpecInfo.apiUrl}"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Method</th>
|
||||
<td>[[${apiSpecInfo.apiMethod}]]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Content-Type</th>
|
||||
<td>[[${apiSpecInfo.apiContentType}]]</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="api_stit api_dot" th:utext="${apiSpecInfo.description}">
|
||||
|
||||
<!-- Service Categories -->
|
||||
<div class="menu-section" th:each="service : ${services}">
|
||||
<div class="menu-title"
|
||||
th:classappend="${selected == service.id} ? 'active'"
|
||||
th:data-group="${service.id}">
|
||||
[[${service.groupName}]]
|
||||
<span class="api-count" th:text="${service.apiGroupApiList.size()}">0</span>
|
||||
</div>
|
||||
|
||||
<!-- Level 2: API List -->
|
||||
<div class="api-list" th:if="${service.apiGroupApiList != null and !service.apiGroupApiList.isEmpty()}">
|
||||
<div class="api-item"
|
||||
th:each="api : ${service.apiGroupApiList}"
|
||||
th:data-api-id="${api.apiId}"
|
||||
th:attr="data-href=@{/apis/detail(apiId=${api.apiId})}">
|
||||
<span class="api-name" th:text="${api.apiDesc}">API Name</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- Mobile Menu Toggle -->
|
||||
<button class="api-mobile-toggle" id="mobileToggle" aria-label="메뉴 열기">
|
||||
☰
|
||||
</button>
|
||||
|
||||
<!-- Mobile Overlay -->
|
||||
<div class="api-mobile-overlay" id="mobileOverlay"></div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="api-market-content">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="api-market-header">
|
||||
<div class="api-market-title">
|
||||
<h1>서비스</h1>
|
||||
<h2 th:text="${apiSpecInfo.apiName}">API 이름</h2>
|
||||
</div>
|
||||
</div>
|
||||
<!-- API Content -->
|
||||
<div class="api-detail-content">
|
||||
|
||||
<div class="api_stit api_sc api_tb_type api_sc2">
|
||||
<h3>요청 메시지</h3>
|
||||
<div class="pc-only" th:utext="${apiSpecInfo.apiRequestSpec}">
|
||||
|
||||
<!-- Tab Navigation -->
|
||||
<div class="api-detail-tabs">
|
||||
<button class="tab-button active" data-tab="api-info">API 정보</button>
|
||||
<button class="tab-button" data-tab="testbed">테스트베드</button>
|
||||
</div>
|
||||
<div class="m-only" th:utext="${apiSpecInfo.apiRequestSpec}">
|
||||
|
||||
<!-- Tab Content -->
|
||||
<div class="tab-content active" id="api-info-tab">
|
||||
<!-- API Overview Card (Merged with Additional Information) -->
|
||||
<div class="api-overview-card">
|
||||
<div class="api-overview-header">
|
||||
<div class="api-method-badge" th:text="${apiSpecInfo.apiMethod}">GET</div>
|
||||
<div class="api-endpoint">
|
||||
<code th:text="${apiSpecInfo.apiUrl}">/api/v1/example</code>
|
||||
</div>
|
||||
</div>
|
||||
<!-- API Information Table -->
|
||||
<div class="api-overview-info">
|
||||
<table class="api-info-table">
|
||||
<tr th:if="${apiSpecInfo.apiContentType != null}">
|
||||
<th>Content Type</th>
|
||||
<td th:text="${apiSpecInfo.apiContentType}">application/json</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="api-simple-description" th:if="${apiSpecInfo.apiSimpleDescription != null}">
|
||||
<p th:text="${apiSpecInfo.apiSimpleDescription}">Simple API description</p>
|
||||
</div>
|
||||
<div class="detail-content" th:utext="${apiSpecInfo.description}">
|
||||
Detailed API description
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="api_stit api_sc">
|
||||
<h3>요청 메시지 예제</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<p class="tit">
|
||||
<span>Header</span>
|
||||
</p>
|
||||
<div class="api_sauce">
|
||||
<p>
|
||||
Content-Type: application/json
|
||||
</p>
|
||||
|
||||
<!-- API Details Grid -->
|
||||
<div class="api-details-grid">
|
||||
|
||||
<!-- Request Specification -->
|
||||
<div class="api-detail-card" th:if="${apiSpecInfo.apiRequestSpec != null}">
|
||||
<h3>Request Specification</h3>
|
||||
<div class="detail-content" th:utext="${apiSpecInfo.apiRequestSpec}">
|
||||
Request spec
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="api_stit api_sc api_sc3">
|
||||
<ul>
|
||||
<li>
|
||||
<p class="tit">
|
||||
<span>Body</span>
|
||||
</p>
|
||||
<div class="api_sauce">
|
||||
<textarea id="sampleRequest" th:text="${apiSpecInfo.sampleRequest}"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Sample Request -->
|
||||
<div class="api-detail-card" th:if="${apiSpecInfo.sampleRequest != null}">
|
||||
<h3>샘플 요청</h3>
|
||||
<div class="detail-content">
|
||||
<pre><code th:text="${apiSpecInfo.sampleRequest}">Sample request</code></pre>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Response Specification -->
|
||||
<div class="api-detail-card" th:if="${apiSpecInfo.apiResponseSpec != null}">
|
||||
<h3>Response Specification</h3>
|
||||
<div class="detail-content" th:utext="${apiSpecInfo.apiResponseSpec}">
|
||||
Response spec
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sample Response -->
|
||||
<div class="api-detail-card" th:if="${apiSpecInfo.sampleResponse != null}">
|
||||
<h3>샘플 응답</h3>
|
||||
<div class="detail-content">
|
||||
<pre><code th:text="${apiSpecInfo.sampleResponse}">Sample response</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Testbed Tab Content -->
|
||||
<div class="tab-content" id="testbed-tab">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/plugins/swaggerUI/swagger-ui.css}"/>
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/plugins/swaggerUI/index.css}"/>
|
||||
|
||||
<div class="api-overview-card">
|
||||
<div id="swagger-ui"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="api_line"></div>
|
||||
</main>
|
||||
</section>
|
||||
</th:block>
|
||||
|
||||
<div class="api_stit api_sc api_sc2 api_tb_type">
|
||||
<h3>응답 메시지</h3>
|
||||
<div class="pc-only" th:utext="${apiSpecInfo.apiResponseSpec}"></div>
|
||||
<div class="m-only" th:utext="${apiSpecInfo.apiResponseSpec}"></div>
|
||||
</div>
|
||||
<div class="api_stit api_sc">
|
||||
<h3>응답 메시지 예제</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<p class="tit">
|
||||
<span>Body</span>
|
||||
</p>
|
||||
<div class="api_sauce">
|
||||
<textarea id="sampleResponse" th:text="${apiSpecInfo.sampleResponse}"></textarea>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="api_btn">
|
||||
<a th:href="@{/apis/testbed/api(id=${apiSpecInfo.apiId})}" class="common_btn_type_1 gray">테스트</a>
|
||||
<div class="btn_gap"></div>
|
||||
<a href="#none" class="common_btn_type_1" id="requestApiKey">API 이용 신청</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
|
||||
|
||||
<th:block layout:fragment="contentScript">
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', (event) => {
|
||||
const commonOptions = {
|
||||
lineNumbers: false,
|
||||
mode: {name: 'javascript', json: true},
|
||||
theme: 'custom-dark',
|
||||
readOnly: true,
|
||||
autoCloseBrackets: true,
|
||||
matchBrackets: true,
|
||||
foldGutter: true
|
||||
};
|
||||
const sampleRequestEditor = CodeMirror.fromTextArea(document.getElementById('sampleRequest'), commonOptions);
|
||||
<script th:inline="javascript">
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// DOM Elements
|
||||
const sidebar = document.getElementById('apiSidebar');
|
||||
const mobileToggle = document.getElementById('mobileToggle');
|
||||
const mobileOverlay = document.getElementById('mobileOverlay');
|
||||
|
||||
const sampleResponseEditor = CodeMirror.fromTextArea(document.getElementById('sampleResponse'), commonOptions);
|
||||
// API Card Click Handlers
|
||||
const apiCards = document.querySelectorAll('.api-card');
|
||||
apiCards.forEach(function(card) {
|
||||
// Click handler
|
||||
card.addEventListener('click', function() {
|
||||
const href = this.getAttribute('data-href');
|
||||
if (href) {
|
||||
window.location.href = href;
|
||||
}
|
||||
});
|
||||
|
||||
// Refresh CodeMirror instances to ensure proper rendering
|
||||
sampleRequestEditor.refresh();
|
||||
sampleResponseEditor.refresh();
|
||||
// Keyboard accessibility
|
||||
card.addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
const href = this.getAttribute('data-href');
|
||||
if (href) {
|
||||
window.location.href = href;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Level 2 API Item Click Handlers
|
||||
const apiItems = document.querySelectorAll('.api-item');
|
||||
apiItems.forEach(function(item) {
|
||||
// Click handler
|
||||
item.addEventListener('click', function(e) {
|
||||
e.stopPropagation(); // Prevent parent menu-title click
|
||||
const href = this.getAttribute('data-href');
|
||||
if (href) {
|
||||
window.location.href = href;
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard accessibility
|
||||
item.addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const href = this.getAttribute('data-href');
|
||||
if (href) {
|
||||
window.location.href = href;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Category/Service Selection - removed as not needed in detail page
|
||||
|
||||
// Mobile Menu Toggle
|
||||
if (mobileToggle && sidebar && mobileOverlay) {
|
||||
mobileToggle.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
sidebar.classList.toggle('mobile-open');
|
||||
mobileOverlay.classList.toggle('active');
|
||||
|
||||
// Update button text
|
||||
if (sidebar.classList.contains('mobile-open')) {
|
||||
this.innerHTML = '✕';
|
||||
this.setAttribute('aria-label', '메뉴 닫기');
|
||||
} else {
|
||||
this.innerHTML = '☰';
|
||||
this.setAttribute('aria-label', '메뉴 열기');
|
||||
}
|
||||
});
|
||||
|
||||
// Add touch event for better mobile support
|
||||
mobileToggle.addEventListener('touchend', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.click();
|
||||
});
|
||||
|
||||
// Close menu on overlay click
|
||||
mobileOverlay.addEventListener('click', function() {
|
||||
sidebar.classList.remove('mobile-open');
|
||||
this.classList.remove('active');
|
||||
mobileToggle.innerHTML = '☰';
|
||||
mobileToggle.setAttribute('aria-label', '메뉴 열기');
|
||||
});
|
||||
} else {
|
||||
console.warn('Mobile toggle elements not found:', {
|
||||
mobileToggle: !!mobileToggle,
|
||||
sidebar: !!sidebar,
|
||||
mobileOverlay: !!mobileOverlay
|
||||
});
|
||||
}
|
||||
|
||||
// Close mobile menu on resize to desktop
|
||||
window.addEventListener('resize', function() {
|
||||
if (window.innerWidth > 768 && sidebar.classList.contains('mobile-open')) {
|
||||
sidebar.classList.remove('mobile-open');
|
||||
mobileOverlay.classList.remove('active');
|
||||
mobileToggle.innerHTML = '☰';
|
||||
mobileToggle.setAttribute('aria-label', '메뉴 열기');
|
||||
}
|
||||
});
|
||||
|
||||
// Tab functionality
|
||||
const tabButtons = document.querySelectorAll('.tab-button');
|
||||
const tabContents = document.querySelectorAll('.tab-content');
|
||||
|
||||
tabButtons.forEach(function(button) {
|
||||
button.addEventListener('click', function() {
|
||||
const targetTab = this.getAttribute('data-tab');
|
||||
|
||||
// Remove active class from all buttons and contents
|
||||
tabButtons.forEach(function(btn) {
|
||||
btn.classList.remove('active');
|
||||
});
|
||||
tabContents.forEach(function(content) {
|
||||
content.classList.remove('active');
|
||||
});
|
||||
|
||||
// Add active class to clicked button and corresponding content
|
||||
this.classList.add('active');
|
||||
document.getElementById(targetTab + '-tab').classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// Swagger UI functionality for testbed tab
|
||||
let swaggerInitialized = false;
|
||||
const currentApiId = /*[[${apiSpecInfo.apiId}]]*/ 'default';
|
||||
|
||||
// Initialize Swagger UI when testbed tab is clicked
|
||||
tabButtons.forEach(function(button) {
|
||||
button.addEventListener('click', function() {
|
||||
const targetTab = this.getAttribute('data-tab');
|
||||
if (targetTab === 'testbed' && !swaggerInitialized) {
|
||||
initializeSwagger(currentApiId);
|
||||
swaggerInitialized = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function initializeSwagger(apiId) {
|
||||
if (!apiId || apiId === 'default') {
|
||||
document.getElementById('swagger-ui').innerHTML = '<p>API 정보를 불러올 수 없습니다.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
const swaggerUrl = `/api/apis/${apiId}/swagger.json`;
|
||||
|
||||
// Load Swagger UI scripts dynamically
|
||||
const loadScript = (src) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
script.src = src;
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
document.body.appendChild(script);
|
||||
});
|
||||
};
|
||||
|
||||
// Load required scripts
|
||||
Promise.all([
|
||||
loadScript(/*[[@{/plugins/swaggerUI/swagger-ui-bundle.js}]]*/ ''),
|
||||
loadScript(/*[[@{/plugins/swaggerUI/swagger-ui-standalone-preset.js}]]*/ '')
|
||||
]).then(() => {
|
||||
// Load code template script
|
||||
return loadScript(/*[[@{/js/code_template.js}]]*/ '');
|
||||
}).then(() => {
|
||||
// Initialize Swagger UI
|
||||
const generateSnippet = (parsedRequest, language) => {
|
||||
let result;
|
||||
try {
|
||||
$.ajax({
|
||||
url: /*[[@{/api/sample_code/}]]*/ '' + language,
|
||||
type: 'POST',
|
||||
async: false,
|
||||
contentType: 'application/json',
|
||||
headers: {
|
||||
'X-XSRF-TOKEN': /*[[${_csrf.token}]]*/ ''
|
||||
},
|
||||
data: JSON.stringify(parsedRequest),
|
||||
success: function(data) {
|
||||
result = data;
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
console.error('Error:', errorThrown);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const SnippetGeneratorPlugin = {
|
||||
fn: {
|
||||
requestSnippetGenerator_csharp: (request) => {
|
||||
var parsedRequest = parseSwaggerRequest(request);
|
||||
return generateSnippet(parsedRequest, 'csharp');
|
||||
},
|
||||
requestSnippetGenerator_ecma5: (request) => {
|
||||
var parsedRequest = parseSwaggerRequest(request);
|
||||
return generateSnippet(parsedRequest, 'ecma5');
|
||||
},
|
||||
requestSnippetGenerator_java: (request) => {
|
||||
var parsedRequest = parseSwaggerRequest(request);
|
||||
return generateSnippet(parsedRequest, 'java');
|
||||
},
|
||||
requestSnippetGenerator_python: (request) => {
|
||||
var parsedRequest = parseSwaggerRequest(request);
|
||||
return generateSnippet(parsedRequest, 'python');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Clear existing Swagger UI
|
||||
document.getElementById('swagger-ui').innerHTML = '';
|
||||
|
||||
// Initialize Swagger UI
|
||||
const ui = SwaggerUIBundle({
|
||||
url: swaggerUrl,
|
||||
dom_id: '#swagger-ui',
|
||||
requestInterceptor: function(req) {
|
||||
if (!req.loadSpec) {
|
||||
const urlPath = new URL(req.url).pathname;
|
||||
const method = req.method.toLowerCase();
|
||||
|
||||
const spec = ui.spec();
|
||||
const specString = JSON.stringify(spec);
|
||||
const specData = JSON.parse(specString);
|
||||
|
||||
if (specData && specData.json && specData.json.paths && specData.json.paths[urlPath]) {
|
||||
const operation = specData.json.paths[urlPath][method];
|
||||
if (operation && operation['x-original-api-id']) {
|
||||
req.headers['original-api-id'] = operation['x-original-api-id'];
|
||||
}
|
||||
}
|
||||
|
||||
req.headers['original-url'] = req.url;
|
||||
req.headers['X-XSRF-TOKEN'] = /*[[${_csrf.token}]]*/ '';
|
||||
|
||||
if ($('#apps').val() != null) {
|
||||
req.headers['client-id'] = $('#apps').val();
|
||||
}
|
||||
|
||||
req.url = window.location.origin + /*[[@{/api/call-api}]]*/ '';
|
||||
}
|
||||
return req;
|
||||
},
|
||||
showMutatedRequest: false,
|
||||
validatorUrl: '',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset.slice(1)
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl,
|
||||
SnippetGeneratorPlugin
|
||||
],
|
||||
layout: 'StandaloneLayout',
|
||||
requestSnippetsEnabled: true,
|
||||
requestSnippets: {
|
||||
generators: {
|
||||
'csharp': {
|
||||
title: 'C#',
|
||||
syntax: 'csharp'
|
||||
},
|
||||
'ecma5': {
|
||||
title: 'ECMA5',
|
||||
syntax: 'javascript'
|
||||
},
|
||||
'java': {
|
||||
title: 'Java',
|
||||
syntax: 'java'
|
||||
},
|
||||
'python': {
|
||||
title: 'Python',
|
||||
syntax: 'python'
|
||||
}
|
||||
},
|
||||
defaultExpanded: true,
|
||||
languages: null
|
||||
}
|
||||
});
|
||||
window.ui = ui;
|
||||
}).catch(error => {
|
||||
console.error('Failed to load Swagger UI:', error);
|
||||
document.getElementById('swagger-ui').innerHTML = '<p>Swagger UI를 불러오는 중 오류가 발생했습니다.</p>';
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</th:block>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -1,153 +1,250 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_base_layout}">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout/kjbank_base_layout}">
|
||||
|
||||
<body>
|
||||
|
||||
<section layout:fragment="contentFragment" class="content">
|
||||
<th:block layout:fragment="contentFragment">
|
||||
<section class="api-market-container">
|
||||
|
||||
<div class="content_wrap">
|
||||
<div class="api_service_type m-only">
|
||||
<img th:src="@{/img/bg_api_visual03.png}" alt="Kbank API 서비스">
|
||||
<div class="service_txt">
|
||||
<p>Kbank API 서비스</p>
|
||||
<span>
|
||||
API를 활용해 당신의 비즈니스 모델을<br/>
|
||||
혁신하여 창의적이고 차별화된 서비스를<br/>
|
||||
제작하세요.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="api_service_type pc-only">
|
||||
<img th:src="@{/img/bg_api_visual02.png}" alt="Kbank API 서비스">
|
||||
<div class="service_txt">
|
||||
<p>Kbank API 서비스</p>
|
||||
<span>
|
||||
Kbank가 제공하는 다양한 API를 소개합니다.<br/>
|
||||
API를 활용해 당신의 비즈니스 모델을 혁신하여 창의적이고<br/>
|
||||
차별화된 서비스를 제작하세요.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="api_inner api_pd">
|
||||
<div class="main_keyword">
|
||||
<div class="search_type2">
|
||||
<form id="searchForm" th:action="@{/apis}" method="get">
|
||||
<div class="search-container2">
|
||||
<input type="hidden" name="groupIds" th:value="${#strings.replace(search.groupIds, '[', '').replace(']', '')}"/>
|
||||
<input type="text" class="search-box2" name="keyword" th:value="${search.keyword}" placeholder="어떤 API를 찾고 계신가요?">
|
||||
<button class="search-button2" type="submit"></button>
|
||||
<!-- Sidebar Navigation -->
|
||||
<aside class="api-market-sidebar" id="apiSidebar">
|
||||
<nav class="api-sidebar-nav">
|
||||
<!-- All APIs -->
|
||||
<div class="menu-section">
|
||||
<div class="menu-title" th:classappend="${selected == '-1'} ? 'active'" data-group="">
|
||||
전체
|
||||
<span class="api-count" th:text="${totalApiCount}">0</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Service Categories -->
|
||||
<div class="menu-section" th:each="service : ${services}">
|
||||
<div class="menu-title"
|
||||
th:classappend="${selected == service.id} ? 'active'"
|
||||
th:data-group="${service.id}">
|
||||
[[${service.groupName}]]
|
||||
<span class="api-count" th:text="${service.apiGroupApiList.size()}">0</span>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- Mobile Menu Toggle -->
|
||||
<button class="api-mobile-toggle" id="mobileToggle" aria-label="메뉴 열기">
|
||||
☰
|
||||
</button>
|
||||
|
||||
<!-- Mobile Overlay -->
|
||||
<div class="api-mobile-overlay" id="mobileOverlay"></div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="api-market-content">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="api-market-header">
|
||||
<div class="api-market-title">
|
||||
<h1>오픈API</h1>
|
||||
<h2>API 마켓</h2>
|
||||
</div>
|
||||
<div class="api-market-search">
|
||||
<form id="searchForm" th:action="@{/apis}" method="get">
|
||||
<input type="hidden" name="groupIds" th:value="${search.groupIds != null and !search.groupIds.isEmpty() ? search.groupIds[0] : ''}"/>
|
||||
<input type="text"
|
||||
class="search-input"
|
||||
name="keyword"
|
||||
th:value="${search.keyword}"
|
||||
placeholder="어떤 API를 찾고 계신가요?">
|
||||
<button type="submit" class="search-submit-btn" aria-label="검색">
|
||||
<span class="search-icon">🔍</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="api_keyword">
|
||||
<ul class="keyword_list tab_list grt">
|
||||
<li>
|
||||
<a href="#">전체<span class="number" th:text="${selectedApiCount}" th:classppend="${selected == '-1'}?'active'"></span></a>
|
||||
</li>
|
||||
<li th:each="item : ${services}">
|
||||
<a href="#" th:data-group="${item.id}" th:classappend="${selected == item.id}?'active'">
|
||||
[[${item.groupName}]]<span class="number" th:text="${item.apiGroupApiList.size()}"></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Result Count -->
|
||||
<div class="api-result-count">
|
||||
총 <strong th:text="${selectedApiCount}">0</strong>건
|
||||
</div>
|
||||
<div class="m-only">
|
||||
<div class="api_info_container card_top">
|
||||
<div class="api_box" th:each="api : ${apis}" th:data-href="@{/apis/detail(id=${api.apiId})}">
|
||||
<div class="api_info_box">
|
||||
<div class="api_list" th:data-href="@{/apis/detail(id=${api.apiId})}">
|
||||
<p class="txt1" th:text="${api.apiName}"></p>
|
||||
<p class="txt2" th:text="${api.apiSimpleDescription}"></p>
|
||||
</div>
|
||||
<div class="api_img" style="height: 48px;">
|
||||
<img th:if="${#strings.isEmpty(api.mainIcon) == false}" th:src="${api.mainIcon}" alt="">
|
||||
</div>
|
||||
|
||||
<!-- API Cards Grid -->
|
||||
<div class="api-card-grid" th:if="${apis != null and !apis.isEmpty()}">
|
||||
<div class="api-card"
|
||||
th:each="api : ${apis}"
|
||||
th:data-href="@{/apis/detail(id=${api.apiId})}"
|
||||
role="button"
|
||||
tabindex="0">
|
||||
<div>
|
||||
<!-- Card Header with Badge -->
|
||||
<div class="api-card-header">
|
||||
<span class="api-card-badge" th:if="${api.apiGroupName}" th:text="${api.apiGroupName}">
|
||||
카테고리
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Card Title -->
|
||||
<h3 class="api-card-title" th:text="${api.apiName}">
|
||||
API 이름
|
||||
</h3>
|
||||
|
||||
<!-- Card Description -->
|
||||
<p class="api-card-description" th:text="${api.apiSimpleDescription ?: 'API 설명이 없습니다.'}">
|
||||
API 설명
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Card Icon -->
|
||||
<div class="api-card-icon">
|
||||
<img th:if="${api.mainIcon}"
|
||||
th:src="${api.mainIcon}"
|
||||
th:alt="${api.apiName}"
|
||||
onerror="this.style.display='none'; this.parentElement.innerHTML='📄'">
|
||||
<span th:if="${#strings.isEmpty(api.mainIcon)}">📄</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pc-only">
|
||||
<div class="ser_result" >총 <span>[[${selectedApiCount}]]건</span> 검색</div>
|
||||
<div class="api_info_container box_list mt_0">
|
||||
<div class="api_box" th:each="api : ${apis}" th:data-href="@{/apis/detail(id=${api.apiId})}">
|
||||
<div class="api_info_box pc-only">
|
||||
<div class="api_list" th:data-href="@{/apis/detail(id=${api.apiId})}">
|
||||
<p class="txt1" th:text="${api.apiName}"></p>
|
||||
<p class="txt2" th:text="${api.apiSimpleDescription}"></p>
|
||||
</div>
|
||||
<div class="api_img">
|
||||
<a th:href="@{/apis/detail(id=${api.apiId})}" class="btn btn-primary"><img th:if="${#strings.isEmpty(api.mainIcon) == false}" th:src="${api.mainIcon}" alt=""></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div class="api-empty-state" th:if="${apis == null or apis.isEmpty()}">
|
||||
<div class="empty-icon">🔍</div>
|
||||
<h3>검색 결과가 없습니다</h3>
|
||||
<p>다른 키워드로 검색하거나 카테고리를 변경해 보세요.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
</section>
|
||||
</th:block>
|
||||
|
||||
</body>
|
||||
|
||||
|
||||
<th:block layout:fragment="contentScript">
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', (event) => {
|
||||
const apiListItems = document.querySelectorAll('.api_list');
|
||||
apiListItems.forEach(function(item) {
|
||||
item.addEventListener('click', function() {
|
||||
var href = this.getAttribute('data-href');
|
||||
if (href) {
|
||||
window.location.href = href;
|
||||
}
|
||||
});
|
||||
});
|
||||
<script th:inline="javascript">
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// DOM Elements
|
||||
const sidebar = document.getElementById('apiSidebar');
|
||||
const mobileToggle = document.getElementById('mobileToggle');
|
||||
const mobileOverlay = document.getElementById('mobileOverlay');
|
||||
const searchForm = document.getElementById('searchForm');
|
||||
const groupIdsInput = searchForm.querySelector('input[name="groupIds"]');
|
||||
const searchInput = searchForm.querySelector('input[name="keyword"]');
|
||||
|
||||
const apiItems = document.querySelectorAll('.api_box');
|
||||
apiItems.forEach(function(item) {
|
||||
item.removeEventListener('click',null);
|
||||
item.addEventListener('click', function() {
|
||||
// API Card Click Handlers
|
||||
const apiCards = document.querySelectorAll('.api-card');
|
||||
apiCards.forEach(function(card) {
|
||||
// Click handler
|
||||
card.addEventListener('click', function() {
|
||||
const href = this.getAttribute('data-href');
|
||||
if (href) {
|
||||
window.location.href = href;
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard accessibility
|
||||
card.addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
const href = this.getAttribute('data-href');
|
||||
if (href) {
|
||||
window.location.href = href;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var keywordListItems = document.querySelectorAll('.keyword_list li');
|
||||
var form = document.getElementById('searchForm');
|
||||
var groupIdsInput = form.querySelector('input[name="groupIds"]');
|
||||
// Category/Service Selection
|
||||
const menuTitles = document.querySelectorAll('.menu-title');
|
||||
menuTitles.forEach(function(title) {
|
||||
title.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
keywordListItems.forEach(function(item) {
|
||||
item.addEventListener('click', function(e) {
|
||||
e.preventDefault(); // Prevent default anchor behavior
|
||||
var anchor = this.querySelector('a');
|
||||
var groupId = anchor.getAttribute('data-group');
|
||||
const groupId = this.getAttribute('data-group');
|
||||
|
||||
// Update groupIds input
|
||||
if (groupId) {
|
||||
groupId = groupId.replace(/[\[\]]/g, '');
|
||||
}
|
||||
if (groupId) {
|
||||
// If specific group is selected, create/set input value
|
||||
if (!groupIdsInput) {
|
||||
groupIdsInput = document.createElement('input');
|
||||
groupIdsInput.type = 'hidden';
|
||||
groupIdsInput.name = 'groupIds';
|
||||
form.appendChild(groupIdsInput);
|
||||
}
|
||||
groupIdsInput.value = groupId;
|
||||
} else {
|
||||
if (groupIdsInput) {
|
||||
groupIdsInput.remove();
|
||||
}
|
||||
groupIdsInput.value = '';
|
||||
}
|
||||
|
||||
form.submit();
|
||||
// Submit form
|
||||
searchForm.submit();
|
||||
});
|
||||
});
|
||||
|
||||
// Search on Enter key
|
||||
searchInput.addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
searchForm.submit();
|
||||
}
|
||||
});
|
||||
|
||||
// Mobile Menu Toggle
|
||||
if (mobileToggle && sidebar && mobileOverlay) {
|
||||
mobileToggle.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
sidebar.classList.toggle('mobile-open');
|
||||
mobileOverlay.classList.toggle('active');
|
||||
|
||||
// Update button text
|
||||
if (sidebar.classList.contains('mobile-open')) {
|
||||
this.innerHTML = '✕';
|
||||
this.setAttribute('aria-label', '메뉴 닫기');
|
||||
} else {
|
||||
this.innerHTML = '☰';
|
||||
this.setAttribute('aria-label', '메뉴 열기');
|
||||
}
|
||||
});
|
||||
|
||||
// Add touch event for better mobile support
|
||||
mobileToggle.addEventListener('touchend', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.click();
|
||||
});
|
||||
|
||||
// Close menu on overlay click
|
||||
mobileOverlay.addEventListener('click', function() {
|
||||
sidebar.classList.remove('mobile-open');
|
||||
this.classList.remove('active');
|
||||
mobileToggle.innerHTML = '☰';
|
||||
mobileToggle.setAttribute('aria-label', '메뉴 열기');
|
||||
});
|
||||
|
||||
// Close menu on category selection (mobile)
|
||||
menuTitles.forEach(function(title) {
|
||||
title.addEventListener('click', function() {
|
||||
if (window.innerWidth <= 768) {
|
||||
sidebar.classList.remove('mobile-open');
|
||||
mobileOverlay.classList.remove('active');
|
||||
mobileToggle.innerHTML = '☰';
|
||||
mobileToggle.setAttribute('aria-label', '메뉴 열기');
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
console.warn('Mobile toggle elements not found:', {
|
||||
mobileToggle: !!mobileToggle,
|
||||
sidebar: !!sidebar,
|
||||
mobileOverlay: !!mobileOverlay
|
||||
});
|
||||
}
|
||||
|
||||
// Close mobile menu on resize to desktop
|
||||
window.addEventListener('resize', function() {
|
||||
if (window.innerWidth > 768 && sidebar.classList.contains('mobile-open')) {
|
||||
sidebar.classList.remove('mobile-open');
|
||||
mobileOverlay.classList.remove('active');
|
||||
mobileToggle.innerHTML = '☰';
|
||||
mobileToggle.setAttribute('aria-label', '메뉴 열기');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</th:block>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -15,9 +15,8 @@
|
||||
<meta content="max-age=0, public" http-equiv="Cache-Control"/>
|
||||
<meta content="index, follow" name="robots"/>
|
||||
<meta content="https://www.eactive.co.kr/" property="og:url"/>
|
||||
<link rel="icon" type="image/ico" th:href="@{/favicon_16px.png}">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/style2.css}">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/common2.css}">
|
||||
<link rel="icon" type="image/ico" th:href="@{/favicon.ico}">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/main.css}">
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrap" class="wrap_top">
|
||||
|
||||
@@ -15,9 +15,8 @@
|
||||
<meta content="max-age=0, public" http-equiv="Cache-Control"/>
|
||||
<meta content="index, follow" name="robots"/>
|
||||
<meta content="https://www.eactive.co.kr/" property="og:url"/>
|
||||
<link rel="icon" type="image/ico" th:href="@{/favicon_16px.png}">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/style2.css}">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/common2.css}">
|
||||
<link rel="icon" type="image/ico" th:href="@{/favicon.ico}">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/main.css}">
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrap" class="wrap_top">
|
||||
|
||||
@@ -15,9 +15,8 @@
|
||||
<meta content="max-age=0, public" http-equiv="Cache-Control"/>
|
||||
<meta content="index, follow" name="robots"/>
|
||||
<meta content="https://www.eactive.co.kr/" property="og:url"/>
|
||||
<link rel="icon" type="image/ico" th:href="@{/favicon_16px.png}">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/style2.css}">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/common2.css}">
|
||||
<link rel="icon" type="image/ico" th:href="@{/favicon.ico}">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/main.css}">
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrap" class="wrap_top">
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<link rel="stylesheet" th:href="@{/plugins/codemirror/theme/monokai.css}" type="text/css"/>
|
||||
<link rel="stylesheet" th:href="@{/plugins/summernote/summernote-lite.css}" type="text/css" />
|
||||
<link rel="stylesheet" th:href="@{/plugins/jquery-ui/jquery-ui.min.css}" />
|
||||
<link rel="icon" type="image/ico" th:href="@{/favicon_16px.png}">
|
||||
<link rel="icon" type="image/ico" th:href="@{/favicon.ico}">
|
||||
|
||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<nav class="header-center">
|
||||
<ul class="nav-menu">
|
||||
<li><a href="#docs" class="nav-link">서비스 소개</a></li>
|
||||
<li><a href="#api-market" class="nav-link">오픈 API</a></li>
|
||||
<li><a href="/apis" class="nav-link">오픈 API</a></li>
|
||||
<li><a href="#playground" class="nav-link">고객지원</a></li>
|
||||
<li><a href="#community" class="nav-link">MY</a></li>
|
||||
</ul>
|
||||
@@ -82,7 +82,7 @@
|
||||
<nav class="drawer-nav">
|
||||
<ul class="drawer-menu">
|
||||
<li><a href="#docs" class="drawer-link">서비스 소개</a></li>
|
||||
<li><a href="#api-market" class="drawer-link">오픈 API</a></li>
|
||||
<li><a href="/apis" class="drawer-link">오픈 API</a></li>
|
||||
<li><a href="#playground" class="drawer-link">고객지원</a></li>
|
||||
<li><a href="#community" class="drawer-link">MY</a></li>
|
||||
</ul>
|
||||
|
||||
Reference in New Issue
Block a user