mirror of
https://github.com/cna-bootcamp/phonebill.git
synced 2026-06-13 12:09:10 +00:00
first
This commit is contained in:
@@ -0,0 +1,485 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>로그인 - 통신요금 관리 서비스</title>
|
||||
<style>
|
||||
/* CSS Variables - Style Guide */
|
||||
:root {
|
||||
/* Primary Colors */
|
||||
--primary-50: #EBF8FF;
|
||||
--primary-100: #BEE3F8;
|
||||
--primary-200: #90CDF4;
|
||||
--primary-300: #63B3ED;
|
||||
--primary-400: #4299E1;
|
||||
--primary-500: #3182CE;
|
||||
--primary-600: #2B77CB;
|
||||
--primary-700: #2C5282;
|
||||
--primary-800: #2A4365;
|
||||
--primary-900: #1A365D;
|
||||
|
||||
/* Gray Colors */
|
||||
--gray-50: #F9FAFB;
|
||||
--gray-100: #F3F4F6;
|
||||
--gray-200: #E5E7EB;
|
||||
--gray-300: #D1D5DB;
|
||||
--gray-400: #9CA3AF;
|
||||
--gray-500: #6B7280;
|
||||
--gray-600: #4B5563;
|
||||
--gray-700: #374151;
|
||||
--gray-800: #1F2937;
|
||||
--gray-900: #111827;
|
||||
|
||||
/* Status Colors */
|
||||
--success-500: #38A169;
|
||||
--error-500: #E53E3E;
|
||||
--warning-500: #ED8936;
|
||||
|
||||
/* Spacing */
|
||||
--space-1: 0.25rem;
|
||||
--space-2: 0.5rem;
|
||||
--space-3: 0.75rem;
|
||||
--space-4: 1rem;
|
||||
--space-5: 1.25rem;
|
||||
--space-6: 1.5rem;
|
||||
--space-8: 2rem;
|
||||
--space-10: 2.5rem;
|
||||
--space-12: 3rem;
|
||||
|
||||
/* Typography */
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.875rem;
|
||||
--text-base: 1rem;
|
||||
--text-lg: 1.125rem;
|
||||
--text-xl: 1.25rem;
|
||||
--text-2xl: 1.5rem;
|
||||
--text-3xl: 1.875rem;
|
||||
--text-4xl: 2.25rem;
|
||||
|
||||
--font-normal: 400;
|
||||
--font-medium: 500;
|
||||
--font-semibold: 600;
|
||||
--font-bold: 700;
|
||||
}
|
||||
|
||||
/* Reset & Base */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif;
|
||||
font-size: var(--text-base);
|
||||
line-height: 1.5;
|
||||
color: var(--gray-700);
|
||||
background-color: var(--gray-50);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Container */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
padding: var(--space-4);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 480px;
|
||||
padding: var(--space-8);
|
||||
}
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: var(--space-8);
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background: linear-gradient(135deg, var(--primary-500) 0%, var(--primary-600) 100%);
|
||||
border-radius: 20px;
|
||||
margin: 0 auto var(--space-4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: var(--text-2xl);
|
||||
font-weight: var(--font-bold);
|
||||
}
|
||||
|
||||
.service-title {
|
||||
font-size: var(--text-2xl);
|
||||
font-weight: var(--font-bold);
|
||||
color: var(--gray-900);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.service-subtitle {
|
||||
font-size: var(--text-base);
|
||||
color: var(--gray-500);
|
||||
}
|
||||
|
||||
/* Card */
|
||||
.card {
|
||||
background-color: white;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
padding: var(--space-8);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
/* Form */
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-6);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-medium);
|
||||
color: var(--gray-700);
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
padding: var(--space-4);
|
||||
border: 2px solid var(--gray-200);
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-base);
|
||||
line-height: 1.5;
|
||||
transition: all 0.2s ease-in-out;
|
||||
min-height: 52px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-500);
|
||||
box-shadow: 0 0 0 3px rgba(49, 130, 206, 0.1);
|
||||
}
|
||||
|
||||
.input::placeholder {
|
||||
color: var(--gray-400);
|
||||
}
|
||||
|
||||
.input.error {
|
||||
border-color: var(--error-500);
|
||||
}
|
||||
|
||||
/* Checkbox */
|
||||
.checkbox-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid var(--gray-300);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.checkbox:checked {
|
||||
background-color: var(--primary-500);
|
||||
border-color: var(--primary-500);
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-600);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Button */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-4) var(--space-6);
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
line-height: 1.5;
|
||||
transition: all 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
min-height: 52px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--primary-500) 0%, var(--primary-600) 100%);
|
||||
color: white;
|
||||
box-shadow: 0 2px 8px rgba(49, 130, 206, 0.2);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(49, 130, 206, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
background: var(--gray-300);
|
||||
color: var(--gray-500);
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Alert */
|
||||
.alert {
|
||||
padding: var(--space-4);
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-sm);
|
||||
margin-bottom: var(--space-4);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alert.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
background-color: #FEF2F2;
|
||||
border: 1px solid #FECACA;
|
||||
color: #991B1B;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
text-align: center;
|
||||
margin-top: var(--space-8);
|
||||
padding-top: var(--space-6);
|
||||
border-top: 1px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--gray-400);
|
||||
}
|
||||
|
||||
/* Loading */
|
||||
.loading {
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.loading::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-top: 2px solid white;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
<div class="logo">📱</div>
|
||||
<h1 class="service-title">통신요금 관리</h1>
|
||||
<p class="service-subtitle">간편하고 안전한 요금 관리 서비스</p>
|
||||
</div>
|
||||
|
||||
<!-- Login Form Card -->
|
||||
<div class="card">
|
||||
<!-- Error Alert -->
|
||||
<div id="errorAlert" class="alert alert-error">
|
||||
<span id="errorMessage"></span>
|
||||
</div>
|
||||
|
||||
<form class="form" id="loginForm">
|
||||
<!-- ID Input -->
|
||||
<div class="form-group">
|
||||
<label for="userId" class="label">아이디</label>
|
||||
<input
|
||||
type="text"
|
||||
id="userId"
|
||||
name="userId"
|
||||
class="input"
|
||||
placeholder="아이디를 입력하세요"
|
||||
required
|
||||
autocomplete="username"
|
||||
aria-describedby="userId-error"
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Password Input -->
|
||||
<div class="form-group">
|
||||
<label for="password" class="label">비밀번호</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
class="input"
|
||||
placeholder="비밀번호를 입력하세요"
|
||||
required
|
||||
autocomplete="current-password"
|
||||
aria-describedby="password-error"
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Auto Login Checkbox -->
|
||||
<div class="checkbox-group">
|
||||
<input type="checkbox" id="autoLogin" name="autoLogin" class="checkbox">
|
||||
<label for="autoLogin" class="checkbox-label">자동 로그인</label>
|
||||
</div>
|
||||
|
||||
<!-- Login Button -->
|
||||
<button type="submit" class="btn btn-primary" id="loginBtn">
|
||||
로그인
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="footer">
|
||||
<p class="footer-text">© 2025 통신요금 관리 서비스. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Login form validation and submission
|
||||
const loginForm = document.getElementById('loginForm');
|
||||
const loginBtn = document.getElementById('loginBtn');
|
||||
const errorAlert = document.getElementById('errorAlert');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
const userIdInput = document.getElementById('userId');
|
||||
const passwordInput = document.getElementById('password');
|
||||
|
||||
let loginAttempts = 0;
|
||||
const maxAttempts = 5;
|
||||
|
||||
// Show error message
|
||||
function showError(message) {
|
||||
errorMessage.textContent = message;
|
||||
errorAlert.classList.add('show');
|
||||
}
|
||||
|
||||
// Hide error message
|
||||
function hideError() {
|
||||
errorAlert.classList.remove('show');
|
||||
}
|
||||
|
||||
// Validate form inputs
|
||||
function validateForm() {
|
||||
const userId = userIdInput.value.trim();
|
||||
const password = passwordInput.value.trim();
|
||||
|
||||
if (!userId) {
|
||||
showError('아이디를 입력해주세요.');
|
||||
userIdInput.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!password) {
|
||||
showError('비밀번호를 입력해주세요.');
|
||||
passwordInput.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle form submission
|
||||
loginForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
hideError();
|
||||
|
||||
if (!validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check login attempts
|
||||
if (loginAttempts >= maxAttempts) {
|
||||
showError('로그인 시도 횟수를 초과했습니다. 30분 후 다시 시도해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
loginBtn.classList.add('loading');
|
||||
loginBtn.disabled = true;
|
||||
|
||||
// Simulate login API call
|
||||
setTimeout(() => {
|
||||
const userId = userIdInput.value.trim();
|
||||
const password = passwordInput.value.trim();
|
||||
|
||||
// Demo login - accept any ID/password for prototype
|
||||
if (userId && password) {
|
||||
// Success - redirect to main page
|
||||
alert('로그인 성공! 메인 화면으로 이동합니다.');
|
||||
window.location.href = '02-메인화면.html';
|
||||
} else {
|
||||
// Failure
|
||||
loginAttempts++;
|
||||
const remainingAttempts = maxAttempts - loginAttempts;
|
||||
|
||||
if (remainingAttempts > 0) {
|
||||
showError(`로그인에 실패했습니다. ${remainingAttempts}회 더 시도할 수 있습니다.`);
|
||||
} else {
|
||||
showError('로그인 시도 횟수를 초과했습니다. 30분 후 다시 시도해주세요.');
|
||||
}
|
||||
|
||||
loginBtn.classList.remove('loading');
|
||||
loginBtn.disabled = false;
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// Input validation feedback
|
||||
[userIdInput, passwordInput].forEach(input => {
|
||||
input.addEventListener('input', function() {
|
||||
this.classList.remove('error');
|
||||
hideError();
|
||||
});
|
||||
|
||||
input.addEventListener('blur', function() {
|
||||
if (!this.value.trim()) {
|
||||
this.classList.add('error');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Enter key handling for accessibility
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter' && e.target.tagName !== 'BUTTON') {
|
||||
loginForm.requestSubmit();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,537 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>메인 화면 - 통신요금 관리 서비스</title>
|
||||
<style>
|
||||
/* CSS Variables - Style Guide */
|
||||
:root {
|
||||
/* Primary Colors */
|
||||
--primary-50: #EBF8FF;
|
||||
--primary-100: #BEE3F8;
|
||||
--primary-200: #90CDF4;
|
||||
--primary-300: #63B3ED;
|
||||
--primary-400: #4299E1;
|
||||
--primary-500: #3182CE;
|
||||
--primary-600: #2B77CB;
|
||||
--primary-700: #2C5282;
|
||||
--primary-800: #2A4365;
|
||||
--primary-900: #1A365D;
|
||||
|
||||
/* Gray Colors */
|
||||
--gray-50: #F9FAFB;
|
||||
--gray-100: #F3F4F6;
|
||||
--gray-200: #E5E7EB;
|
||||
--gray-300: #D1D5DB;
|
||||
--gray-400: #9CA3AF;
|
||||
--gray-500: #6B7280;
|
||||
--gray-600: #4B5563;
|
||||
--gray-700: #374151;
|
||||
--gray-800: #1F2937;
|
||||
--gray-900: #111827;
|
||||
|
||||
/* Status Colors */
|
||||
--success-50: #F0FFF4;
|
||||
--success-500: #38A169;
|
||||
--error-500: #E53E3E;
|
||||
--warning-50: #FFFAF0;
|
||||
--warning-500: #ED8936;
|
||||
|
||||
/* Spacing */
|
||||
--space-1: 0.25rem;
|
||||
--space-2: 0.5rem;
|
||||
--space-3: 0.75rem;
|
||||
--space-4: 1rem;
|
||||
--space-5: 1.25rem;
|
||||
--space-6: 1.5rem;
|
||||
--space-8: 2rem;
|
||||
--space-10: 2.5rem;
|
||||
--space-12: 3rem;
|
||||
|
||||
/* Typography */
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.875rem;
|
||||
--text-base: 1rem;
|
||||
--text-lg: 1.125rem;
|
||||
--text-xl: 1.25rem;
|
||||
--text-2xl: 1.5rem;
|
||||
--text-3xl: 1.875rem;
|
||||
--text-4xl: 2.25rem;
|
||||
|
||||
--font-normal: 400;
|
||||
--font-medium: 500;
|
||||
--font-semibold: 600;
|
||||
--font-bold: 700;
|
||||
}
|
||||
|
||||
/* Reset & Base */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif;
|
||||
font-size: var(--text-base);
|
||||
line-height: 1.5;
|
||||
color: var(--gray-700);
|
||||
background-color: var(--gray-50);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Container */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
margin: 0 auto;
|
||||
padding: var(--space-4);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 600px;
|
||||
padding: var(--space-6);
|
||||
}
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--space-4) 0;
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: linear-gradient(135deg, var(--primary-500) 0%, var(--primary-600) 100%);
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-bold);
|
||||
}
|
||||
|
||||
.service-title {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-bold);
|
||||
color: var(--gray-900);
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
padding: var(--space-2) var(--space-4);
|
||||
background-color: white;
|
||||
color: var(--gray-600);
|
||||
border: 1px solid var(--gray-300);
|
||||
border-radius: 8px;
|
||||
font-size: var(--text-sm);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.logout-btn:hover {
|
||||
background-color: var(--gray-50);
|
||||
color: var(--gray-700);
|
||||
}
|
||||
|
||||
/* Welcome Section */
|
||||
.welcome-section {
|
||||
background: linear-gradient(135deg, var(--primary-500) 0%, var(--primary-600) 100%);
|
||||
color: white;
|
||||
padding: var(--space-8);
|
||||
border-radius: 20px;
|
||||
margin-bottom: var(--space-8);
|
||||
box-shadow: 0 4px 20px rgba(49, 130, 206, 0.2);
|
||||
}
|
||||
|
||||
.welcome-title {
|
||||
font-size: var(--text-2xl);
|
||||
font-weight: var(--font-bold);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.user-info {
|
||||
font-size: var(--text-base);
|
||||
opacity: 0.9;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.user-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.user-details {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
.user-phone {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-semibold);
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
padding: var(--space-2) var(--space-4);
|
||||
border-radius: 999px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.current-product {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.current-product {
|
||||
align-items: flex-end;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.current-product-label {
|
||||
font-size: var(--text-sm);
|
||||
opacity: 0.8;
|
||||
font-weight: var(--font-normal);
|
||||
}
|
||||
|
||||
.current-product-name {
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
padding: var(--space-1) var(--space-3);
|
||||
border-radius: 8px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Service Menu */
|
||||
.service-menu {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.menu-title {
|
||||
font-size: var(--text-xl);
|
||||
font-weight: var(--font-bold);
|
||||
color: var(--gray-900);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.menu-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.menu-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: var(--space-6);
|
||||
}
|
||||
}
|
||||
|
||||
/* Menu Card */
|
||||
.menu-card {
|
||||
background-color: white;
|
||||
border-radius: 16px;
|
||||
padding: var(--space-6);
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid var(--gray-100);
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.menu-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.menu-card.disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
background-color: var(--gray-50);
|
||||
}
|
||||
|
||||
.menu-card.disabled:hover {
|
||||
transform: none;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--text-2xl);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.menu-icon.bill {
|
||||
background: linear-gradient(135deg, #10B981 0%, #059669 100%);
|
||||
}
|
||||
|
||||
.menu-icon.product {
|
||||
background: linear-gradient(135deg, #F59E0B 0%, #D97706 100%);
|
||||
}
|
||||
|
||||
.menu-title-text {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--gray-900);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.menu-description {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-500);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Access Denied Modal */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal {
|
||||
background-color: white;
|
||||
border-radius: 16px;
|
||||
padding: var(--space-8);
|
||||
margin: var(--space-4);
|
||||
max-width: 320px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.modal-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background-color: var(--error-500);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto var(--space-4);
|
||||
color: white;
|
||||
font-size: var(--text-2xl);
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--gray-900);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.modal-message {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-600);
|
||||
margin-bottom: var(--space-6);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.modal-btn {
|
||||
padding: var(--space-3) var(--space-6);
|
||||
background-color: var(--primary-500);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-medium);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.modal-btn:hover {
|
||||
background-color: var(--primary-600);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
<div class="header-left">
|
||||
<div class="logo">📱</div>
|
||||
<h1 class="service-title">통신요금 관리</h1>
|
||||
</div>
|
||||
<button class="logout-btn" onclick="handleLogout()">로그아웃</button>
|
||||
</div>
|
||||
|
||||
<!-- Welcome Section -->
|
||||
<div class="welcome-section">
|
||||
<h2 class="welcome-title">안녕하세요!</h2>
|
||||
<p class="user-info">통신요금 관리 서비스에 오신 것을 환영합니다.</p>
|
||||
<div class="user-details">
|
||||
<span class="user-phone">010-1234-5678</span>
|
||||
<div class="current-product">
|
||||
<span class="current-product-label">현재 상품</span>
|
||||
<span class="current-product-name" id="currentProductName">5G 프리미엄 플랜</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Service Menu -->
|
||||
<div class="service-menu">
|
||||
<h3 class="menu-title">서비스 메뉴</h3>
|
||||
|
||||
<div class="menu-grid">
|
||||
<!-- 요금 조회 메뉴 -->
|
||||
<a href="03-요금조회메뉴.html" class="menu-card" id="billCard">
|
||||
<div class="menu-icon bill">📊</div>
|
||||
<h4 class="menu-title-text">요금 조회</h4>
|
||||
<p class="menu-description">
|
||||
월별 통신요금과 사용량을<br>
|
||||
상세하게 확인할 수 있습니다.
|
||||
</p>
|
||||
</a>
|
||||
|
||||
<!-- 상품 변경 메뉴 -->
|
||||
<a href="05-상품변경메뉴.html" class="menu-card" id="productCard">
|
||||
<div class="menu-icon product">🔄</div>
|
||||
<h4 class="menu-title-text">상품 변경</h4>
|
||||
<p class="menu-description">
|
||||
현재 이용 중인 요금제를<br>
|
||||
다른 상품으로 변경할 수 있습니다.
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Access Denied Modal -->
|
||||
<div class="modal-overlay" id="accessDeniedModal">
|
||||
<div class="modal">
|
||||
<div class="modal-icon">🚫</div>
|
||||
<h3 class="modal-title">접근 권한 없음</h3>
|
||||
<p class="modal-message">해당 서비스를 이용할 권한이 없습니다.<br>고객센터로 문의해주세요.</p>
|
||||
<button class="modal-btn" onclick="closeModal()">확인</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// User permissions simulation
|
||||
const userPermissions = {
|
||||
bill: true, // 요금 조회 권한
|
||||
product: true // 상품 변경 권한
|
||||
};
|
||||
|
||||
// Initialize page
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadCurrentProduct();
|
||||
checkPermissions();
|
||||
});
|
||||
|
||||
// Load current product from localStorage
|
||||
function loadCurrentProduct() {
|
||||
try {
|
||||
const currentProduct = localStorage.getItem('currentProduct');
|
||||
if (currentProduct) {
|
||||
const product = JSON.parse(currentProduct);
|
||||
const productNameElement = document.getElementById('currentProductName');
|
||||
if (productNameElement && product.name) {
|
||||
productNameElement.textContent = product.name;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('localStorage에서 상품 정보를 불러오는 중 오류 발생:', error);
|
||||
// 기본값 유지 (5G 프리미엄 플랜)
|
||||
}
|
||||
}
|
||||
|
||||
// Check user permissions and update UI
|
||||
function checkPermissions() {
|
||||
const billCard = document.getElementById('billCard');
|
||||
const productCard = document.getElementById('productCard');
|
||||
|
||||
// 요금 조회 권한 확인
|
||||
if (!userPermissions.bill) {
|
||||
billCard.classList.add('disabled');
|
||||
billCard.href = '#';
|
||||
billCard.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
showAccessDeniedModal('요금 조회');
|
||||
});
|
||||
}
|
||||
|
||||
// 상품 변경 권한 확인
|
||||
if (!userPermissions.product) {
|
||||
productCard.classList.add('disabled');
|
||||
productCard.href = '#';
|
||||
productCard.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
showAccessDeniedModal('상품 변경');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Show access denied modal
|
||||
function showAccessDeniedModal(serviceName) {
|
||||
const modal = document.getElementById('accessDeniedModal');
|
||||
const message = modal.querySelector('.modal-message');
|
||||
message.innerHTML = `${serviceName} 서비스를 이용할 권한이 없습니다.<br>고객센터로 문의해주세요.`;
|
||||
modal.style.display = 'flex';
|
||||
}
|
||||
|
||||
// Close modal
|
||||
function closeModal() {
|
||||
const modal = document.getElementById('accessDeniedModal');
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
|
||||
// Handle logout
|
||||
function handleLogout() {
|
||||
if (confirm('로그아웃 하시겠습니까?')) {
|
||||
alert('안전하게 로그아웃되었습니다.');
|
||||
window.location.href = '01-로그인.html';
|
||||
}
|
||||
}
|
||||
|
||||
// Modal close on overlay click
|
||||
document.getElementById('accessDeniedModal').addEventListener('click', function(e) {
|
||||
if (e.target === this) {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Escape key to close modal
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,690 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>요금조회 메뉴 - 통신요금 관리 서비스</title>
|
||||
<style>
|
||||
/* CSS Variables - Style Guide */
|
||||
:root {
|
||||
/* Primary Colors */
|
||||
--primary-50: #EBF8FF;
|
||||
--primary-100: #BEE3F8;
|
||||
--primary-200: #90CDF4;
|
||||
--primary-300: #63B3ED;
|
||||
--primary-400: #4299E1;
|
||||
--primary-500: #3182CE;
|
||||
--primary-600: #2B77CB;
|
||||
--primary-700: #2C5282;
|
||||
--primary-800: #2A4365;
|
||||
--primary-900: #1A365D;
|
||||
|
||||
/* Gray Colors */
|
||||
--gray-50: #F9FAFB;
|
||||
--gray-100: #F3F4F6;
|
||||
--gray-200: #E5E7EB;
|
||||
--gray-300: #D1D5DB;
|
||||
--gray-400: #9CA3AF;
|
||||
--gray-500: #6B7280;
|
||||
--gray-600: #4B5563;
|
||||
--gray-700: #374151;
|
||||
--gray-800: #1F2937;
|
||||
--gray-900: #111827;
|
||||
|
||||
/* Status Colors */
|
||||
--success-50: #F0FFF4;
|
||||
--success-500: #38A169;
|
||||
--error-500: #E53E3E;
|
||||
--warning-50: #FFFAF0;
|
||||
--warning-500: #ED8936;
|
||||
|
||||
/* Spacing */
|
||||
--space-1: 0.25rem;
|
||||
--space-2: 0.5rem;
|
||||
--space-3: 0.75rem;
|
||||
--space-4: 1rem;
|
||||
--space-5: 1.25rem;
|
||||
--space-6: 1.5rem;
|
||||
--space-8: 2rem;
|
||||
--space-10: 2.5rem;
|
||||
--space-12: 3rem;
|
||||
|
||||
/* Typography */
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.875rem;
|
||||
--text-base: 1rem;
|
||||
--text-lg: 1.125rem;
|
||||
--text-xl: 1.25rem;
|
||||
--text-2xl: 1.5rem;
|
||||
--text-3xl: 1.875rem;
|
||||
--text-4xl: 2.25rem;
|
||||
|
||||
--font-normal: 400;
|
||||
--font-medium: 500;
|
||||
--font-semibold: 600;
|
||||
--font-bold: 700;
|
||||
}
|
||||
|
||||
/* Reset & Base */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif;
|
||||
font-size: var(--text-base);
|
||||
line-height: 1.5;
|
||||
color: var(--gray-700);
|
||||
background-color: var(--gray-50);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Container */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
margin: 0 auto;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 600px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--space-4);
|
||||
background-color: white;
|
||||
border-bottom: 1px solid var(--gray-200);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
background-color: var(--gray-100);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
color: var(--gray-700);
|
||||
font-size: var(--text-lg);
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
background-color: var(--gray-200);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: var(--text-xl);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--gray-900);
|
||||
}
|
||||
|
||||
.menu-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
background: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
color: var(--gray-600);
|
||||
font-size: var(--text-lg);
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding: var(--space-6);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-8);
|
||||
}
|
||||
|
||||
/* User Info Card */
|
||||
.user-info-card {
|
||||
background: linear-gradient(135deg, var(--primary-500) 0%, var(--primary-600) 100%);
|
||||
color: white;
|
||||
padding: var(--space-6);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 20px rgba(49, 130, 206, 0.2);
|
||||
}
|
||||
|
||||
.user-info-title {
|
||||
font-size: var(--text-sm);
|
||||
opacity: 0.9;
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.user-phone {
|
||||
font-size: var(--text-2xl);
|
||||
font-weight: var(--font-bold);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
/* Inquiry Options Card */
|
||||
.inquiry-card {
|
||||
background-color: white;
|
||||
border-radius: 16px;
|
||||
padding: var(--space-6);
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid var(--gray-100);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: var(--text-xl);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--gray-900);
|
||||
margin-bottom: var(--space-6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.card-title-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background-color: var(--primary-100);
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--primary-600);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-medium);
|
||||
color: var(--gray-700);
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.select-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.select {
|
||||
width: 100%;
|
||||
padding: var(--space-4);
|
||||
border: 2px solid var(--gray-200);
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-base);
|
||||
line-height: 1.5;
|
||||
background-color: white;
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
transition: all 0.2s ease-in-out;
|
||||
min-height: 52px;
|
||||
}
|
||||
|
||||
.select:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-500);
|
||||
box-shadow: 0 0 0 3px rgba(49, 130, 206, 0.1);
|
||||
}
|
||||
|
||||
.select-wrapper::after {
|
||||
content: "▼";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: var(--space-4);
|
||||
transform: translateY(-50%);
|
||||
color: var(--gray-400);
|
||||
font-size: var(--text-sm);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.form-help {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-500);
|
||||
margin-top: var(--space-2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.help-icon {
|
||||
color: var(--primary-500);
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
|
||||
/* Action Buttons */
|
||||
.action-buttons {
|
||||
padding: var(--space-6);
|
||||
padding-top: 0;
|
||||
display: flex;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-4) var(--space-6);
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
line-height: 1.5;
|
||||
transition: all 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
min-height: 52px;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--primary-500) 0%, var(--primary-600) 100%);
|
||||
color: white;
|
||||
box-shadow: 0 2px 8px rgba(49, 130, 206, 0.2);
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(49, 130, 206, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: white;
|
||||
color: var(--gray-700);
|
||||
border: 2px solid var(--gray-300);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: var(--gray-50);
|
||||
border-color: var(--gray-400);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Loading State */
|
||||
.btn.loading {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.btn.loading::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-top: 2px solid white;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Alert */
|
||||
.alert {
|
||||
padding: var(--space-4);
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-sm);
|
||||
margin-bottom: var(--space-4);
|
||||
display: none;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.alert.show {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
background-color: #FEF2F2;
|
||||
border: 1px solid #FECACA;
|
||||
color: #991B1B;
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
background-color: var(--warning-50);
|
||||
border: 1px solid #FED7AA;
|
||||
color: #92400E;
|
||||
}
|
||||
|
||||
.alert-icon {
|
||||
margin-top: var(--space-1);
|
||||
font-size: var(--text-base);
|
||||
}
|
||||
|
||||
.alert-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Usage Info */
|
||||
.usage-info {
|
||||
background-color: var(--gray-50);
|
||||
border-radius: 12px;
|
||||
padding: var(--space-4);
|
||||
border: 1px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.usage-title {
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-medium);
|
||||
color: var(--gray-700);
|
||||
margin-bottom: var(--space-2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.usage-list {
|
||||
list-style: none;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-600);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.usage-list li {
|
||||
position: relative;
|
||||
padding-left: var(--space-4);
|
||||
margin-bottom: var(--space-1);
|
||||
}
|
||||
|
||||
.usage-list li::before {
|
||||
content: "•";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--primary-500);
|
||||
font-weight: var(--font-bold);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
<div class="header-left">
|
||||
<button class="back-btn" onclick="goBack()" aria-label="뒤로가기">
|
||||
←
|
||||
</button>
|
||||
<h1 class="page-title">요금 조회</h1>
|
||||
</div>
|
||||
<button class="menu-btn" onclick="showMenu()" aria-label="메뉴">
|
||||
☰
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="main-content">
|
||||
<!-- User Info Card -->
|
||||
<div class="user-info-card">
|
||||
<div class="user-info-title">조회 대상 회선</div>
|
||||
<div class="user-phone">
|
||||
📱 010-1234-5678
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Inquiry Options Card -->
|
||||
<div class="inquiry-card">
|
||||
<h2 class="card-title">
|
||||
<span class="card-title-icon">📅</span>
|
||||
조회 옵션 설정
|
||||
</h2>
|
||||
|
||||
<!-- Error Alert -->
|
||||
<div id="errorAlert" class="alert alert-error">
|
||||
<span class="alert-icon">⚠️</span>
|
||||
<div class="alert-content">
|
||||
<span id="errorMessage"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Warning Alert -->
|
||||
<div id="warningAlert" class="alert alert-warning">
|
||||
<span class="alert-icon">💡</span>
|
||||
<div class="alert-content">
|
||||
<span id="warningMessage"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="inquiryForm">
|
||||
<!-- 조회월 선택 -->
|
||||
<div class="form-group">
|
||||
<label for="inquiryMonth" class="form-label">조회월 선택</label>
|
||||
<div class="select-wrapper">
|
||||
<select id="inquiryMonth" name="inquiryMonth" class="select" required>
|
||||
<option value="">조회할 월을 선택해주세요</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-help">
|
||||
<span class="help-icon">ℹ️</span>
|
||||
최근 6개월 요금 정보를 조회할 수 있습니다
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Usage Info -->
|
||||
<div class="usage-info">
|
||||
<div class="usage-title">
|
||||
<span>📋</span>
|
||||
조회 가능한 정보
|
||||
</div>
|
||||
<ul class="usage-list">
|
||||
<li>월 요금 상세 내역 (기본료, 통화료, 데이터료 등)</li>
|
||||
<li>사용량 정보 (통화시간, 데이터 사용량, SMS 등)</li>
|
||||
<li>할인 및 혜택 내역</li>
|
||||
<li>단말기 할부금 및 기타 부대비용</li>
|
||||
<li>약정 정보 및 예상 해지비용</li>
|
||||
</ul>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="action-buttons">
|
||||
<button type="button" class="btn btn-secondary" onclick="goBack()">
|
||||
취소
|
||||
</button>
|
||||
<button type="submit" form="inquiryForm" class="btn btn-primary" id="inquiryBtn">
|
||||
요금 조회
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 현재 날짜 기준으로 최근 6개월 옵션 생성
|
||||
function generateMonthOptions() {
|
||||
const select = document.getElementById('inquiryMonth');
|
||||
const now = new Date();
|
||||
|
||||
// 현재 월부터 6개월 전까지 옵션 생성
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const date = new Date(now.getFullYear(), now.getMonth() - i, 1);
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth() + 1;
|
||||
const monthStr = month.toString().padStart(2, '0');
|
||||
|
||||
const option = document.createElement('option');
|
||||
option.value = `${year}${monthStr}`;
|
||||
|
||||
if (i === 0) {
|
||||
option.textContent = `${year}년 ${month}월 (현재 월)`;
|
||||
option.selected = true; // 기본값으로 현재 월 선택
|
||||
} else {
|
||||
option.textContent = `${year}년 ${month}월`;
|
||||
}
|
||||
|
||||
select.appendChild(option);
|
||||
}
|
||||
}
|
||||
|
||||
// 폼 요소 참조
|
||||
const inquiryForm = document.getElementById('inquiryForm');
|
||||
const inquiryBtn = document.getElementById('inquiryBtn');
|
||||
const errorAlert = document.getElementById('errorAlert');
|
||||
const warningAlert = document.getElementById('warningAlert');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
const warningMessage = document.getElementById('warningMessage');
|
||||
const monthSelect = document.getElementById('inquiryMonth');
|
||||
|
||||
// 에러 메시지 표시
|
||||
function showError(message) {
|
||||
errorMessage.textContent = message;
|
||||
errorAlert.classList.add('show');
|
||||
hideWarning();
|
||||
}
|
||||
|
||||
// 경고 메시지 표시
|
||||
function showWarning(message) {
|
||||
warningMessage.textContent = message;
|
||||
warningAlert.classList.add('show');
|
||||
hideError();
|
||||
}
|
||||
|
||||
// 에러 메시지 숨기기
|
||||
function hideError() {
|
||||
errorAlert.classList.remove('show');
|
||||
}
|
||||
|
||||
// 경고 메시지 숨기기
|
||||
function hideWarning() {
|
||||
warningAlert.classList.remove('show');
|
||||
}
|
||||
|
||||
// 메시지 전체 숨기기
|
||||
function hideAllMessages() {
|
||||
hideError();
|
||||
hideWarning();
|
||||
}
|
||||
|
||||
// 폼 유효성 검사
|
||||
function validateForm() {
|
||||
const selectedMonth = monthSelect.value;
|
||||
|
||||
if (!selectedMonth) {
|
||||
showError('조회할 월을 선택해주세요.');
|
||||
monthSelect.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 폼 제출 처리
|
||||
inquiryForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
hideAllMessages();
|
||||
|
||||
if (!validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 로딩 상태 시작
|
||||
inquiryBtn.classList.add('loading');
|
||||
inquiryBtn.disabled = true;
|
||||
|
||||
const selectedMonth = monthSelect.value;
|
||||
const selectedText = monthSelect.options[monthSelect.selectedIndex].text;
|
||||
|
||||
// 로딩 시뮬레이션
|
||||
setTimeout(() => {
|
||||
try {
|
||||
// 현재 날짜와 비교하여 미래 월인지 확인
|
||||
const now = new Date();
|
||||
const currentYear = now.getFullYear();
|
||||
const currentMonth = now.getMonth() + 1;
|
||||
const currentYearMonth = parseInt(`${currentYear}${currentMonth.toString().padStart(2, '0')}`);
|
||||
const selectedYearMonth = parseInt(selectedMonth);
|
||||
|
||||
if (selectedYearMonth > currentYearMonth) {
|
||||
showWarning('미래 월의 요금 정보는 조회할 수 없습니다.');
|
||||
inquiryBtn.classList.remove('loading');
|
||||
inquiryBtn.disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 성공 - 조회 결과 페이지로 이동
|
||||
sessionStorage.setItem('selectedMonth', selectedMonth);
|
||||
sessionStorage.setItem('selectedMonthText', selectedText);
|
||||
window.location.href = '04-요금조회결과.html';
|
||||
|
||||
} catch (error) {
|
||||
// 오류 처리
|
||||
showError('요금 조회 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.');
|
||||
inquiryBtn.classList.remove('loading');
|
||||
inquiryBtn.disabled = false;
|
||||
}
|
||||
}, 1500); // 1.5초 로딩 시뮬레이션
|
||||
});
|
||||
|
||||
// 선택 변경 시 메시지 숨기기
|
||||
monthSelect.addEventListener('change', function() {
|
||||
hideAllMessages();
|
||||
});
|
||||
|
||||
// 뒤로가기
|
||||
function goBack() {
|
||||
if (confirm('요금 조회를 취소하고 메인 화면으로 돌아가시겠습니까?')) {
|
||||
window.location.href = '02-메인화면.html';
|
||||
}
|
||||
}
|
||||
|
||||
// 메뉴 표시 (추후 구현)
|
||||
function showMenu() {
|
||||
alert('메뉴 기능은 추후 구현 예정입니다.');
|
||||
}
|
||||
|
||||
// 키보드 접근성
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
hideAllMessages();
|
||||
}
|
||||
});
|
||||
|
||||
// 페이지 로드 시 초기화
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
generateMonthOptions();
|
||||
|
||||
// 페이지 진입 시 현재 월 선택에 대한 안내 표시
|
||||
setTimeout(() => {
|
||||
showWarning('기본적으로 현재 월이 선택되어 있습니다. 다른 월을 조회하려면 드롭다운에서 선택해주세요.');
|
||||
}, 500);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,592 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>상품 변경 - 통신요금 관리 서비스</title>
|
||||
<style>
|
||||
/* CSS Variables - Style Guide */
|
||||
:root {
|
||||
/* Primary Colors */
|
||||
--primary-50: #EBF8FF;
|
||||
--primary-100: #BEE3F8;
|
||||
--primary-200: #90CDF4;
|
||||
--primary-300: #63B3ED;
|
||||
--primary-400: #4299E1;
|
||||
--primary-500: #3182CE;
|
||||
--primary-600: #2B77CB;
|
||||
--primary-700: #2C5282;
|
||||
--primary-800: #2A4365;
|
||||
--primary-900: #1A365D;
|
||||
|
||||
/* Gray Colors */
|
||||
--gray-50: #F9FAFB;
|
||||
--gray-100: #F3F4F6;
|
||||
--gray-200: #E5E7EB;
|
||||
--gray-300: #D1D5DB;
|
||||
--gray-400: #9CA3AF;
|
||||
--gray-500: #6B7280;
|
||||
--gray-600: #4B5563;
|
||||
--gray-700: #374151;
|
||||
--gray-800: #1F2937;
|
||||
--gray-900: #111827;
|
||||
|
||||
/* Status Colors */
|
||||
--success-500: #38A169;
|
||||
--error-500: #E53E3E;
|
||||
--warning-500: #ED8936;
|
||||
|
||||
/* Spacing */
|
||||
--space-1: 0.25rem;
|
||||
--space-2: 0.5rem;
|
||||
--space-3: 0.75rem;
|
||||
--space-4: 1rem;
|
||||
--space-5: 1.25rem;
|
||||
--space-6: 1.5rem;
|
||||
--space-8: 2rem;
|
||||
--space-10: 2.5rem;
|
||||
--space-12: 3rem;
|
||||
|
||||
/* Typography */
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.875rem;
|
||||
--text-base: 1rem;
|
||||
--text-lg: 1.125rem;
|
||||
--text-xl: 1.25rem;
|
||||
--text-2xl: 1.5rem;
|
||||
--text-3xl: 1.875rem;
|
||||
--text-4xl: 2.25rem;
|
||||
|
||||
--font-normal: 400;
|
||||
--font-medium: 500;
|
||||
--font-semibold: 600;
|
||||
--font-bold: 700;
|
||||
}
|
||||
|
||||
/* Reset & Base */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif;
|
||||
font-size: var(--text-base);
|
||||
line-height: 1.5;
|
||||
color: var(--gray-700);
|
||||
background-color: var(--gray-50);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Container */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
padding: var(--space-4);
|
||||
min-height: 100vh;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 480px;
|
||||
padding: var(--space-6);
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: white;
|
||||
z-index: 10;
|
||||
padding: var(--space-4) 0;
|
||||
border-bottom: 1px solid var(--gray-200);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
background: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: var(--text-xl);
|
||||
color: var(--gray-600);
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
background-color: var(--gray-100);
|
||||
color: var(--gray-800);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: var(--text-xl);
|
||||
font-weight: var(--font-bold);
|
||||
color: var(--gray-900);
|
||||
}
|
||||
|
||||
/* Card */
|
||||
.card {
|
||||
background-color: white;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
padding: var(--space-6);
|
||||
margin-bottom: var(--space-6);
|
||||
border: 1px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--gray-900);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
/* Customer Info */
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: var(--space-3) 0;
|
||||
border-bottom: 1px solid var(--gray-100);
|
||||
}
|
||||
|
||||
.info-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-500);
|
||||
font-weight: var(--font-medium);
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: var(--text-base);
|
||||
color: var(--gray-900);
|
||||
font-weight: var(--font-semibold);
|
||||
}
|
||||
|
||||
/* Product Info */
|
||||
.product-card {
|
||||
background: linear-gradient(135deg, var(--primary-50) 0%, var(--primary-100) 100%);
|
||||
border: 2px solid var(--primary-200);
|
||||
border-radius: 16px;
|
||||
padding: var(--space-6);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.product-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.product-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: var(--primary-500);
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: var(--text-lg);
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-size: var(--text-xl);
|
||||
font-weight: var(--font-bold);
|
||||
color: var(--primary-800);
|
||||
}
|
||||
|
||||
.product-price {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--primary-700);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.benefits-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.benefit-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-2) 0;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-700);
|
||||
}
|
||||
|
||||
.benefit-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: var(--success-500);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
/* Notice */
|
||||
.notice {
|
||||
background: #FFF7ED;
|
||||
border: 1px solid #FED7AA;
|
||||
border-radius: 12px;
|
||||
padding: var(--space-4);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.notice-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--warning-500);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.notice-text {
|
||||
font-size: var(--text-sm);
|
||||
color: #92400E;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* Button */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-4) var(--space-6);
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
line-height: 1.5;
|
||||
transition: all 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
min-height: 52px;
|
||||
text-decoration: none;
|
||||
width: 100%;
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.btn:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--primary-500) 0%, var(--primary-600) 100%);
|
||||
color: white;
|
||||
box-shadow: 0 2px 8px rgba(49, 130, 206, 0.2);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(49, 130, 206, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: white;
|
||||
color: var(--gray-600);
|
||||
border: 2px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--gray-50);
|
||||
border-color: var(--gray-300);
|
||||
color: var(--gray-700);
|
||||
}
|
||||
|
||||
/* Loading */
|
||||
.loading {
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.loading::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-top: 2px solid white;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Skeleton */
|
||||
.skeleton {
|
||||
background: linear-gradient(90deg, var(--gray-200) 25%, var(--gray-100) 50%, var(--gray-200) 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes skeleton {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
|
||||
/* Alert */
|
||||
.alert {
|
||||
padding: var(--space-4);
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-sm);
|
||||
margin-bottom: var(--space-4);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alert.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
background-color: #FEF2F2;
|
||||
border: 1px solid #FECACA;
|
||||
color: #991B1B;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<div class="header-content">
|
||||
<button class="back-btn" onclick="goBack()" aria-label="뒤로가기">
|
||||
←
|
||||
</button>
|
||||
<h1 class="page-title">상품 변경</h1>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Error Alert -->
|
||||
<div id="errorAlert" class="alert alert-error">
|
||||
<span id="errorMessage"></span>
|
||||
</div>
|
||||
|
||||
<!-- Customer Info Card -->
|
||||
<div class="card">
|
||||
<h2 class="card-title">고객 정보</h2>
|
||||
<div class="info-row">
|
||||
<span class="info-label">회선번호</span>
|
||||
<span class="info-value">010-1234-5678</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">고객ID</span>
|
||||
<span class="info-value">customer123</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Current Product Info -->
|
||||
<div class="product-card">
|
||||
<div class="product-header">
|
||||
<div class="product-icon">📱</div>
|
||||
<div>
|
||||
<div class="product-name">5G 프리미엄 플랜</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="product-price">월 69,000원</div>
|
||||
<ul class="benefits-list">
|
||||
<li class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>5G 데이터 무제한</span>
|
||||
</li>
|
||||
<li class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>음성통화 무제한</span>
|
||||
</li>
|
||||
<li class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>문자 무제한</span>
|
||||
</li>
|
||||
<li class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>해외 로밍 50% 할인</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Notice -->
|
||||
<div class="notice">
|
||||
<div class="notice-title">
|
||||
<span>⚠️</span>
|
||||
<span>상품 변경 시 주의사항</span>
|
||||
</div>
|
||||
<p class="notice-text">
|
||||
• 상품 변경은 다음 월 1일부터 적용됩니다<br>
|
||||
• 기존 약정 조건에 따라 위약금이 발생할 수 있습니다<br>
|
||||
• 변경 후에는 이전 상품으로 즉시 되돌릴 수 없습니다<br>
|
||||
• 부가서비스는 별도로 재신청이 필요할 수 있습니다
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="actions">
|
||||
<button class="btn btn-primary" id="changeBtn" onclick="goToProductChange()">
|
||||
상품 변경하기
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="goBack()">
|
||||
취소
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Navigation functions
|
||||
function goBack() {
|
||||
window.history.back();
|
||||
}
|
||||
|
||||
function goToProductChange() {
|
||||
const changeBtn = document.getElementById('changeBtn');
|
||||
|
||||
// Show loading state
|
||||
changeBtn.classList.add('loading');
|
||||
changeBtn.disabled = true;
|
||||
|
||||
// Simulate loading
|
||||
setTimeout(() => {
|
||||
window.location.href = '06-상품변경화면.html';
|
||||
}, 800);
|
||||
}
|
||||
|
||||
// Show error message
|
||||
function showError(message) {
|
||||
const errorAlert = document.getElementById('errorAlert');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
errorMessage.textContent = message;
|
||||
errorAlert.classList.add('show');
|
||||
}
|
||||
|
||||
// Hide error message
|
||||
function hideError() {
|
||||
const errorAlert = document.getElementById('errorAlert');
|
||||
errorAlert.classList.remove('show');
|
||||
}
|
||||
|
||||
// Load customer and product info on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadCurrentProduct();
|
||||
|
||||
// Handle keyboard navigation
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
goBack();
|
||||
}
|
||||
|
||||
if (e.key === 'Enter' && e.target.tagName === 'BUTTON') {
|
||||
e.target.click();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Load current product from localStorage
|
||||
function loadCurrentProduct() {
|
||||
try {
|
||||
const currentProduct = localStorage.getItem('currentProduct');
|
||||
if (currentProduct) {
|
||||
const product = JSON.parse(currentProduct);
|
||||
updateCurrentProductDisplay(product);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('localStorage에서 상품 정보를 불러오는 중 오류 발생:', error);
|
||||
// 기본값으로 화면이 이미 설정되어 있음
|
||||
}
|
||||
}
|
||||
|
||||
// Update current product display
|
||||
function updateCurrentProductDisplay(product) {
|
||||
// Update product name
|
||||
const productNameElement = document.querySelector('.product-name');
|
||||
if (productNameElement && product.name) {
|
||||
productNameElement.textContent = product.name;
|
||||
}
|
||||
|
||||
// Update product price
|
||||
const productPriceElement = document.querySelector('.product-price');
|
||||
if (productPriceElement && product.price) {
|
||||
productPriceElement.textContent = product.price;
|
||||
}
|
||||
|
||||
// Update benefits
|
||||
if (product.benefits && Array.isArray(product.benefits)) {
|
||||
const benefitsList = document.querySelector('.benefits-list');
|
||||
if (benefitsList) {
|
||||
benefitsList.innerHTML = '';
|
||||
product.benefits.forEach(benefit => {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'benefit-item';
|
||||
li.innerHTML = `
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>${benefit}</span>
|
||||
`;
|
||||
benefitsList.appendChild(li);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle back button for accessibility
|
||||
window.addEventListener('popstate', function(e) {
|
||||
// Handle browser back button
|
||||
});
|
||||
|
||||
// Add focus management for accessibility
|
||||
const focusableElements = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
|
||||
const modal = document.querySelector('.container');
|
||||
const firstFocusableElement = modal.querySelectorAll(focusableElements)[0];
|
||||
const focusableContent = modal.querySelectorAll(focusableElements);
|
||||
const lastFocusableElement = focusableContent[focusableContent.length - 1];
|
||||
|
||||
document.addEventListener('keydown', function(e) {
|
||||
const isTabPressed = e.key === 'Tab';
|
||||
|
||||
if (!isTabPressed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.shiftKey) {
|
||||
if (document.activeElement === firstFocusableElement) {
|
||||
lastFocusableElement.focus();
|
||||
e.preventDefault();
|
||||
}
|
||||
} else {
|
||||
if (document.activeElement === lastFocusableElement) {
|
||||
firstFocusableElement.focus();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,797 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>상품 선택 - 통신요금 관리 서비스</title>
|
||||
<style>
|
||||
/* CSS Variables - Style Guide */
|
||||
:root {
|
||||
/* Primary Colors */
|
||||
--primary-50: #EBF8FF;
|
||||
--primary-100: #BEE3F8;
|
||||
--primary-200: #90CDF4;
|
||||
--primary-300: #63B3ED;
|
||||
--primary-400: #4299E1;
|
||||
--primary-500: #3182CE;
|
||||
--primary-600: #2B77CB;
|
||||
--primary-700: #2C5282;
|
||||
--primary-800: #2A4365;
|
||||
--primary-900: #1A365D;
|
||||
|
||||
/* Gray Colors */
|
||||
--gray-50: #F9FAFB;
|
||||
--gray-100: #F3F4F6;
|
||||
--gray-200: #E5E7EB;
|
||||
--gray-300: #D1D5DB;
|
||||
--gray-400: #9CA3AF;
|
||||
--gray-500: #6B7280;
|
||||
--gray-600: #4B5563;
|
||||
--gray-700: #374151;
|
||||
--gray-800: #1F2937;
|
||||
--gray-900: #111827;
|
||||
|
||||
/* Status Colors */
|
||||
--success-500: #38A169;
|
||||
--error-500: #E53E3E;
|
||||
--warning-500: #ED8936;
|
||||
|
||||
/* Spacing */
|
||||
--space-1: 0.25rem;
|
||||
--space-2: 0.5rem;
|
||||
--space-3: 0.75rem;
|
||||
--space-4: 1rem;
|
||||
--space-5: 1.25rem;
|
||||
--space-6: 1.5rem;
|
||||
--space-8: 2rem;
|
||||
--space-10: 2.5rem;
|
||||
--space-12: 3rem;
|
||||
|
||||
/* Typography */
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.875rem;
|
||||
--text-base: 1rem;
|
||||
--text-lg: 1.125rem;
|
||||
--text-xl: 1.25rem;
|
||||
--text-2xl: 1.5rem;
|
||||
--text-3xl: 1.875rem;
|
||||
--text-4xl: 2.25rem;
|
||||
|
||||
--font-normal: 400;
|
||||
--font-medium: 500;
|
||||
--font-semibold: 600;
|
||||
--font-bold: 700;
|
||||
}
|
||||
|
||||
/* Reset & Base */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif;
|
||||
font-size: var(--text-base);
|
||||
line-height: 1.5;
|
||||
color: var(--gray-700);
|
||||
background-color: var(--gray-50);
|
||||
min-height: 100vh;
|
||||
padding-bottom: 100px; /* Space for fixed buttons */
|
||||
}
|
||||
|
||||
/* Container */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
padding: var(--space-4);
|
||||
background-color: white;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 480px;
|
||||
padding: var(--space-6);
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: white;
|
||||
z-index: 10;
|
||||
padding: var(--space-4) 0;
|
||||
border-bottom: 1px solid var(--gray-200);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
background: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: var(--text-xl);
|
||||
color: var(--gray-600);
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
background-color: var(--gray-100);
|
||||
color: var(--gray-800);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: var(--text-xl);
|
||||
font-weight: var(--font-bold);
|
||||
color: var(--gray-900);
|
||||
}
|
||||
|
||||
/* Current Product Summary */
|
||||
.current-product {
|
||||
background: linear-gradient(135deg, var(--gray-100) 0%, var(--gray-50) 100%);
|
||||
border: 2px solid var(--gray-200);
|
||||
border-radius: 12px;
|
||||
padding: var(--space-4);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.current-label {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-500);
|
||||
margin-bottom: var(--space-2);
|
||||
font-weight: var(--font-medium);
|
||||
}
|
||||
|
||||
.current-name {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--gray-800);
|
||||
margin-bottom: var(--space-1);
|
||||
}
|
||||
|
||||
.current-price {
|
||||
font-size: var(--text-base);
|
||||
color: var(--gray-600);
|
||||
}
|
||||
|
||||
/* Product List */
|
||||
.products-section {
|
||||
margin-bottom: var(--space-8);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--gray-900);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.product-card {
|
||||
border: 2px solid var(--gray-200);
|
||||
border-radius: 16px;
|
||||
padding: var(--space-5);
|
||||
margin-bottom: var(--space-4);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.product-card:hover {
|
||||
border-color: var(--primary-300);
|
||||
box-shadow: 0 4px 12px rgba(49, 130, 206, 0.1);
|
||||
}
|
||||
|
||||
.product-card.selected {
|
||||
border-color: var(--primary-500);
|
||||
background: linear-gradient(135deg, var(--primary-50) 0%, var(--primary-100) 100%);
|
||||
box-shadow: 0 4px 12px rgba(49, 130, 206, 0.2);
|
||||
}
|
||||
|
||||
.product-radio {
|
||||
position: absolute;
|
||||
top: var(--space-4);
|
||||
right: var(--space-4);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid var(--gray-300);
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.product-card.selected .product-radio {
|
||||
border-color: var(--primary-500);
|
||||
background: var(--primary-500);
|
||||
}
|
||||
|
||||
.product-card.selected .product-radio::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.product-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
margin-bottom: var(--space-3);
|
||||
margin-right: var(--space-8);
|
||||
}
|
||||
|
||||
.product-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: var(--primary-500);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: var(--text-base);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.product-card.selected .product-icon {
|
||||
background: var(--primary-600);
|
||||
}
|
||||
|
||||
.product-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--gray-900);
|
||||
margin-bottom: var(--space-1);
|
||||
}
|
||||
|
||||
.product-price {
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-medium);
|
||||
color: var(--primary-600);
|
||||
}
|
||||
|
||||
.benefits-grid {
|
||||
display: grid;
|
||||
gap: var(--space-2);
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.benefit-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-600);
|
||||
}
|
||||
|
||||
.benefit-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background: var(--success-500);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.price-comparison {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
padding-top: var(--space-3);
|
||||
border-top: 1px solid var(--gray-200);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.price-change {
|
||||
font-weight: var(--font-semibold);
|
||||
}
|
||||
|
||||
.price-up {
|
||||
color: var(--error-500);
|
||||
}
|
||||
|
||||
.price-down {
|
||||
color: var(--success-500);
|
||||
}
|
||||
|
||||
.price-same {
|
||||
color: var(--gray-500);
|
||||
}
|
||||
|
||||
/* Fixed Action Buttons */
|
||||
.fixed-actions {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
background: white;
|
||||
padding: var(--space-4);
|
||||
border-top: 1px solid var(--gray-200);
|
||||
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.fixed-actions {
|
||||
max-width: 480px;
|
||||
padding: var(--space-6);
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-4) var(--space-6);
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
line-height: 1.5;
|
||||
transition: all 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
min-height: 52px;
|
||||
text-decoration: none;
|
||||
width: 100%;
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.btn:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--primary-500) 0%, var(--primary-600) 100%);
|
||||
color: white;
|
||||
box-shadow: 0 2px 8px rgba(49, 130, 206, 0.2);
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(49, 130, 206, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
background: var(--gray-300);
|
||||
color: var(--gray-500);
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: white;
|
||||
color: var(--gray-600);
|
||||
border: 2px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--gray-50);
|
||||
border-color: var(--gray-300);
|
||||
color: var(--gray-700);
|
||||
}
|
||||
|
||||
/* Loading */
|
||||
.loading {
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.loading::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-top: 2px solid white;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Skeleton */
|
||||
.skeleton {
|
||||
background: linear-gradient(90deg, var(--gray-200) 25%, var(--gray-100) 50%, var(--gray-200) 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton 1.5s infinite;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.skeleton-product {
|
||||
height: 140px;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
@keyframes skeleton {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
|
||||
/* Alert */
|
||||
.alert {
|
||||
padding: var(--space-4);
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-sm);
|
||||
margin-bottom: var(--space-4);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alert.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
background-color: #FEF2F2;
|
||||
border: 1px solid #FECACA;
|
||||
color: #991B1B;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<div class="header-content">
|
||||
<button class="back-btn" onclick="goBack()" aria-label="뒤로가기">
|
||||
←
|
||||
</button>
|
||||
<h1 class="page-title">상품 선택</h1>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Error Alert -->
|
||||
<div id="errorAlert" class="alert alert-error">
|
||||
<span id="errorMessage"></span>
|
||||
</div>
|
||||
|
||||
<!-- Current Product Summary -->
|
||||
<div class="current-product">
|
||||
<div class="current-label">현재 이용 중인 상품</div>
|
||||
<div class="current-name">5G 프리미엄 플랜</div>
|
||||
<div class="current-price">월 69,000원</div>
|
||||
</div>
|
||||
|
||||
<!-- Products Section -->
|
||||
<div class="products-section">
|
||||
<h2 class="section-title">변경 가능한 상품</h2>
|
||||
|
||||
<!-- Loading Skeletons (hidden by default) -->
|
||||
<div id="loadingSkeletons" style="display: none;">
|
||||
<div class="skeleton skeleton-product"></div>
|
||||
<div class="skeleton skeleton-product"></div>
|
||||
<div class="skeleton skeleton-product"></div>
|
||||
</div>
|
||||
|
||||
<!-- Product List -->
|
||||
<div id="productList">
|
||||
<!-- Product Card 1 -->
|
||||
<div class="product-card" data-product-id="basic" onclick="selectProduct(this)">
|
||||
<div class="product-radio"></div>
|
||||
<div class="product-header">
|
||||
<div class="product-icon">📱</div>
|
||||
<div class="product-info">
|
||||
<div class="product-name">5G 베이직 플랜</div>
|
||||
<div class="product-price">월 39,000원</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="benefits-grid">
|
||||
<div class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>5G 데이터 10GB</span>
|
||||
</div>
|
||||
<div class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>음성통화 300분</span>
|
||||
</div>
|
||||
<div class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>문자 무제한</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="price-comparison">
|
||||
<span>현재 상품 대비</span>
|
||||
<span class="price-change price-down">월 30,000원 절약</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Product Card 2 -->
|
||||
<div class="product-card" data-product-id="standard" onclick="selectProduct(this)">
|
||||
<div class="product-radio"></div>
|
||||
<div class="product-header">
|
||||
<div class="product-icon">📱</div>
|
||||
<div class="product-info">
|
||||
<div class="product-name">5G 스탠다드 플랜</div>
|
||||
<div class="product-price">월 59,000원</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="benefits-grid">
|
||||
<div class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>5G 데이터 50GB</span>
|
||||
</div>
|
||||
<div class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>음성통화 무제한</span>
|
||||
</div>
|
||||
<div class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>문자 무제한</span>
|
||||
</div>
|
||||
<div class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>해외 로밍 20% 할인</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="price-comparison">
|
||||
<span>현재 상품 대비</span>
|
||||
<span class="price-change price-down">월 10,000원 절약</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Product Card 3 -->
|
||||
<div class="product-card" data-product-id="unlimited" onclick="selectProduct(this)">
|
||||
<div class="product-radio"></div>
|
||||
<div class="product-header">
|
||||
<div class="product-icon">📱</div>
|
||||
<div class="product-info">
|
||||
<div class="product-name">5G 언리미티드 플랜</div>
|
||||
<div class="product-price">월 89,000원</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="benefits-grid">
|
||||
<div class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>5G 데이터 무제한</span>
|
||||
</div>
|
||||
<div class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>음성통화 무제한</span>
|
||||
</div>
|
||||
<div class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>문자 무제한</span>
|
||||
</div>
|
||||
<div class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>해외 로밍 무료</span>
|
||||
</div>
|
||||
<div class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>OTT 서비스 3개</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="price-comparison">
|
||||
<span>현재 상품 대비</span>
|
||||
<span class="price-change price-up">월 20,000원 추가</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fixed Action Buttons -->
|
||||
<div class="fixed-actions">
|
||||
<button class="btn btn-primary" id="nextBtn" disabled onclick="goToRequest()">
|
||||
선택한 상품으로 변경
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="goBack()">
|
||||
취소
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let selectedProductId = null;
|
||||
|
||||
// Select product function
|
||||
function selectProduct(cardElement) {
|
||||
// Remove selection from all cards
|
||||
document.querySelectorAll('.product-card').forEach(card => {
|
||||
card.classList.remove('selected');
|
||||
});
|
||||
|
||||
// Add selection to clicked card
|
||||
cardElement.classList.add('selected');
|
||||
|
||||
// Store selected product ID
|
||||
selectedProductId = cardElement.dataset.productId;
|
||||
|
||||
// Enable next button
|
||||
const nextBtn = document.getElementById('nextBtn');
|
||||
nextBtn.disabled = false;
|
||||
|
||||
// Update button text based on selection
|
||||
const productName = cardElement.querySelector('.product-name').textContent;
|
||||
nextBtn.textContent = `${productName}으로 변경`;
|
||||
}
|
||||
|
||||
// Navigation functions
|
||||
function goBack() {
|
||||
window.history.back();
|
||||
}
|
||||
|
||||
function goToRequest() {
|
||||
if (!selectedProductId) {
|
||||
showError('변경할 상품을 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
const nextBtn = document.getElementById('nextBtn');
|
||||
|
||||
// Show loading state
|
||||
nextBtn.classList.add('loading');
|
||||
nextBtn.disabled = true;
|
||||
|
||||
// Store selected product in sessionStorage
|
||||
const selectedCard = document.querySelector(`[data-product-id="${selectedProductId}"]`);
|
||||
const productData = {
|
||||
id: selectedProductId,
|
||||
name: selectedCard.querySelector('.product-name').textContent,
|
||||
price: selectedCard.querySelector('.product-price').textContent,
|
||||
benefits: Array.from(selectedCard.querySelectorAll('.benefit-item span:last-child')).map(el => el.textContent)
|
||||
};
|
||||
sessionStorage.setItem('selectedProduct', JSON.stringify(productData));
|
||||
|
||||
// Simulate loading
|
||||
setTimeout(() => {
|
||||
window.location.href = '07-상품변경요청.html';
|
||||
}, 800);
|
||||
}
|
||||
|
||||
// Show error message
|
||||
function showError(message) {
|
||||
const errorAlert = document.getElementById('errorAlert');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
errorMessage.textContent = message;
|
||||
errorAlert.classList.add('show');
|
||||
|
||||
// Hide after 5 seconds
|
||||
setTimeout(() => {
|
||||
hideError();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Hide error message
|
||||
function hideError() {
|
||||
const errorAlert = document.getElementById('errorAlert');
|
||||
errorAlert.classList.remove('show');
|
||||
}
|
||||
|
||||
// Load products on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Load current product from localStorage
|
||||
loadCurrentProduct();
|
||||
// Simulate loading products
|
||||
loadProducts();
|
||||
|
||||
// Handle keyboard navigation
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
goBack();
|
||||
}
|
||||
|
||||
if (e.key === 'Enter' && e.target.classList.contains('product-card')) {
|
||||
selectProduct(e.target);
|
||||
}
|
||||
|
||||
// Arrow key navigation for product cards
|
||||
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
||||
const cards = Array.from(document.querySelectorAll('.product-card'));
|
||||
const currentIndex = cards.findIndex(card => card === document.activeElement);
|
||||
|
||||
if (e.key === 'ArrowDown' && currentIndex < cards.length - 1) {
|
||||
cards[currentIndex + 1].focus();
|
||||
e.preventDefault();
|
||||
} else if (e.key === 'ArrowUp' && currentIndex > 0) {
|
||||
cards[currentIndex - 1].focus();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Make product cards focusable for keyboard navigation
|
||||
document.querySelectorAll('.product-card').forEach(card => {
|
||||
card.setAttribute('tabindex', '0');
|
||||
card.setAttribute('role', 'radio');
|
||||
card.setAttribute('aria-checked', 'false');
|
||||
});
|
||||
});
|
||||
|
||||
// Load current product from localStorage
|
||||
function loadCurrentProduct() {
|
||||
try {
|
||||
const currentProduct = localStorage.getItem('currentProduct');
|
||||
if (currentProduct) {
|
||||
const product = JSON.parse(currentProduct);
|
||||
const currentNameElement = document.querySelector('.current-name');
|
||||
if (currentNameElement && product.name) {
|
||||
currentNameElement.textContent = product.name;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('localStorage에서 상품 정보를 불러오는 중 오류 발생:', error);
|
||||
// 기본값 유지
|
||||
}
|
||||
}
|
||||
|
||||
function loadProducts() {
|
||||
const loadingSkeletons = document.getElementById('loadingSkeletons');
|
||||
const productList = document.getElementById('productList');
|
||||
|
||||
// Show loading
|
||||
loadingSkeletons.style.display = 'block';
|
||||
productList.style.display = 'none';
|
||||
|
||||
// Simulate API call
|
||||
setTimeout(() => {
|
||||
// Hide loading, show products
|
||||
loadingSkeletons.style.display = 'none';
|
||||
productList.style.display = 'block';
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// Update aria-checked when product is selected
|
||||
function selectProduct(cardElement) {
|
||||
// Remove selection from all cards
|
||||
document.querySelectorAll('.product-card').forEach(card => {
|
||||
card.classList.remove('selected');
|
||||
card.setAttribute('aria-checked', 'false');
|
||||
});
|
||||
|
||||
// Add selection to clicked card
|
||||
cardElement.classList.add('selected');
|
||||
cardElement.setAttribute('aria-checked', 'true');
|
||||
|
||||
// Store selected product ID
|
||||
selectedProductId = cardElement.dataset.productId;
|
||||
|
||||
// Store selected product info in sessionStorage
|
||||
const productName = cardElement.querySelector('.product-name').textContent;
|
||||
const productPrice = cardElement.querySelector('.product-price').textContent;
|
||||
sessionStorage.setItem('selectedProductName', productName);
|
||||
sessionStorage.setItem('selectedProductPrice', productPrice);
|
||||
|
||||
// Enable next button
|
||||
const nextBtn = document.getElementById('nextBtn');
|
||||
nextBtn.disabled = false;
|
||||
|
||||
// Update button text based on selection
|
||||
nextBtn.textContent = `${productName}으로 변경`;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,774 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>상품 변경 요청 - 통신요금 관리 서비스</title>
|
||||
<style>
|
||||
/* CSS Variables - Style Guide */
|
||||
:root {
|
||||
/* Primary Colors */
|
||||
--primary-50: #EBF8FF;
|
||||
--primary-100: #BEE3F8;
|
||||
--primary-200: #90CDF4;
|
||||
--primary-300: #63B3ED;
|
||||
--primary-400: #4299E1;
|
||||
--primary-500: #3182CE;
|
||||
--primary-600: #2B77CB;
|
||||
--primary-700: #2C5282;
|
||||
--primary-800: #2A4365;
|
||||
--primary-900: #1A365D;
|
||||
|
||||
/* Gray Colors */
|
||||
--gray-50: #F9FAFB;
|
||||
--gray-100: #F3F4F6;
|
||||
--gray-200: #E5E7EB;
|
||||
--gray-300: #D1D5DB;
|
||||
--gray-400: #9CA3AF;
|
||||
--gray-500: #6B7280;
|
||||
--gray-600: #4B5563;
|
||||
--gray-700: #374151;
|
||||
--gray-800: #1F2937;
|
||||
--gray-900: #111827;
|
||||
|
||||
/* Status Colors */
|
||||
--success-500: #38A169;
|
||||
--error-500: #E53E3E;
|
||||
--warning-500: #ED8936;
|
||||
|
||||
/* Spacing */
|
||||
--space-1: 0.25rem;
|
||||
--space-2: 0.5rem;
|
||||
--space-3: 0.75rem;
|
||||
--space-4: 1rem;
|
||||
--space-5: 1.25rem;
|
||||
--space-6: 1.5rem;
|
||||
--space-8: 2rem;
|
||||
--space-10: 2.5rem;
|
||||
--space-12: 3rem;
|
||||
|
||||
/* Typography */
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.875rem;
|
||||
--text-base: 1rem;
|
||||
--text-lg: 1.125rem;
|
||||
--text-xl: 1.25rem;
|
||||
--text-2xl: 1.5rem;
|
||||
--text-3xl: 1.875rem;
|
||||
--text-4xl: 2.25rem;
|
||||
|
||||
--font-normal: 400;
|
||||
--font-medium: 500;
|
||||
--font-semibold: 600;
|
||||
--font-bold: 700;
|
||||
}
|
||||
|
||||
/* Reset & Base */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif;
|
||||
font-size: var(--text-base);
|
||||
line-height: 1.5;
|
||||
color: var(--gray-700);
|
||||
background-color: var(--gray-50);
|
||||
min-height: 100vh;
|
||||
padding-bottom: 120px; /* Space for fixed buttons */
|
||||
}
|
||||
|
||||
/* Container */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
padding: var(--space-4);
|
||||
background-color: white;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 480px;
|
||||
padding: var(--space-6);
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: white;
|
||||
z-index: 10;
|
||||
padding: var(--space-4) 0;
|
||||
border-bottom: 1px solid var(--gray-200);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
background: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: var(--text-xl);
|
||||
color: var(--gray-600);
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
background-color: var(--gray-100);
|
||||
color: var(--gray-800);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: var(--text-xl);
|
||||
font-weight: var(--font-bold);
|
||||
color: var(--gray-900);
|
||||
}
|
||||
|
||||
/* Card */
|
||||
.card {
|
||||
background-color: white;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
padding: var(--space-6);
|
||||
margin-bottom: var(--space-6);
|
||||
border: 1px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--gray-900);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
/* Change Comparison */
|
||||
.change-comparison {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-4);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.product-summary {
|
||||
flex: 1;
|
||||
padding: var(--space-4);
|
||||
border-radius: 12px;
|
||||
border: 2px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.product-summary.current {
|
||||
background: var(--gray-50);
|
||||
}
|
||||
|
||||
.product-summary.new {
|
||||
background: linear-gradient(135deg, var(--primary-50) 0%, var(--primary-100) 100%);
|
||||
border-color: var(--primary-200);
|
||||
}
|
||||
|
||||
.product-label {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--gray-500);
|
||||
margin-bottom: var(--space-1);
|
||||
font-weight: var(--font-medium);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--gray-900);
|
||||
margin-bottom: var(--space-1);
|
||||
}
|
||||
|
||||
.product-price {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--primary-600);
|
||||
font-weight: var(--font-medium);
|
||||
}
|
||||
|
||||
.change-arrow {
|
||||
font-size: var(--text-2xl);
|
||||
color: var(--primary-500);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Notice */
|
||||
.notice {
|
||||
background: #FFF7ED;
|
||||
border: 1px solid #FED7AA;
|
||||
border-radius: 12px;
|
||||
padding: var(--space-4);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.notice-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--warning-500);
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.notice-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.notice-item {
|
||||
font-size: var(--text-sm);
|
||||
color: #92400E;
|
||||
line-height: 1.4;
|
||||
margin-bottom: var(--space-2);
|
||||
padding-left: var(--space-4);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.notice-item::before {
|
||||
content: '•';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--warning-500);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.notice-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Progress */
|
||||
.progress-section {
|
||||
margin-bottom: var(--space-8);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: var(--gray-200);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, var(--primary-500) 0%, var(--primary-600) 100%);
|
||||
border-radius: 4px;
|
||||
transition: width 0.3s ease-in-out;
|
||||
width: 0%;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
text-align: center;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-600);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.progress-status {
|
||||
text-align: center;
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--primary-600);
|
||||
}
|
||||
|
||||
.progress-steps {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: var(--space-4);
|
||||
}
|
||||
|
||||
.progress-step {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: var(--text-xs);
|
||||
color: var(--gray-400);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.progress-step.active {
|
||||
color: var(--primary-600);
|
||||
font-weight: var(--font-semibold);
|
||||
}
|
||||
|
||||
.progress-step.completed {
|
||||
color: var(--success-500);
|
||||
font-weight: var(--font-semibold);
|
||||
}
|
||||
|
||||
/* Fixed Action Buttons */
|
||||
.fixed-actions {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
background: white;
|
||||
padding: var(--space-4);
|
||||
border-top: 1px solid var(--gray-200);
|
||||
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.fixed-actions {
|
||||
max-width: 480px;
|
||||
padding: var(--space-6);
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-4) var(--space-6);
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
line-height: 1.5;
|
||||
transition: all 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
min-height: 52px;
|
||||
text-decoration: none;
|
||||
width: 100%;
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.btn:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #3182CE !important; /* Fallback color with important */
|
||||
background: linear-gradient(135deg, #3182CE 0%, #2B77CB 100%) !important;
|
||||
color: white !important;
|
||||
box-shadow: 0 2px 8px rgba(49, 130, 206, 0.2);
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(49, 130, 206, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
background: var(--gray-300);
|
||||
color: var(--gray-500);
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: white;
|
||||
color: var(--gray-600);
|
||||
border: 2px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--gray-50);
|
||||
border-color: var(--gray-300);
|
||||
color: var(--gray-700);
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
color: var(--primary-600);
|
||||
border: 2px solid var(--primary-200);
|
||||
}
|
||||
|
||||
.btn-outline:hover {
|
||||
background: var(--primary-50);
|
||||
border-color: var(--primary-300);
|
||||
}
|
||||
|
||||
/* Loading */
|
||||
.loading {
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.loading::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-top: 2px solid white;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Alert */
|
||||
.alert {
|
||||
padding: var(--space-4);
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-sm);
|
||||
margin-bottom: var(--space-4);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alert.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
background-color: #FEF2F2;
|
||||
border: 1px solid #FECACA;
|
||||
color: #991B1B;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background-color: #F0FDF4;
|
||||
border: 1px solid #BBF7D0;
|
||||
color: #166534;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<div class="header-content">
|
||||
<button class="back-btn" onclick="goBack()" aria-label="뒤로가기">
|
||||
←
|
||||
</button>
|
||||
<h1 class="page-title">상품 변경 요청</h1>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Alerts -->
|
||||
<div id="errorAlert" class="alert alert-error">
|
||||
<span id="errorMessage"></span>
|
||||
</div>
|
||||
|
||||
<div id="successAlert" class="alert alert-success">
|
||||
<span id="successMessage"></span>
|
||||
</div>
|
||||
|
||||
<!-- Change Confirmation -->
|
||||
<div class="card">
|
||||
<h2 class="card-title">변경 내용 확인</h2>
|
||||
<div class="change-comparison">
|
||||
<div class="product-summary current">
|
||||
<div class="product-label">현재 상품</div>
|
||||
<div class="product-name">5G 프리미엄 플랜</div>
|
||||
<div class="product-price">월 69,000원</div>
|
||||
</div>
|
||||
<div class="change-arrow">→</div>
|
||||
<div class="product-summary new">
|
||||
<div class="product-label">변경할 상품</div>
|
||||
<div class="product-name" id="newProductName">5G 스탠다드 플랜</div>
|
||||
<div class="product-price" id="newProductPrice">월 59,000원</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Important Notice -->
|
||||
<div class="notice">
|
||||
<div class="notice-title">
|
||||
<span>⚠️</span>
|
||||
<span>중요 안내사항</span>
|
||||
</div>
|
||||
<ul class="notice-list">
|
||||
<li class="notice-item">상품 변경은 다음 월 1일부터 적용됩니다</li>
|
||||
<li class="notice-item">현재 약정 기간이 남아있는 경우 위약금이 발생할 수 있습니다</li>
|
||||
<li class="notice-item">기존 부가서비스는 자동으로 해지되며, 필요시 재신청해야 합니다</li>
|
||||
<li class="notice-item">변경 후 14일 이내에 취소 가능하나, 일부 제약이 있을 수 있습니다</li>
|
||||
<li class="notice-item">요금제 변경에 따른 데이터 이월은 불가능합니다</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Progress Section -->
|
||||
<div class="card progress-section">
|
||||
<h2 class="card-title">사전 검증 진행 상황</h2>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="progressFill"></div>
|
||||
</div>
|
||||
<div class="progress-text" id="progressText">검증을 시작하세요</div>
|
||||
<div class="progress-status" id="progressStatus">검증 대기중</div>
|
||||
|
||||
<div class="progress-steps">
|
||||
<div class="progress-step" data-step="1">
|
||||
<div>약정 확인</div>
|
||||
</div>
|
||||
<div class="progress-step" data-step="2">
|
||||
<div>자격 검증</div>
|
||||
</div>
|
||||
<div class="progress-step" data-step="3">
|
||||
<div>요금 계산</div>
|
||||
</div>
|
||||
<div class="progress-step" data-step="4">
|
||||
<div>승인 완료</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fixed Action Buttons -->
|
||||
<div class="fixed-actions">
|
||||
<button class="btn btn-primary" id="submitBtn" disabled onclick="startValidation()">
|
||||
사전 검증 시작
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="goBack()">
|
||||
취소
|
||||
</button>
|
||||
<button class="btn btn-outline" onclick="goToPrevious()">
|
||||
이전 단계
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let validationStep = 0;
|
||||
let validationCompleted = false;
|
||||
|
||||
// Load selected product data from previous screen
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadSelectedProduct();
|
||||
enableSubmitButton();
|
||||
|
||||
// Handle keyboard navigation
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
goBack();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function loadSelectedProduct() {
|
||||
const selectedProduct = sessionStorage.getItem('selectedProduct');
|
||||
if (selectedProduct) {
|
||||
const product = JSON.parse(selectedProduct);
|
||||
document.getElementById('newProductName').textContent = product.name;
|
||||
document.getElementById('newProductPrice').textContent = product.price;
|
||||
}
|
||||
}
|
||||
|
||||
function enableSubmitButton() {
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
submitBtn.disabled = false;
|
||||
}
|
||||
|
||||
function startValidation() {
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
|
||||
if (validationCompleted) {
|
||||
// Already validated, proceed to result
|
||||
goToResult();
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable button during validation
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = '검증 중...';
|
||||
|
||||
// Start validation process
|
||||
runValidationSteps();
|
||||
}
|
||||
|
||||
function runValidationSteps() {
|
||||
const steps = [
|
||||
{ name: '약정 확인', duration: 2000 },
|
||||
{ name: '자격 검증', duration: 1500 },
|
||||
{ name: '요금 계산', duration: 1800 },
|
||||
{ name: '승인 완료', duration: 1000 }
|
||||
];
|
||||
|
||||
let currentStep = 0;
|
||||
|
||||
function processNextStep() {
|
||||
if (currentStep >= steps.length) {
|
||||
onValidationComplete();
|
||||
return;
|
||||
}
|
||||
|
||||
const step = steps[currentStep];
|
||||
const stepIndex = currentStep + 1;
|
||||
|
||||
// Update progress
|
||||
updateProgress(stepIndex, steps.length, `${step.name} 진행 중...`);
|
||||
|
||||
// Mark current step as active
|
||||
document.querySelectorAll('.progress-step').forEach((el, index) => {
|
||||
if (index < stepIndex - 1) {
|
||||
el.classList.add('completed');
|
||||
el.classList.remove('active');
|
||||
} else if (index === stepIndex - 1) {
|
||||
el.classList.add('active');
|
||||
el.classList.remove('completed');
|
||||
} else {
|
||||
el.classList.remove('active', 'completed');
|
||||
}
|
||||
});
|
||||
|
||||
// Simulate step processing
|
||||
setTimeout(() => {
|
||||
currentStep++;
|
||||
processNextStep();
|
||||
}, step.duration);
|
||||
}
|
||||
|
||||
processNextStep();
|
||||
}
|
||||
|
||||
function updateProgress(current, total, statusText) {
|
||||
const progress = (current / total) * 100;
|
||||
const progressFill = document.getElementById('progressFill');
|
||||
const progressText = document.getElementById('progressText');
|
||||
const progressStatus = document.getElementById('progressStatus');
|
||||
|
||||
progressFill.style.width = `${progress}%`;
|
||||
progressText.textContent = `${current}/${total} 단계`;
|
||||
progressStatus.textContent = statusText;
|
||||
}
|
||||
|
||||
function onValidationComplete() {
|
||||
validationCompleted = true;
|
||||
|
||||
// Update UI
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
const progressStatus = document.getElementById('progressStatus');
|
||||
|
||||
updateProgress(4, 4, '모든 검증이 완료되었습니다');
|
||||
|
||||
// Mark all steps as completed
|
||||
document.querySelectorAll('.progress-step').forEach(el => {
|
||||
el.classList.add('completed');
|
||||
el.classList.remove('active');
|
||||
});
|
||||
|
||||
// Show success message
|
||||
showSuccess('사전 검증이 성공적으로 완료되었습니다.');
|
||||
|
||||
// Enable submit button
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = '변경 신청하기';
|
||||
|
||||
// Add some celebration effect
|
||||
setTimeout(() => {
|
||||
submitBtn.style.background = 'linear-gradient(135deg, var(--success-500) 0%, var(--success-600) 100%)';
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function submitRequest() {
|
||||
const progressStatus = document.getElementById('progressStatus');
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
|
||||
// Update status text
|
||||
progressStatus.textContent = '상품 변경 요청을 처리하고 있습니다...';
|
||||
|
||||
// Show loading on button
|
||||
submitBtn.classList.add('loading');
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = '처리 중...';
|
||||
|
||||
// Start actual submission process
|
||||
setTimeout(() => {
|
||||
goToResult();
|
||||
}, 800);
|
||||
}
|
||||
|
||||
function goToResult() {
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
|
||||
// Show loading state
|
||||
submitBtn.classList.add('loading');
|
||||
submitBtn.disabled = true;
|
||||
|
||||
// Store request data
|
||||
let currentProductName = '5G 프리미엄 플랜';
|
||||
try {
|
||||
const currentProduct = localStorage.getItem('currentProduct');
|
||||
if (currentProduct) {
|
||||
const product = JSON.parse(currentProduct);
|
||||
currentProductName = product.name;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('localStorage에서 상품 정보를 불러오는 중 오류 발생:', error);
|
||||
}
|
||||
|
||||
const requestData = {
|
||||
currentProduct: currentProductName,
|
||||
newProduct: sessionStorage.getItem('selectedProductName') || '5G 스탠다드 플랜',
|
||||
newPrice: sessionStorage.getItem('selectedProductPrice') || '월 59,000원',
|
||||
requestTime: new Date().toISOString(),
|
||||
status: 'success'
|
||||
};
|
||||
sessionStorage.setItem('changeRequest', JSON.stringify(requestData));
|
||||
|
||||
// Simulate final processing
|
||||
setTimeout(() => {
|
||||
window.location.href = '08-처리결과화면.html';
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
// Navigation functions
|
||||
function goBack() {
|
||||
window.history.back();
|
||||
}
|
||||
|
||||
function goToPrevious() {
|
||||
window.location.href = '06-상품변경화면.html';
|
||||
}
|
||||
|
||||
// Alert functions
|
||||
function showError(message) {
|
||||
const errorAlert = document.getElementById('errorAlert');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
errorMessage.textContent = message;
|
||||
errorAlert.classList.add('show');
|
||||
|
||||
setTimeout(() => {
|
||||
hideError();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
function hideError() {
|
||||
const errorAlert = document.getElementById('errorAlert');
|
||||
errorAlert.classList.remove('show');
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
const successAlert = document.getElementById('successAlert');
|
||||
const successMessage = document.getElementById('successMessage');
|
||||
successMessage.textContent = message;
|
||||
successAlert.classList.add('show');
|
||||
|
||||
setTimeout(() => {
|
||||
hideSuccess();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function hideSuccess() {
|
||||
const successAlert = document.getElementById('successAlert');
|
||||
successAlert.classList.remove('show');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,739 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>처리 결과 - 통신요금 관리 서비스</title>
|
||||
<style>
|
||||
/* CSS Variables - Style Guide */
|
||||
:root {
|
||||
/* Primary Colors */
|
||||
--primary-50: #EBF8FF;
|
||||
--primary-100: #BEE3F8;
|
||||
--primary-200: #90CDF4;
|
||||
--primary-300: #63B3ED;
|
||||
--primary-400: #4299E1;
|
||||
--primary-500: #3182CE;
|
||||
--primary-600: #2B77CB;
|
||||
--primary-700: #2C5282;
|
||||
--primary-800: #2A4365;
|
||||
--primary-900: #1A365D;
|
||||
|
||||
/* Gray Colors */
|
||||
--gray-50: #F9FAFB;
|
||||
--gray-100: #F3F4F6;
|
||||
--gray-200: #E5E7EB;
|
||||
--gray-300: #D1D5DB;
|
||||
--gray-400: #9CA3AF;
|
||||
--gray-500: #6B7280;
|
||||
--gray-600: #4B5563;
|
||||
--gray-700: #374151;
|
||||
--gray-800: #1F2937;
|
||||
--gray-900: #111827;
|
||||
|
||||
/* Status Colors */
|
||||
--success-50: #F0FDF4;
|
||||
--success-100: #DCFCE7;
|
||||
--success-500: #38A169;
|
||||
--success-600: #2F855A;
|
||||
--error-50: #FEF2F2;
|
||||
--error-100: #FEE2E2;
|
||||
--error-500: #E53E3E;
|
||||
--error-600: #DC2626;
|
||||
--warning-500: #ED8936;
|
||||
|
||||
/* Spacing */
|
||||
--space-1: 0.25rem;
|
||||
--space-2: 0.5rem;
|
||||
--space-3: 0.75rem;
|
||||
--space-4: 1rem;
|
||||
--space-5: 1.25rem;
|
||||
--space-6: 1.5rem;
|
||||
--space-8: 2rem;
|
||||
--space-10: 2.5rem;
|
||||
--space-12: 3rem;
|
||||
|
||||
/* Typography */
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.875rem;
|
||||
--text-base: 1rem;
|
||||
--text-lg: 1.125rem;
|
||||
--text-xl: 1.25rem;
|
||||
--text-2xl: 1.5rem;
|
||||
--text-3xl: 1.875rem;
|
||||
--text-4xl: 2.25rem;
|
||||
|
||||
--font-normal: 400;
|
||||
--font-medium: 500;
|
||||
--font-semibold: 600;
|
||||
--font-bold: 700;
|
||||
}
|
||||
|
||||
/* Reset & Base */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif;
|
||||
font-size: var(--text-base);
|
||||
line-height: 1.5;
|
||||
color: var(--gray-700);
|
||||
background-color: var(--gray-50);
|
||||
min-height: 100vh;
|
||||
padding-bottom: 100px; /* Space for fixed buttons */
|
||||
}
|
||||
|
||||
/* Container */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
padding: var(--space-4);
|
||||
background-color: white;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 480px;
|
||||
padding: var(--space-6);
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
text-align: center;
|
||||
padding: var(--space-6) 0;
|
||||
border-bottom: 1px solid var(--gray-200);
|
||||
margin-bottom: var(--space-8);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: var(--text-2xl);
|
||||
font-weight: var(--font-bold);
|
||||
color: var(--gray-900);
|
||||
}
|
||||
|
||||
/* Result Status */
|
||||
.result-status {
|
||||
text-align: center;
|
||||
margin-bottom: var(--space-8);
|
||||
padding: var(--space-8);
|
||||
}
|
||||
|
||||
.result-icon {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin: 0 auto var(--space-6);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--text-4xl);
|
||||
animation: scaleIn 0.6s ease-out;
|
||||
}
|
||||
|
||||
.result-status.success .result-icon {
|
||||
background: linear-gradient(135deg, var(--success-500) 0%, var(--success-600) 100%);
|
||||
color: white;
|
||||
box-shadow: 0 8px 25px rgba(56, 161, 105, 0.3);
|
||||
}
|
||||
|
||||
.result-status.error .result-icon {
|
||||
background: linear-gradient(135deg, var(--error-500) 0%, var(--error-600) 100%);
|
||||
color: white;
|
||||
box-shadow: 0 8px 25px rgba(229, 62, 62, 0.3);
|
||||
}
|
||||
|
||||
@keyframes scaleIn {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.result-title {
|
||||
font-size: var(--text-2xl);
|
||||
font-weight: var(--font-bold);
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.result-status.success .result-title {
|
||||
color: var(--success-600);
|
||||
}
|
||||
|
||||
.result-status.error .result-title {
|
||||
color: var(--error-600);
|
||||
}
|
||||
|
||||
.result-description {
|
||||
font-size: var(--text-base);
|
||||
color: var(--gray-600);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Card */
|
||||
.card {
|
||||
background-color: white;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
padding: var(--space-6);
|
||||
margin-bottom: var(--space-6);
|
||||
border: 1px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--gray-900);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
/* Success Details */
|
||||
.success-details {
|
||||
background: linear-gradient(135deg, var(--success-50) 0%, var(--success-100) 100%);
|
||||
border: 2px solid var(--success-100);
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: var(--space-3) 0;
|
||||
border-bottom: 1px solid rgba(56, 161, 105, 0.1);
|
||||
}
|
||||
|
||||
.detail-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-600);
|
||||
font-weight: var(--font-medium);
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.detail-value.product {
|
||||
color: var(--primary-600);
|
||||
}
|
||||
|
||||
.detail-value.date {
|
||||
color: var(--success-600);
|
||||
}
|
||||
|
||||
.detail-value.number {
|
||||
color: var(--gray-900);
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
/* Error Details */
|
||||
.error-details {
|
||||
background: linear-gradient(135deg, var(--error-50) 0%, var(--error-100) 100%);
|
||||
border: 2px solid var(--error-100);
|
||||
}
|
||||
|
||||
.error-reason {
|
||||
background: #FEF2F2;
|
||||
border: 1px solid #FECACA;
|
||||
border-radius: 12px;
|
||||
padding: var(--space-4);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.error-title {
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--error-600);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.error-text {
|
||||
font-size: var(--text-sm);
|
||||
color: #991B1B;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.solution-steps {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.solution-step {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-3);
|
||||
margin-bottom: var(--space-3);
|
||||
padding: var(--space-3);
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: var(--primary-500);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-semibold);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.step-text {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-700);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Contact Info */
|
||||
.contact-info {
|
||||
background: var(--gray-50);
|
||||
border: 1px solid var(--gray-200);
|
||||
border-radius: 12px;
|
||||
padding: var(--space-4);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.contact-title {
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--gray-900);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.contact-number {
|
||||
font-size: var(--text-xl);
|
||||
font-weight: var(--font-bold);
|
||||
color: var(--primary-600);
|
||||
margin-bottom: var(--space-1);
|
||||
}
|
||||
|
||||
.contact-hours {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-500);
|
||||
}
|
||||
|
||||
/* Fixed Action Buttons */
|
||||
.fixed-actions {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
background: white;
|
||||
padding: var(--space-4);
|
||||
border-top: 1px solid var(--gray-200);
|
||||
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.fixed-actions {
|
||||
max-width: 480px;
|
||||
padding: var(--space-6);
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-4) var(--space-6);
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
line-height: 1.5;
|
||||
transition: all 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
min-height: 52px;
|
||||
text-decoration: none;
|
||||
width: 100%;
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.btn:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--primary-500) 0%, var(--primary-600) 100%);
|
||||
color: white;
|
||||
box-shadow: 0 2px 8px rgba(49, 130, 206, 0.2);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(49, 130, 206, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: white;
|
||||
color: var(--gray-600);
|
||||
border: 2px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--gray-50);
|
||||
border-color: var(--gray-300);
|
||||
color: var(--gray-700);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: linear-gradient(135deg, var(--success-500) 0%, var(--success-600) 100%);
|
||||
color: white;
|
||||
box-shadow: 0 2px 8px rgba(56, 161, 105, 0.2);
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(56, 161, 105, 0.3);
|
||||
}
|
||||
|
||||
.btn-error {
|
||||
background: linear-gradient(135deg, var(--error-500) 0%, var(--error-600) 100%);
|
||||
color: white;
|
||||
box-shadow: 0 2px 8px rgba(229, 62, 62, 0.2);
|
||||
}
|
||||
|
||||
.btn-error:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(229, 62, 62, 0.3);
|
||||
}
|
||||
|
||||
/* Loading */
|
||||
.loading {
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.loading::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-top: 2px solid white;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Hidden class for conditional display */
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<h1 class="page-title">처리 결과</h1>
|
||||
</header>
|
||||
|
||||
<!-- Success Result -->
|
||||
<div id="successResult" class="result-status success">
|
||||
<div class="result-icon">✓</div>
|
||||
<h2 class="result-title">상품 변경이 완료되었습니다</h2>
|
||||
<p class="result-description">
|
||||
선택하신 상품으로 변경 신청이 성공적으로 처리되었습니다.<br>
|
||||
변경된 상품은 다음 월 1일부터 적용됩니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Error Result (Hidden by default) -->
|
||||
<div id="errorResult" class="result-status error hidden">
|
||||
<div class="result-icon">✕</div>
|
||||
<h2 class="result-title">상품 변경에 실패했습니다</h2>
|
||||
<p class="result-description">
|
||||
죄송합니다. 상품 변경 처리 중 문제가 발생했습니다.<br>
|
||||
아래의 해결 방법을 확인해주세요.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Success Details -->
|
||||
<div id="successDetails" class="card success-details">
|
||||
<h3 class="card-title">변경 내용</h3>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">변경된 상품</span>
|
||||
<span class="detail-value product" id="changedProduct">5G 스탠다드 플랜</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">월 요금</span>
|
||||
<span class="detail-value product" id="changedPrice">월 59,000원</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">적용일</span>
|
||||
<span class="detail-value date">2025년 2월 1일</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">처리번호</span>
|
||||
<span class="detail-value number" id="processNumber">CHG-2025010512345</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">처리일시</span>
|
||||
<span class="detail-value" id="processTime">2025-01-05 14:23:45</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error Details (Hidden by default) -->
|
||||
<div id="errorDetails" class="card error-details hidden">
|
||||
<h3 class="card-title">실패 사유</h3>
|
||||
<div class="error-reason">
|
||||
<div class="error-title">약정 위반으로 인한 변경 불가</div>
|
||||
<div class="error-text">
|
||||
현재 이용 중인 상품의 약정 기간이 남아있어 상품 변경이 불가능합니다.
|
||||
약정 해지 후 다시 시도하시거나 고객센터로 문의해 주세요.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="card-title">해결 방법</h4>
|
||||
<ol class="solution-steps">
|
||||
<li class="solution-step">
|
||||
<span class="step-number">1</span>
|
||||
<span class="step-text">현재 약정 상태와 위약금을 확인해보세요</span>
|
||||
</li>
|
||||
<li class="solution-step">
|
||||
<span class="step-number">2</span>
|
||||
<span class="step-text">약정 기간 만료 후 다시 시도하거나 위약금을 납부하고 변경하세요</span>
|
||||
</li>
|
||||
<li class="solution-step">
|
||||
<span class="step-number">3</span>
|
||||
<span class="step-text">고객센터에 문의하여 다른 변경 방법을 안내받으세요</span>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<div class="contact-info">
|
||||
<div class="contact-title">고객센터</div>
|
||||
<div class="contact-number">1588-0000</div>
|
||||
<div class="contact-hours">평일 09:00~18:00 (토/일/공휴일 휴무)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fixed Action Buttons -->
|
||||
<div class="fixed-actions">
|
||||
<!-- Success Actions -->
|
||||
<div id="successActions">
|
||||
<button class="btn btn-primary" onclick="goToMain()">
|
||||
메인으로
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="viewBill()">
|
||||
요금 조회
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Error Actions (Hidden by default) -->
|
||||
<div id="errorActions" class="hidden">
|
||||
<button class="btn btn-error" onclick="retryChange()">
|
||||
다시 시도
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="contactSupport()">
|
||||
고객센터 연결
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="goToMain()">
|
||||
메인으로
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Load request data and determine result
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadResultData();
|
||||
|
||||
// Handle keyboard navigation
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter' && e.target.tagName === 'BUTTON') {
|
||||
e.target.click();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function loadResultData() {
|
||||
const requestData = sessionStorage.getItem('changeRequest');
|
||||
|
||||
// Simulate random success/failure for demo
|
||||
// In real app, this would come from the API response
|
||||
const isSuccess = Math.random() > 0.2; // 80% success rate for demo
|
||||
|
||||
if (requestData) {
|
||||
const request = JSON.parse(requestData);
|
||||
|
||||
if (isSuccess) {
|
||||
showSuccessResult(request);
|
||||
} else {
|
||||
showErrorResult();
|
||||
}
|
||||
} else {
|
||||
// Default success display
|
||||
showSuccessResult({
|
||||
newProduct: '5G 스탠다드 플랜',
|
||||
newPrice: '월 59,000원'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function showSuccessResult(requestData) {
|
||||
// Show success elements
|
||||
document.getElementById('successResult').classList.remove('hidden');
|
||||
document.getElementById('successDetails').classList.remove('hidden');
|
||||
document.getElementById('successActions').classList.remove('hidden');
|
||||
|
||||
// Hide error elements
|
||||
document.getElementById('errorResult').classList.add('hidden');
|
||||
document.getElementById('errorDetails').classList.add('hidden');
|
||||
document.getElementById('errorActions').classList.add('hidden');
|
||||
|
||||
// Update success details
|
||||
if (requestData.newProduct) {
|
||||
document.getElementById('changedProduct').textContent = requestData.newProduct;
|
||||
}
|
||||
if (requestData.newPrice) {
|
||||
document.getElementById('changedPrice').textContent = requestData.newPrice;
|
||||
}
|
||||
|
||||
// Save successful product change to localStorage
|
||||
const newProduct = {
|
||||
name: requestData.newProduct || '5G 스탠다드 플랜',
|
||||
price: requestData.newPrice || '월 59,000원',
|
||||
benefits: getProductBenefits(requestData.newProduct),
|
||||
changeDate: new Date().toISOString()
|
||||
};
|
||||
|
||||
localStorage.setItem('currentProduct', JSON.stringify(newProduct));
|
||||
|
||||
// Generate process number and time
|
||||
const processNumber = `CHG-${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}${String(new Date().getDate()).padStart(2, '0')}${String(Math.floor(Math.random() * 99999)).padStart(5, '0')}`;
|
||||
const processTime = new Date().toLocaleString('ko-KR', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false
|
||||
}).replace(/\. /g, '-').replace('.', '');
|
||||
|
||||
document.getElementById('processNumber').textContent = processNumber;
|
||||
document.getElementById('processTime').textContent = processTime;
|
||||
}
|
||||
|
||||
// Get product benefits based on product name
|
||||
function getProductBenefits(productName) {
|
||||
const productBenefits = {
|
||||
'5G 스탠다드 플랜': [
|
||||
'5G 데이터 100GB',
|
||||
'음성통화 무제한',
|
||||
'문자 무제한',
|
||||
'영상통화 300분'
|
||||
],
|
||||
'5G 베이직 플랜': [
|
||||
'5G 데이터 50GB',
|
||||
'음성통화 무제한',
|
||||
'문자 무제한',
|
||||
'영상통화 300분'
|
||||
],
|
||||
'5G 프리미엄 플랜': [
|
||||
'5G 데이터 무제한',
|
||||
'음성통화 무제한',
|
||||
'문자 무제한',
|
||||
'해외 로밍 50% 할인'
|
||||
]
|
||||
};
|
||||
|
||||
return productBenefits[productName] || [
|
||||
'5G 데이터 무제한',
|
||||
'음성통화 무제한',
|
||||
'문자 무제한'
|
||||
];
|
||||
}
|
||||
|
||||
function showErrorResult() {
|
||||
// Show error elements
|
||||
document.getElementById('errorResult').classList.remove('hidden');
|
||||
document.getElementById('errorDetails').classList.remove('hidden');
|
||||
document.getElementById('errorActions').classList.remove('hidden');
|
||||
|
||||
// Hide success elements
|
||||
document.getElementById('successResult').classList.add('hidden');
|
||||
document.getElementById('successDetails').classList.add('hidden');
|
||||
document.getElementById('successActions').classList.add('hidden');
|
||||
}
|
||||
|
||||
// Navigation functions
|
||||
function goToMain() {
|
||||
const btn = event.target;
|
||||
btn.classList.add('loading');
|
||||
btn.disabled = true;
|
||||
|
||||
setTimeout(() => {
|
||||
window.location.href = '02-메인화면.html';
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function viewBill() {
|
||||
const btn = event.target;
|
||||
btn.classList.add('loading');
|
||||
btn.disabled = true;
|
||||
|
||||
setTimeout(() => {
|
||||
window.location.href = '03-요금조회메뉴.html';
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function retryChange() {
|
||||
const btn = event.target;
|
||||
btn.classList.add('loading');
|
||||
btn.disabled = true;
|
||||
|
||||
// Clear previous data
|
||||
sessionStorage.removeItem('changeRequest');
|
||||
sessionStorage.removeItem('selectedProduct');
|
||||
|
||||
setTimeout(() => {
|
||||
window.location.href = '05-상품변경메뉴.html';
|
||||
}, 800);
|
||||
}
|
||||
|
||||
function contactSupport() {
|
||||
// Simulate contacting support
|
||||
alert('고객센터 연결 기능입니다.\n\n전화번호: 1588-0000\n운영시간: 평일 09:00~18:00');
|
||||
}
|
||||
|
||||
// Auto-clear session data after 30 minutes to prevent stale data
|
||||
setTimeout(() => {
|
||||
sessionStorage.removeItem('changeRequest');
|
||||
sessionStorage.removeItem('selectedProduct');
|
||||
}, 30 * 60 * 1000); // 30 minutes
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,743 @@
|
||||
# 통신요금 관리 서비스 - 스타일 가이드
|
||||
|
||||
- [통신요금 관리 서비스 - 스타일 가이드](#통신요금-관리-서비스---스타일-가이드)
|
||||
- [브랜드 아이덴티티](#브랜드-아이덴티티)
|
||||
- [디자인 원칙](#디자인-원칙)
|
||||
- [컬러 시스템](#컬러-시스템)
|
||||
- [타이포그래피](#타이포그래피)
|
||||
- [간격 시스템](#간격-시스템)
|
||||
- [컴포넌트 스타일](#컴포넌트-스타일)
|
||||
- [반응형 브레이크포인트](#반응형-브레이크포인트)
|
||||
- [대상 서비스 특화 컴포넌트](#대상-서비스-특화-컴포넌트)
|
||||
- [인터랙션 패턴](#인터랙션-패턴)
|
||||
- [변경 이력](#변경-이력)
|
||||
|
||||
---
|
||||
|
||||
## 브랜드 아이덴티티
|
||||
|
||||
### 서비스 컨셉
|
||||
- **키워드**: 신뢰성, 편리함, 명확성
|
||||
- **브랜드 메시지**: "간편하고 안전한 통신요금 관리"
|
||||
- **타겟**: 일반 MVNO 고객 (20대~60대)
|
||||
|
||||
### 디자인 컨셉
|
||||
- **미니멀리즘**: 불필요한 요소 제거, 핵심 기능 집중
|
||||
- **명확성 우선**: 정보 전달의 명확성과 가독성
|
||||
- **안정감**: 금융 서비스의 신뢰성과 보안성 강조
|
||||
- **접근성**: 모든 사용자가 편리하게 이용할 수 있는 인터페이스
|
||||
|
||||
---
|
||||
|
||||
## 디자인 원칙
|
||||
|
||||
### 1. 명확성 (Clarity)
|
||||
- 모든 UI 요소는 그 목적이 명확해야 함
|
||||
- 전문용어 사용 최소화, 일반적인 표현 우선
|
||||
- 중요 정보는 시각적으로 강조
|
||||
|
||||
### 2. 일관성 (Consistency)
|
||||
- 동일한 요소는 동일한 스타일 적용
|
||||
- 예측 가능한 인터랙션 패턴
|
||||
- 통일된 색상과 타이포그래피 사용
|
||||
|
||||
### 3. 효율성 (Efficiency)
|
||||
- 최소한의 클릭으로 목표 달성
|
||||
- 불필요한 단계 제거
|
||||
- 빠른 로딩과 반응성 보장
|
||||
|
||||
### 4. 안전성 (Safety)
|
||||
- 중요한 액션에는 확인 단계 제공
|
||||
- 오류 방지와 명확한 피드백
|
||||
- 개인정보 보호 강조
|
||||
|
||||
### 5. 포용성 (Inclusivity)
|
||||
- 접근성 지침 준수
|
||||
- 다양한 디바이스와 환경 지원
|
||||
- 사용자 능력과 상황 고려
|
||||
|
||||
---
|
||||
|
||||
## 컬러 시스템
|
||||
|
||||
### Primary Colors
|
||||
```css
|
||||
/* 메인 브랜드 컬러 - 신뢰감을 주는 블루 */
|
||||
--primary-50: #EBF8FF;
|
||||
--primary-100: #BEE3F8;
|
||||
--primary-200: #90CDF4;
|
||||
--primary-300: #63B3ED;
|
||||
--primary-400: #4299E1;
|
||||
--primary-500: #3182CE; /* Main Brand Color */
|
||||
--primary-600: #2B77CB;
|
||||
--primary-700: #2C5282;
|
||||
--primary-800: #2A4365;
|
||||
--primary-900: #1A365D;
|
||||
```
|
||||
|
||||
### Secondary Colors
|
||||
```css
|
||||
/* 보조 컬러 - 포인트 및 상태 표시 */
|
||||
--secondary-50: #F7FAFC;
|
||||
--secondary-100: #EDF2F7;
|
||||
--secondary-200: #E2E8F0;
|
||||
--secondary-300: #CBD5E0;
|
||||
--secondary-400: #A0AEC0;
|
||||
--secondary-500: #718096;
|
||||
--secondary-600: #4A5568;
|
||||
--secondary-700: #2D3748;
|
||||
--secondary-800: #1A202C;
|
||||
--secondary-900: #171923;
|
||||
```
|
||||
|
||||
### Status Colors
|
||||
```css
|
||||
/* 성공 - 그린 */
|
||||
--success-50: #F0FFF4;
|
||||
--success-100: #C6F6D5;
|
||||
--success-500: #38A169;
|
||||
--success-600: #2F855A;
|
||||
|
||||
/* 경고 - 오렌지 */
|
||||
--warning-50: #FFFAF0;
|
||||
--warning-100: #FEEBC8;
|
||||
--warning-500: #ED8936;
|
||||
--warning-600: #DD6B20;
|
||||
|
||||
/* 오류 - 레드 */
|
||||
--error-50: #FED7D7;
|
||||
--error-100: #FED7D7;
|
||||
--error-500: #E53E3E;
|
||||
--error-600: #C53030;
|
||||
|
||||
/* 정보 - 블루 */
|
||||
--info-50: #EBF8FF;
|
||||
--info-100: #BEE3F8;
|
||||
--info-500: #3182CE;
|
||||
--info-600: #2B77CB;
|
||||
```
|
||||
|
||||
### Neutral Colors
|
||||
```css
|
||||
/* 텍스트 및 배경 */
|
||||
--gray-50: #F9FAFB; /* Background Light */
|
||||
--gray-100: #F3F4F6; /* Background */
|
||||
--gray-200: #E5E7EB; /* Border Light */
|
||||
--gray-300: #D1D5DB; /* Border */
|
||||
--gray-400: #9CA3AF; /* Text Muted */
|
||||
--gray-500: #6B7280; /* Text Secondary */
|
||||
--gray-600: #4B5563; /* Text Primary Light */
|
||||
--gray-700: #374151; /* Text Primary */
|
||||
--gray-800: #1F2937; /* Text Primary Dark */
|
||||
--gray-900: #111827; /* Text Emphasis */
|
||||
```
|
||||
|
||||
### 컬러 사용 가이드
|
||||
- **Primary**: 주요 액션 버튼, 링크, 브랜드 요소
|
||||
- **Secondary**: 보조 버튼, 아이콘, 경계선
|
||||
- **Success**: 성공 메시지, 완료 상태
|
||||
- **Warning**: 주의 메시지, 중요 알림
|
||||
- **Error**: 오류 메시지, 실패 상태
|
||||
- **Gray**: 텍스트, 배경, 구분선
|
||||
|
||||
---
|
||||
|
||||
## 타이포그래피
|
||||
|
||||
### 폰트 패밀리
|
||||
```css
|
||||
/* 기본 폰트 스택 */
|
||||
font-family:
|
||||
'Noto Sans KR', /* 한글 */
|
||||
'Roboto', /* 영문 */
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Apple SD Gothic Neo',
|
||||
'Malgun Gothic',
|
||||
sans-serif;
|
||||
```
|
||||
|
||||
### 폰트 크기 및 행간
|
||||
```css
|
||||
/* Heading */
|
||||
--text-4xl: 2.25rem; /* 36px - Page Title */
|
||||
--text-3xl: 1.875rem; /* 30px - Section Title */
|
||||
--text-2xl: 1.5rem; /* 24px - Card Title */
|
||||
--text-xl: 1.25rem; /* 20px - Sub Title */
|
||||
--text-lg: 1.125rem; /* 18px - Large Text */
|
||||
|
||||
/* Body */
|
||||
--text-base: 1rem; /* 16px - Body Text */
|
||||
--text-sm: 0.875rem; /* 14px - Small Text */
|
||||
--text-xs: 0.75rem; /* 12px - Caption */
|
||||
|
||||
/* Line Height */
|
||||
--leading-tight: 1.25; /* Heading */
|
||||
--leading-normal: 1.5; /* Body */
|
||||
--leading-relaxed: 1.625; /* Long Text */
|
||||
```
|
||||
|
||||
### 폰트 두께
|
||||
```css
|
||||
--font-light: 300; /* Light text */
|
||||
--font-normal: 400; /* Body text */
|
||||
--font-medium: 500; /* Emphasis */
|
||||
--font-semibold: 600; /* Sub heading */
|
||||
--font-bold: 700; /* Heading */
|
||||
```
|
||||
|
||||
### 타이포그래피 클래스
|
||||
```css
|
||||
/* Heading Styles */
|
||||
.heading-1 { font-size: 2.25rem; font-weight: 700; line-height: 1.25; }
|
||||
.heading-2 { font-size: 1.875rem; font-weight: 600; line-height: 1.25; }
|
||||
.heading-3 { font-size: 1.5rem; font-weight: 600; line-height: 1.25; }
|
||||
.heading-4 { font-size: 1.25rem; font-weight: 500; line-height: 1.25; }
|
||||
|
||||
/* Body Styles */
|
||||
.body-large { font-size: 1.125rem; font-weight: 400; line-height: 1.5; }
|
||||
.body-normal { font-size: 1rem; font-weight: 400; line-height: 1.5; }
|
||||
.body-small { font-size: 0.875rem; font-weight: 400; line-height: 1.5; }
|
||||
.caption { font-size: 0.75rem; font-weight: 400; line-height: 1.25; }
|
||||
|
||||
/* Emphasis */
|
||||
.text-emphasis { font-weight: 600; color: var(--gray-900); }
|
||||
.text-muted { color: var(--gray-500); }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 간격 시스템
|
||||
|
||||
### 기본 간격 단위 (8px 그리드 시스템)
|
||||
```css
|
||||
--space-0: 0;
|
||||
--space-1: 0.25rem; /* 4px */
|
||||
--space-2: 0.5rem; /* 8px */
|
||||
--space-3: 0.75rem; /* 12px */
|
||||
--space-4: 1rem; /* 16px */
|
||||
--space-5: 1.25rem; /* 20px */
|
||||
--space-6: 1.5rem; /* 24px */
|
||||
--space-8: 2rem; /* 32px */
|
||||
--space-10: 2.5rem; /* 40px */
|
||||
--space-12: 3rem; /* 48px */
|
||||
--space-16: 4rem; /* 64px */
|
||||
--space-20: 5rem; /* 80px */
|
||||
```
|
||||
|
||||
### 컴포넌트별 간격 가이드
|
||||
- **Component Padding**: 16px (space-4) - 24px (space-6)
|
||||
- **Content Margin**: 16px (space-4) - 32px (space-8)
|
||||
- **Section Gap**: 32px (space-8) - 48px (space-12)
|
||||
- **Page Padding**: 20px (space-5) - 40px (space-10)
|
||||
|
||||
### 레이아웃 간격
|
||||
```css
|
||||
/* Container */
|
||||
--container-padding-mobile: var(--space-4); /* 16px */
|
||||
--container-padding-tablet: var(--space-6); /* 24px */
|
||||
--container-padding-desktop: var(--space-8); /* 32px */
|
||||
|
||||
/* Grid Gap */
|
||||
--grid-gap-small: var(--space-4); /* 16px */
|
||||
--grid-gap-medium: var(--space-6); /* 24px */
|
||||
--grid-gap-large: var(--space-8); /* 32px */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 컴포넌트 스타일
|
||||
|
||||
### 버튼 (Button)
|
||||
```css
|
||||
/* Base Button */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-3) var(--space-6);
|
||||
border-radius: 8px;
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-medium);
|
||||
line-height: 1.5;
|
||||
transition: all 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
min-height: 44px; /* 터치 접근성 */
|
||||
}
|
||||
|
||||
/* Primary Button */
|
||||
.btn-primary {
|
||||
background-color: var(--primary-500);
|
||||
color: white;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background-color: var(--primary-600);
|
||||
}
|
||||
.btn-primary:disabled {
|
||||
background-color: var(--gray-300);
|
||||
color: var(--gray-500);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Secondary Button */
|
||||
.btn-secondary {
|
||||
background-color: white;
|
||||
color: var(--gray-700);
|
||||
border: 1px solid var(--gray-300);
|
||||
}
|
||||
.btn-secondary:hover {
|
||||
background-color: var(--gray-50);
|
||||
border-color: var(--gray-400);
|
||||
}
|
||||
|
||||
/* Danger Button */
|
||||
.btn-danger {
|
||||
background-color: var(--error-500);
|
||||
color: white;
|
||||
}
|
||||
.btn-danger:hover {
|
||||
background-color: var(--error-600);
|
||||
}
|
||||
```
|
||||
|
||||
### 카드 (Card)
|
||||
```css
|
||||
.card {
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
padding: var(--space-6);
|
||||
border: 1px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
margin-bottom: var(--space-4);
|
||||
padding-bottom: var(--space-4);
|
||||
border-bottom: 1px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: var(--text-xl);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--gray-900);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
color: var(--gray-700);
|
||||
}
|
||||
```
|
||||
|
||||
### 폼 요소 (Form)
|
||||
```css
|
||||
/* Input */
|
||||
.input {
|
||||
width: 100%;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border: 1px solid var(--gray-300);
|
||||
border-radius: 8px;
|
||||
font-size: var(--text-base);
|
||||
line-height: 1.5;
|
||||
transition: border-color 0.2s ease-in-out;
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-500);
|
||||
box-shadow: 0 0 0 3px rgba(49, 130, 206, 0.1);
|
||||
}
|
||||
|
||||
.input.error {
|
||||
border-color: var(--error-500);
|
||||
}
|
||||
|
||||
/* Label */
|
||||
.label {
|
||||
display: block;
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-medium);
|
||||
color: var(--gray-700);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
/* Select */
|
||||
.select {
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml,..."); /* 드롭다운 아이콘 */
|
||||
background-repeat: no-repeat;
|
||||
background-position: right var(--space-3) center;
|
||||
background-size: 16px;
|
||||
}
|
||||
```
|
||||
|
||||
### 알림 메시지 (Alert)
|
||||
```css
|
||||
.alert {
|
||||
padding: var(--space-4);
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background-color: var(--success-50);
|
||||
border-color: var(--success-500);
|
||||
color: var(--success-800);
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
background-color: var(--warning-50);
|
||||
border-color: var(--warning-500);
|
||||
color: var(--warning-800);
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
background-color: var(--error-50);
|
||||
border-color: var(--error-500);
|
||||
color: var(--error-800);
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
background-color: var(--info-50);
|
||||
border-color: var(--info-500);
|
||||
color: var(--info-800);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 반응형 브레이크포인트
|
||||
|
||||
### 브레이크포인트 정의
|
||||
```css
|
||||
/* Mobile First Approach */
|
||||
:root {
|
||||
--breakpoint-sm: 640px; /* Small devices */
|
||||
--breakpoint-md: 768px; /* Medium devices */
|
||||
--breakpoint-lg: 1024px; /* Large devices */
|
||||
--breakpoint-xl: 1280px; /* Extra large devices */
|
||||
}
|
||||
|
||||
/* Media Query Mixins */
|
||||
@media (min-width: 640px) { /* sm */ }
|
||||
@media (min-width: 768px) { /* md */ }
|
||||
@media (min-width: 1024px) { /* lg */ }
|
||||
@media (min-width: 1280px) { /* xl */ }
|
||||
```
|
||||
|
||||
### 반응형 컨테이너
|
||||
```css
|
||||
.container {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--space-4);
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.container {
|
||||
max-width: 640px;
|
||||
padding: 0 var(--space-6);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 1024px;
|
||||
padding: 0 var(--space-8);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 반응형 그리드
|
||||
```css
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
grid-template-columns: 1fr; /* Mobile: 1 column */
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.grid-md-2 {
|
||||
grid-template-columns: repeat(2, 1fr); /* Tablet: 2 columns */
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.grid-lg-3 {
|
||||
grid-template-columns: repeat(3, 1fr); /* Desktop: 3 columns */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 대상 서비스 특화 컴포넌트
|
||||
|
||||
### 요금 정보 카드 (Bill Card)
|
||||
```css
|
||||
.bill-card {
|
||||
background: linear-gradient(135deg, var(--primary-500) 0%, var(--primary-600) 100%);
|
||||
color: white;
|
||||
padding: var(--space-6);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 20px rgba(49, 130, 206, 0.2);
|
||||
}
|
||||
|
||||
.bill-amount {
|
||||
font-size: var(--text-4xl);
|
||||
font-weight: var(--font-bold);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.bill-period {
|
||||
font-size: var(--text-sm);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.bill-details {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 8px;
|
||||
padding: var(--space-4);
|
||||
margin-top: var(--space-4);
|
||||
}
|
||||
```
|
||||
|
||||
### 상품 비교 카드 (Product Card)
|
||||
```css
|
||||
.product-card {
|
||||
border: 2px solid var(--gray-200);
|
||||
border-radius: 12px;
|
||||
padding: var(--space-6);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.product-card.selected {
|
||||
border-color: var(--primary-500);
|
||||
box-shadow: 0 0 0 3px rgba(49, 130, 206, 0.1);
|
||||
}
|
||||
|
||||
.product-card.current {
|
||||
background-color: var(--success-50);
|
||||
border-color: var(--success-500);
|
||||
}
|
||||
|
||||
.product-badge {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: var(--space-4);
|
||||
background-color: var(--primary-500);
|
||||
color: white;
|
||||
padding: var(--space-1) var(--space-3);
|
||||
border-radius: 999px;
|
||||
font-size: var(--text-xs);
|
||||
font-weight: var(--font-medium);
|
||||
}
|
||||
|
||||
.product-price {
|
||||
font-size: var(--text-2xl);
|
||||
font-weight: var(--font-bold);
|
||||
color: var(--primary-600);
|
||||
}
|
||||
```
|
||||
|
||||
### 진행 상태 표시 (Progress)
|
||||
```css
|
||||
.progress-container {
|
||||
background-color: var(--gray-100);
|
||||
border-radius: 999px;
|
||||
height: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--primary-500) 0%, var(--primary-400) 100%);
|
||||
border-radius: 999px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-steps {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.progress-step {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-500);
|
||||
}
|
||||
|
||||
.progress-step.active {
|
||||
color: var(--primary-600);
|
||||
font-weight: var(--font-medium);
|
||||
}
|
||||
|
||||
.progress-step.completed {
|
||||
color: var(--success-600);
|
||||
}
|
||||
```
|
||||
|
||||
### 상태 뱃지 (Status Badge)
|
||||
```css
|
||||
.status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: var(--space-1) var(--space-3);
|
||||
border-radius: 999px;
|
||||
font-size: var(--text-xs);
|
||||
font-weight: var(--font-medium);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.status-badge.processing {
|
||||
background-color: var(--warning-100);
|
||||
color: var(--warning-800);
|
||||
}
|
||||
|
||||
.status-badge.completed {
|
||||
background-color: var(--success-100);
|
||||
color: var(--success-800);
|
||||
}
|
||||
|
||||
.status-badge.failed {
|
||||
background-color: var(--error-100);
|
||||
color: var(--error-800);
|
||||
}
|
||||
|
||||
.status-badge::before {
|
||||
content: "";
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background-color: currentColor;
|
||||
margin-right: var(--space-2);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 인터랙션 패턴
|
||||
|
||||
### 애니메이션 타이밍
|
||||
```css
|
||||
:root {
|
||||
--duration-fast: 0.15s;
|
||||
--duration-normal: 0.3s;
|
||||
--duration-slow: 0.5s;
|
||||
|
||||
--ease-in: cubic-bezier(0.4, 0, 1, 1);
|
||||
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
||||
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
```
|
||||
|
||||
### 호버 효과
|
||||
```css
|
||||
.interactive:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
transition: all var(--duration-normal) var(--ease-out);
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
```
|
||||
|
||||
### 로딩 상태
|
||||
```css
|
||||
.loading {
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.loading::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid var(--gray-300);
|
||||
border-top: 2px solid var(--primary-500);
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
||||
}
|
||||
```
|
||||
|
||||
### 포커스 상태
|
||||
```css
|
||||
.focusable:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px rgba(49, 130, 206, 0.3);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.focus-visible {
|
||||
outline: 2px solid var(--primary-500);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
```
|
||||
|
||||
### 상태 전환
|
||||
```css
|
||||
.fade-enter {
|
||||
opacity: 0;
|
||||
}
|
||||
.fade-enter-active {
|
||||
opacity: 1;
|
||||
transition: opacity var(--duration-normal) var(--ease-out);
|
||||
}
|
||||
|
||||
.slide-enter {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
.slide-enter-active {
|
||||
transform: translateX(0);
|
||||
transition: transform var(--duration-normal) var(--ease-out);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 변경 이력
|
||||
|
||||
| 버전 | 날짜 | 변경사항 | 작성자 |
|
||||
|------|------|----------|--------|
|
||||
| 1.0 | 2025-01-05 | 초기 스타일 가이드 작성 | 박화면 |
|
||||
|
||||
---
|
||||
|
||||
## 스타일 가이드 활용 방법
|
||||
|
||||
### CSS 변수 사용
|
||||
모든 스타일 정의에서 CSS 변수를 사용하여 일관성을 유지하고 쉬운 테마 변경을 지원합니다.
|
||||
|
||||
### 컴포넌트 기반 설계
|
||||
재사용 가능한 컴포넌트 스타일을 정의하여 개발 효율성과 일관성을 높입니다.
|
||||
|
||||
### 접근성 고려
|
||||
모든 컴포넌트는 WCAG 2.1 AA 기준을 준수하여 접근성을 보장합니다.
|
||||
|
||||
### 반응형 우선
|
||||
Mobile First 접근 방식으로 모든 디바이스에서 최적의 사용자 경험을 제공합니다.
|
||||
@@ -0,0 +1,578 @@
|
||||
# 통신요금 관리 서비스 - UI/UX 설계서
|
||||
|
||||
- [통신요금 관리 서비스 - UI/UX 설계서](#통신요금-관리-서비스---uiux-설계서)
|
||||
- [프로젝트 개요](#프로젝트-개요)
|
||||
- [정보 아키텍처](#정보-아키텍처)
|
||||
- [프로토타입 화면 목록](#프로토타입-화면-목록)
|
||||
- [사용자 플로우](#사용자-플로우)
|
||||
- [화면별 상세 설계](#화면별-상세-설계)
|
||||
- [화면간 전환 및 네비게이션](#화면간-전환-및-네비게이션)
|
||||
- [반응형 설계 전략](#반응형-설계-전략)
|
||||
- [접근성 보장 방안](#접근성-보장-방안)
|
||||
- [성능 최적화 방안](#성능-최적화-방안)
|
||||
- [변경 이력](#변경-이력)
|
||||
|
||||
---
|
||||
|
||||
## 프로젝트 개요
|
||||
|
||||
### 서비스 목적
|
||||
MVNO 고객의 통신요금 조회 및 상품변경을 지원하는 웹 서비스
|
||||
|
||||
### 주요 기능
|
||||
1. **사용자 인증**: 안전한 로그인/로그아웃
|
||||
2. **요금 조회**: 월별 통신요금 조회
|
||||
3. **상품 변경**: 요금제 변경 요청 및 처리
|
||||
|
||||
### 설계 기준
|
||||
- **유저스토리 기반**: 총 10개 유저스토리 100% 반영
|
||||
- **B2C 웹 서비스**: 일반 고객 대상
|
||||
- **보안 우선**: 개인정보 및 금융정보 보호
|
||||
- **사용성 중심**: 직관적이고 간단한 UI/UX
|
||||
|
||||
---
|
||||
|
||||
## 정보 아키텍처
|
||||
|
||||
### 서비스 구조
|
||||
```
|
||||
통신요금 관리 서비스
|
||||
├── 인증 영역
|
||||
│ ├── 로그인
|
||||
│ └── 권한 확인
|
||||
├── 요금 조회 영역
|
||||
│ ├── 조회 메뉴
|
||||
│ ├── 조회 신청
|
||||
│ └── 조회 결과
|
||||
└── 상품 변경 영역
|
||||
├── 변경 메뉴
|
||||
├── 변경 화면
|
||||
├── 변경 요청
|
||||
└── 처리 결과
|
||||
```
|
||||
|
||||
### 네비게이션 구조
|
||||
```
|
||||
메인 화면
|
||||
├── 요금 조회 메뉴 → 요금 조회 신청 → 조회 결과
|
||||
└── 상품 변경 메뉴 → 상품 변경 화면 → 변경 요청 → 처리 결과
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 프로토타입 화면 목록
|
||||
|
||||
| 화면 ID | 화면명 | 관련 유저스토리 | 우선순위 |
|
||||
|---------|--------|-----------------|----------|
|
||||
| SCR-001 | 로그인 | UFR-AUTH-010 | M |
|
||||
| SCR-002 | 메인 화면 | UFR-AUTH-020 | M |
|
||||
| SCR-003 | 요금조회 메뉴 | UFR-BILL-010 | M |
|
||||
| SCR-004 | 요금조회 결과 | UFR-BILL-020, UFR-BILL-030, UFR-BILL-040 | M |
|
||||
| SCR-005 | 상품변경 메뉴 | UFR-PROD-010 | M |
|
||||
| SCR-006 | 상품변경 화면 | UFR-PROD-020 | M |
|
||||
| SCR-007 | 상품변경 요청 | UFR-PROD-030 | M |
|
||||
| SCR-008 | 처리결과 화면 | UFR-PROD-040 | M |
|
||||
|
||||
---
|
||||
|
||||
## 사용자 플로우
|
||||
|
||||
### 메인 플로우
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[로그인] --> B[메인 화면]
|
||||
B --> C[요금 조회]
|
||||
B --> D[상품 변경]
|
||||
|
||||
C --> C1[요금조회 메뉴]
|
||||
C1 --> C2[조회월 선택]
|
||||
C2 --> C3[요금조회 결과]
|
||||
C3 --> B
|
||||
|
||||
D --> D1[상품변경 메뉴]
|
||||
D1 --> D2[상품변경 화면]
|
||||
D2 --> D3[상품 선택]
|
||||
D3 --> D4[변경 요청]
|
||||
D4 --> D5[처리결과]
|
||||
D5 --> B
|
||||
```
|
||||
|
||||
### 오류 처리 플로우
|
||||
```mermaid
|
||||
flowchart TD
|
||||
E1[로그인 실패] --> E1_1[오류 메시지 표시] --> E1_2[로그인 재시도]
|
||||
E2[권한 없음] --> E2_1[권한 오류 메시지] --> E2_2[메인 화면]
|
||||
E3[조회 실패] --> E3_1[조회 오류 메시지] --> E3_2[조회 메뉴]
|
||||
E4[변경 실패] --> E4_1[변경 오류 메시지] --> E4_2[변경 화면]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 화면별 상세 설계
|
||||
|
||||
### SCR-001: 로그인
|
||||
**개요**
|
||||
- 목적: 사용자 인증 및 서비스 접근
|
||||
- 관련 유저스토리: UFR-AUTH-010
|
||||
- 비즈니스 중요도: M/5
|
||||
|
||||
**주요 기능**
|
||||
- ID/Password 입력
|
||||
- 자동 로그인 옵션
|
||||
- 로그인 버튼
|
||||
- 오류 메시지 표시
|
||||
|
||||
**UI 구성요소**
|
||||
```
|
||||
Header
|
||||
├── 서비스 로고
|
||||
└── 서비스 제목
|
||||
|
||||
Main Content
|
||||
├── 로그인 폼
|
||||
│ ├── ID 입력 필드 (required)
|
||||
│ ├── Password 입력 필드 (required, type=password)
|
||||
│ ├── 자동 로그인 체크박스
|
||||
│ └── 로그인 버튼 (primary)
|
||||
└── 오류 메시지 영역
|
||||
|
||||
Footer
|
||||
└── 저작권 정보
|
||||
```
|
||||
|
||||
**인터랙션**
|
||||
- ID/Password 유효성 검사 (실시간)
|
||||
- 로그인 버튼 활성화/비활성화
|
||||
- 5회 실패 시 계정 잠금 안내
|
||||
- 성공 시 메인 화면 이동
|
||||
|
||||
---
|
||||
|
||||
### SCR-002: 메인 화면
|
||||
**개요**
|
||||
- 목적: 서비스 메뉴 제공 및 권한별 접근 제어
|
||||
- 관련 유저스토리: UFR-AUTH-020
|
||||
- 비즈니스 중요도: M/3
|
||||
|
||||
**주요 기능**
|
||||
- 사용자 정보 표시 (회선번호)
|
||||
- 서비스 메뉴 제공
|
||||
- 권한별 메뉴 표시/숨김
|
||||
|
||||
**UI 구성요소**
|
||||
```
|
||||
Header
|
||||
├── 서비스 로고
|
||||
├── 사용자 정보 (회선번호)
|
||||
└── 로그아웃 버튼
|
||||
|
||||
Main Content
|
||||
├── 환영 메시지
|
||||
└── 서비스 메뉴 그리드
|
||||
├── 요금 조회 카드 (권한 확인)
|
||||
└── 상품 변경 카드 (권한 확인)
|
||||
|
||||
Footer
|
||||
└── 저작권 정보
|
||||
```
|
||||
|
||||
**인터랙션**
|
||||
- 권한 확인 후 메뉴 표시
|
||||
- 권한 없는 메뉴는 비활성화 또는 숨김
|
||||
- 카드 호버 효과
|
||||
- 카드 클릭 시 해당 서비스로 이동
|
||||
|
||||
---
|
||||
|
||||
### SCR-003: 요금조회 메뉴
|
||||
**개요**
|
||||
- 목적: 요금 조회 옵션 제공
|
||||
- 관련 유저스토리: UFR-BILL-010
|
||||
- 비즈니스 중요도: M/5
|
||||
|
||||
**주요 기능**
|
||||
- 회선번호 표시
|
||||
- 조회월 선택 옵션
|
||||
- 조회 신청 기능
|
||||
|
||||
**UI 구성요소**
|
||||
```
|
||||
Header
|
||||
├── 뒤로가기 버튼
|
||||
└── 페이지 제목 "요금 조회"
|
||||
|
||||
Main Content
|
||||
├── 고객 정보 섹션
|
||||
│ └── 회선번호 표시
|
||||
├── 조회 옵션 섹션
|
||||
│ ├── 조회월 선택 (드롭다운)
|
||||
│ │ ├── 기본값: "현재 월"
|
||||
│ │ └── 이전 6개월 옵션
|
||||
│ └── 안내 텍스트
|
||||
└── 액션 버튼 그룹
|
||||
├── 조회 버튼 (primary)
|
||||
└── 취소 버튼 (secondary)
|
||||
```
|
||||
|
||||
**인터랙션**
|
||||
- 조회월 드롭다운 선택
|
||||
- 조회 버튼 클릭 시 로딩 상태
|
||||
- 오류 시 에러 메시지 표시
|
||||
|
||||
---
|
||||
|
||||
### SCR-004: 요금조회 결과
|
||||
**개요**
|
||||
- 목적: 조회된 요금 정보 표시
|
||||
- 관련 유저스토리: UFR-BILL-020, UFR-BILL-030, UFR-BILL-040
|
||||
- 비즈니스 중요도: M/8, M/13, M/8
|
||||
|
||||
**주요 기능**
|
||||
- 요금 정보 상세 표시
|
||||
- 사용량 정보 제공
|
||||
- 새로운 조회 기능
|
||||
|
||||
**UI 구성요소**
|
||||
```
|
||||
Header
|
||||
├── 뒤로가기 버튼
|
||||
└── 페이지 제목 "요금 조회 결과"
|
||||
|
||||
Main Content
|
||||
├── 요금 정보 카드
|
||||
│ ├── 청구월
|
||||
│ ├── 상품명 (요금제)
|
||||
│ ├── 총 요금 (강조 표시)
|
||||
│ ├── 할인 정보
|
||||
│ └── 약정 정보
|
||||
├── 사용량 정보 카드
|
||||
│ ├── 통화 사용량
|
||||
│ ├── 데이터 사용량
|
||||
│ └── SMS 사용량
|
||||
├── 부가 정보 카드
|
||||
│ ├── 단말기 할부금
|
||||
│ ├── 예상 해지비용
|
||||
│ └── 청구/납부 정보
|
||||
└── 액션 버튼 그룹
|
||||
├── 다른 월 조회 버튼
|
||||
└── 메인으로 버튼
|
||||
```
|
||||
|
||||
**인터랙션**
|
||||
- 정보 카드 접기/펼치기
|
||||
- 다른 월 조회 클릭 시 조회 메뉴로 이동
|
||||
- 로딩 중 스켈레톤 UI 표시
|
||||
|
||||
---
|
||||
|
||||
### SCR-005: 상품변경 메뉴
|
||||
**개요**
|
||||
- 목적: 상품 변경 진입점 제공
|
||||
- 관련 유저스토리: UFR-PROD-010
|
||||
- 비즈니스 중요도: M/5
|
||||
|
||||
**주요 기능**
|
||||
- 고객 정보 표시
|
||||
- 현재 상품 정보 표시
|
||||
- 상품 변경 화면으로 이동
|
||||
|
||||
**UI 구성요소**
|
||||
```
|
||||
Header
|
||||
├── 뒤로가기 버튼
|
||||
└── 페이지 제목 "상품 변경"
|
||||
|
||||
Main Content
|
||||
├── 고객 정보 카드
|
||||
│ ├── 회선번호
|
||||
│ └── 고객ID
|
||||
├── 현재 상품 정보 카드
|
||||
│ ├── 상품명
|
||||
│ ├── 월 기본료
|
||||
│ └── 주요 혜택
|
||||
├── 안내 메시지
|
||||
│ └── 상품 변경 시 주의사항
|
||||
└── 액션 버튼 그룹
|
||||
├── 상품 변경하기 버튼 (primary)
|
||||
└── 취소 버튼 (secondary)
|
||||
```
|
||||
|
||||
**인터랙션**
|
||||
- 현재 상품 정보 로딩
|
||||
- 상품 변경하기 클릭 시 변경 화면으로 이동
|
||||
- 로딩 실패 시 에러 메시지
|
||||
|
||||
---
|
||||
|
||||
### SCR-006: 상품변경 화면
|
||||
**개요**
|
||||
- 목적: 변경 가능한 상품 목록 제공
|
||||
- 관련 유저스토리: UFR-PROD-020
|
||||
- 비즈니스 중요도: M/8
|
||||
|
||||
**주요 기능**
|
||||
- 변경 가능한 상품 목록 표시
|
||||
- 상품 비교 기능
|
||||
- 상품 선택 기능
|
||||
|
||||
**UI 구성요소**
|
||||
```
|
||||
Header
|
||||
├── 뒤로가기 버튼
|
||||
└── 페이지 제목 "상품 선택"
|
||||
|
||||
Main Content
|
||||
├── 현재 상품 요약 (고정)
|
||||
├── 상품 목록 섹션
|
||||
│ └── 상품 카드들
|
||||
│ ├── 상품명
|
||||
│ ├── 월 기본료
|
||||
│ ├── 주요 혜택 리스트
|
||||
│ ├── 현재 상품과 비교
|
||||
│ └── 선택 라디오 버튼
|
||||
└── 액션 버튼 그룹 (고정)
|
||||
├── 선택한 상품으로 변경 (primary, disabled)
|
||||
└── 취소 버튼 (secondary)
|
||||
```
|
||||
|
||||
**인터랙션**
|
||||
- 상품 선택 시 버튼 활성화
|
||||
- 상품 카드 선택 상태 시각화
|
||||
- 스크롤 시 헤더와 버튼 고정
|
||||
- 상품 로딩 중 스켈레톤 표시
|
||||
|
||||
---
|
||||
|
||||
### SCR-007: 상품변경 요청
|
||||
**개요**
|
||||
- 목적: 선택한 상품으로 변경 요청 확인
|
||||
- 관련 유저스토리: UFR-PROD-030
|
||||
- 비즈니스 중요도: M/13
|
||||
|
||||
**주요 기능**
|
||||
- 변경 내용 확인
|
||||
- 사전 체크 진행 상황
|
||||
- 변경 요청 최종 실행
|
||||
|
||||
**UI 구성요소**
|
||||
```
|
||||
Header
|
||||
├── 뒤로가기 버튼
|
||||
└── 페이지 제목 "상품 변경 요청"
|
||||
|
||||
Main Content
|
||||
├── 변경 내용 확인 카드
|
||||
│ ├── 현재 상품
|
||||
│ ├── 변경 화살표 아이콘
|
||||
│ └── 변경할 상품
|
||||
├── 주의사항 섹션
|
||||
│ ├── 변경 시 주의사항
|
||||
│ ├── 약정/할부 안내
|
||||
│ └── 요금 변경 안내
|
||||
├── 진행 상황 표시
|
||||
│ ├── 사전 체크 진행 바
|
||||
│ └── 상태 메시지
|
||||
└── 액션 버튼 그룹
|
||||
├── 변경 신청 버튼 (primary)
|
||||
├── 취소 버튼 (secondary)
|
||||
└── 이전 단계 버튼
|
||||
```
|
||||
|
||||
**인터랙션**
|
||||
- 사전 체크 진행 상태 실시간 업데이트
|
||||
- 체크 완료 후 변경 신청 버튼 활성화
|
||||
- 체크 실패 시 오류 메시지 및 재시도 옵션
|
||||
|
||||
---
|
||||
|
||||
### SCR-008: 처리결과 화면
|
||||
**개요**
|
||||
- 목적: 상품 변경 처리 결과 표시
|
||||
- 관련 유저스토리: UFR-PROD-040
|
||||
- 비즈니스 중요도: M/21
|
||||
|
||||
**주요 기능**
|
||||
- 처리 결과 상태 표시
|
||||
- 상세 처리 내용 제공
|
||||
- 후속 액션 안내
|
||||
|
||||
**UI 구성요소**
|
||||
```
|
||||
Header
|
||||
└── 페이지 제목 "처리 결과"
|
||||
|
||||
Main Content
|
||||
├── 결과 상태 카드
|
||||
│ ├── 성공/실패 아이콘 (대형)
|
||||
│ ├── 결과 메시지 (제목)
|
||||
│ └── 상태 설명
|
||||
├── 처리 내용 카드 (성공 시)
|
||||
│ ├── 변경된 상품 정보
|
||||
│ ├── 적용일
|
||||
│ └── 처리 번호
|
||||
├── 실패 사유 카드 (실패 시)
|
||||
│ ├── 실패 원인
|
||||
│ ├── 해결 방법
|
||||
│ └── 고객센터 안내
|
||||
└── 액션 버튼 그룹
|
||||
├── 메인으로 버튼 (primary)
|
||||
├── 다시 시도 버튼 (실패 시)
|
||||
└── 고객센터 연결 (실패 시)
|
||||
```
|
||||
|
||||
**인터랙션**
|
||||
- 결과에 따른 적절한 UI 표시
|
||||
- 성공/실패 상태별 차별화된 컬러 스킴
|
||||
- 추가 액션 버튼 제공
|
||||
|
||||
---
|
||||
|
||||
## 화면간 전환 및 네비게이션
|
||||
|
||||
### 네비게이션 패턴
|
||||
- **계층적 네비게이션**: 뒤로가기 버튼 제공
|
||||
- **브레드크럼**: 깊이 2단계 이상 시 경로 표시
|
||||
- **메인 복귀**: 모든 화면에서 홈 버튼 제공
|
||||
|
||||
### 전환 효과
|
||||
- **페이지 전환**: 부드러운 슬라이드 애니메이션 (300ms)
|
||||
- **모달/팝업**: 페이드 인/아웃 효과 (200ms)
|
||||
- **로딩 상태**: 스켈레톤 UI 또는 스피너
|
||||
|
||||
### URL 구조
|
||||
```
|
||||
/ → 로그인 페이지
|
||||
/main → 메인 화면
|
||||
/bill/menu → 요금조회 메뉴
|
||||
/bill/result → 요금조회 결과
|
||||
/product/menu → 상품변경 메뉴
|
||||
/product/change → 상품변경 화면
|
||||
/product/request → 상품변경 요청
|
||||
/product/result → 처리결과
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 반응형 설계 전략
|
||||
|
||||
### 브레이크포인트
|
||||
- **Mobile**: ~ 767px
|
||||
- **Tablet**: 768px ~ 1023px
|
||||
- **Desktop**: 1024px ~
|
||||
|
||||
### 레이아웃 전략
|
||||
**Mobile First 설계**
|
||||
- 기본: 단일 컬럼 레이아웃
|
||||
- 카드 형태의 콘텐츠 구성
|
||||
- 터치 친화적 버튼 크기 (44px 이상)
|
||||
|
||||
**Tablet**
|
||||
- 2컬럼 레이아웃 (카드 그리드)
|
||||
- 사이드바 네비게이션 고려
|
||||
- 확장된 터치 영역
|
||||
|
||||
**Desktop**
|
||||
- 3컬럼 레이아웃 가능
|
||||
- 고정 폭 컨테이너 (최대 1200px)
|
||||
- 호버 상태 적극 활용
|
||||
|
||||
### 콘텐츠 우선순위
|
||||
1. **핵심 정보**: 항상 우선 표시
|
||||
2. **액션 버튼**: 고정 위치 (하단)
|
||||
3. **부가 정보**: 접기/펼치기로 제어
|
||||
|
||||
---
|
||||
|
||||
## 접근성 보장 방안
|
||||
|
||||
### WCAG 2.1 AA 수준 준수
|
||||
**인식 가능성 (Perceivable)**
|
||||
- 명도 대비 4.5:1 이상 유지
|
||||
- 대체 텍스트 제공 (모든 이미지)
|
||||
- 텍스트 크기 조절 가능 (최대 200%)
|
||||
|
||||
**운용 가능성 (Operable)**
|
||||
- 키보드 접근성 완전 지원
|
||||
- 포커스 순서 논리적 구성
|
||||
- 자동 재생 콘텐츠 없음
|
||||
|
||||
**이해 가능성 (Understandable)**
|
||||
- 명확한 언어 사용
|
||||
- 입력 오류 방지 및 수정 지원
|
||||
- 일관된 네비게이션
|
||||
|
||||
**견고성 (Robust)**
|
||||
- 시맨틱 HTML 사용
|
||||
- ARIA 라벨 적절히 활용
|
||||
- 스크린 리더 호환성
|
||||
|
||||
### 구체적 구현 사항
|
||||
- **폼 요소**: 라벨과 입력 필드 명확한 연결
|
||||
- **버튼**: 명확한 텍스트 또는 aria-label
|
||||
- **오류 메시지**: 명확한 위치와 해결 방법 안내
|
||||
- **로딩 상태**: aria-live를 통한 상태 알림
|
||||
|
||||
---
|
||||
|
||||
## 성능 최적화 방안
|
||||
|
||||
### 로딩 성능
|
||||
**초기 로딩**
|
||||
- Critical CSS 인라인 처리
|
||||
- 이미지 지연 로딩 (Lazy Loading)
|
||||
- 폰트 최적화 (font-display: swap)
|
||||
|
||||
**코드 분할**
|
||||
- 페이지별 번들 분리
|
||||
- 동적 import 활용
|
||||
- 트리 쉐이킹 적용
|
||||
|
||||
### 런타임 성능
|
||||
**상태 관리**
|
||||
- 불필요한 리렌더링 방지
|
||||
- 메모이제이션 활용
|
||||
- 가상화 (긴 목록)
|
||||
|
||||
**네트워크 최적화**
|
||||
- API 응답 캐싱
|
||||
- 요청 중복 제거
|
||||
- 압축 및 minify
|
||||
|
||||
### 사용자 경험
|
||||
**로딩 상태**
|
||||
- 스켈레톤 UI 제공
|
||||
- 프로그레스바 표시
|
||||
- 오프라인 상태 대응
|
||||
|
||||
**오류 처리**
|
||||
- 명확한 오류 메시지
|
||||
- 재시도 메커니즘
|
||||
- 폴백 UI 제공
|
||||
|
||||
### 성능 지표 목표
|
||||
- **First Contentful Paint**: < 1.5초
|
||||
- **Largest Contentful Paint**: < 2.5초
|
||||
- **First Input Delay**: < 100ms
|
||||
- **Cumulative Layout Shift**: < 0.1
|
||||
|
||||
---
|
||||
|
||||
## 변경 이력
|
||||
|
||||
| 버전 | 날짜 | 변경사항 | 작성자 |
|
||||
|------|------|----------|--------|
|
||||
| 1.0 | 2025-01-05 | 초기 UI/UX 설계서 작성 | 박화면 |
|
||||
|
||||
---
|
||||
|
||||
## 검토 사항
|
||||
|
||||
### 유저스토리 매칭 검토 ✅
|
||||
- 총 10개 유저스토리 100% 반영
|
||||
- 화면별 관련 유저스토리 명시
|
||||
- 불필요한 추가 설계 없음
|
||||
|
||||
### 설계 원칙 준수 ✅
|
||||
- 통신요금 관리 서비스 특화 설계
|
||||
- 보안성과 사용성 균형
|
||||
- 접근성 및 성능 고려
|
||||
Reference in New Issue
Block a user