pharmacy-pos-qr-system/backend/templates/index.html
thug0bin 2b3d8649ba feat: 회원가입 페이지 추가 + 카카오 전화번호 신청 문서
- /signup 회원가입 페이지 (이름 + 전화번호 + 개인정보 동의)
- /api/signup API (get_or_create_user + 세션 저장)
- 카카오 간편 가입 버튼 (카카오 로그인으로 가입)
- 홈 화면에 회원가입 메뉴 추가
- 이미 로그인 시 /signup 접근하면 마이페이지로 리다이렉트
- 카카오 전화번호 수집 신청용 수집 사유 + 시나리오 문서

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 09:34:41 +09:00

452 lines
15 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="theme-color" content="#6366f1">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="청춘약국">
<link rel="manifest" href="/static/manifest.json">
<link rel="apple-touch-icon" href="/static/icons/icon-192.png">
<link rel="icon" type="image/png" sizes="192x192" href="/static/icons/icon-192.png">
<title>청춘약국 마일리지</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&display=swap" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: #f5f7fa;
min-height: 100vh;
-webkit-font-smoothing: antialiased;
}
.app-container {
background: #ffffff;
min-height: 100vh;
max-width: 420px;
margin: 0 auto;
}
.hero {
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
padding: 48px 24px 56px;
text-align: center;
color: #ffffff;
position: relative;
}
.hero-logo {
width: 72px;
height: 72px;
background: rgba(255,255,255,0.15);
border-radius: 20px;
margin: 0 auto 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 36px;
}
.hero-title {
font-size: 26px;
font-weight: 700;
letter-spacing: -0.5px;
margin-bottom: 8px;
}
.hero-desc {
font-size: 15px;
font-weight: 400;
opacity: 0.85;
letter-spacing: -0.2px;
}
/* 로그인 상태 배지 */
.user-badge {
display: inline-flex;
align-items: center;
gap: 8px;
background: rgba(255,255,255,0.2);
padding: 8px 16px;
border-radius: 20px;
margin-top: 16px;
font-size: 14px;
font-weight: 500;
}
.main-content {
padding: 24px;
margin-top: -20px;
position: relative;
}
/* QR 스캔 버튼 */
.scan-card {
background: #ffffff;
border-radius: 20px;
padding: 28px 24px;
box-shadow: 0 8px 32px rgba(0,0,0,0.08);
text-align: center;
margin-bottom: 20px;
}
.scan-title {
font-size: 16px;
font-weight: 700;
color: #212529;
margin-bottom: 6px;
letter-spacing: -0.3px;
}
.scan-desc {
font-size: 13px;
color: #868e96;
margin-bottom: 20px;
letter-spacing: -0.2px;
}
.btn-scan {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
width: 100%;
padding: 18px;
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
color: #ffffff;
border: none;
border-radius: 16px;
font-size: 17px;
font-weight: 700;
cursor: pointer;
letter-spacing: -0.3px;
transition: all 0.2s ease;
box-shadow: 0 4px 16px rgba(99, 102, 241, 0.3);
}
.btn-scan:active { transform: scale(0.98); }
.btn-scan svg {
width: 24px;
height: 24px;
fill: #ffffff;
}
/* 메뉴 카드들 */
.menu-grid {
display: flex;
flex-direction: column;
gap: 12px;
}
.menu-card {
display: flex;
align-items: center;
gap: 16px;
background: #ffffff;
border: 1px solid #e9ecef;
border-radius: 16px;
padding: 20px;
text-decoration: none;
transition: all 0.2s ease;
}
.menu-card:active {
transform: scale(0.98);
background: #f8f9fa;
}
.menu-icon {
width: 48px;
height: 48px;
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
flex-shrink: 0;
}
.menu-icon.purple { background: rgba(99, 102, 241, 0.1); }
.menu-icon.yellow { background: rgba(254, 229, 0, 0.3); }
.menu-icon.green { background: rgba(16, 185, 129, 0.1); }
.menu-text {
flex: 1;
}
.menu-title {
font-size: 15px;
font-weight: 700;
color: #212529;
letter-spacing: -0.3px;
margin-bottom: 2px;
}
.menu-desc {
font-size: 13px;
color: #868e96;
letter-spacing: -0.2px;
}
.menu-arrow {
color: #ced4da;
font-size: 18px;
}
/* QR 스캐너 모달 */
.scanner-overlay {
display: none;
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.95);
z-index: 1000;
flex-direction: column;
align-items: center;
justify-content: center;
}
.scanner-overlay.open { display: flex; }
.scanner-header {
position: absolute;
top: 0; left: 0; right: 0;
padding: 16px 24px;
padding-top: calc(16px + env(safe-area-inset-top, 0px));
display: flex;
justify-content: space-between;
align-items: center;
z-index: 10;
}
.scanner-title {
color: #ffffff;
font-size: 18px;
font-weight: 700;
}
.btn-close-scanner {
color: #ffffff;
background: rgba(255,255,255,0.15);
border: none;
border-radius: 50%;
width: 40px;
height: 40px;
font-size: 20px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
#qr-reader {
width: 300px;
height: 300px;
border-radius: 20px;
overflow: hidden;
}
.scanner-hint {
color: rgba(255,255,255,0.7);
font-size: 14px;
margin-top: 24px;
text-align: center;
letter-spacing: -0.2px;
}
.footer {
text-align: center;
padding: 24px;
padding-bottom: calc(24px + env(safe-area-inset-bottom, 0px));
}
.footer-link {
color: #adb5bd;
font-size: 12px;
text-decoration: none;
letter-spacing: -0.2px;
}
.footer-link + .footer-link { margin-left: 16px; }
@media (max-width: 480px) {
.hero {
padding-top: calc(48px + env(safe-area-inset-top, 0px));
}
}
</style>
</head>
<body>
<div class="app-container">
<div class="hero">
<div class="hero-logo">💊</div>
<div class="hero-title">청춘약국 마일리지</div>
<div class="hero-desc">구매금액의 3%를 포인트로 돌려드려요</div>
{% if logged_in %}
<div class="user-badge">
<span>{{ logged_in_name }}님 로그인 중</span>
</div>
{% endif %}
</div>
<div class="main-content">
<!-- QR 스캔 카드 -->
<div class="scan-card">
<div class="scan-title">영수증 QR 스캔</div>
<div class="scan-desc">
{% if logged_in %}
카메라로 QR을 찍으면 바로 적립됩니다
{% else %}
영수증의 QR 코드를 스캔하여 적립하세요
{% endif %}
</div>
<button class="btn-scan" id="btnOpenScanner">
<svg viewBox="0 0 24 24"><path d="M3 11h2V9H3v2zm0 2h2v-2H3v2zm4 8h2v-2H7v2zM3 15h2v-2H3v2zm8-12h-2v2h2V3zm4 0v2h2V3h-2zm-4 18h2v-2h-2v2zM3 3v2h2V3H3zm0 16h2v-2H3v2zm8-16h-2v2h2V3zm8 4h2V5h-2v2zm0 4h2V9h-2v2zm0-8v2h2V3h-2zm0 12h2v-2h-2v2zm0 4v-2h-2v2h2zm-4 0h2v-2h-2v2zm-8-8h10v-2H7v2zm0 4h6v-2H7v2zm4-8h2V7h-2v2zM3 7h2V5H3v2z"/></svg>
QR 코드 스캔하기
</button>
</div>
<!-- 메뉴 카드들 -->
<div class="menu-grid">
{% if logged_in %}
<a href="/my-page?phone={{ logged_in_phone }}" class="menu-card">
<div class="menu-icon purple">📊</div>
<div class="menu-text">
<div class="menu-title">내 마일리지</div>
<div class="menu-desc">적립 내역 및 포인트 확인</div>
</div>
<div class="menu-arrow"></div>
</a>
<a href="/logout" class="menu-card">
<div class="menu-icon green">🔓</div>
<div class="menu-text">
<div class="menu-title">로그아웃</div>
<div class="menu-desc">다른 계정으로 전환</div>
</div>
<div class="menu-arrow"></div>
</a>
{% else %}
<a href="/my-page/kakao/start" class="menu-card">
<div class="menu-icon yellow">
<svg width="22" height="22" viewBox="0 0 20 20" fill="none"><path d="M10 1C4.477 1 0 4.477 0 8.5c0 2.58 1.693 4.847 4.243 6.134l-1.084 3.97a.3.3 0 00.457.338L7.7 16.392c.75.112 1.52.17 2.3.17 5.523 0 10-3.477 10-7.562C20 4.477 15.523 1 10 1z" fill="#191919"/></svg>
</div>
<div class="menu-text">
<div class="menu-title">카카오로 시작하기</div>
<div class="menu-desc">로그인하면 QR만 찍으면 바로 적립</div>
</div>
<div class="menu-arrow"></div>
</a>
<a href="/signup" class="menu-card">
<div class="menu-icon green">📝</div>
<div class="menu-text">
<div class="menu-title">회원가입</div>
<div class="menu-desc">전화번호 + 이름으로 간편 가입</div>
</div>
<div class="menu-arrow"></div>
</a>
<a href="/my-page" class="menu-card">
<div class="menu-icon purple">📊</div>
<div class="menu-text">
<div class="menu-title">마일리지 조회</div>
<div class="menu-desc">전화번호로 적립 내역 확인</div>
</div>
<div class="menu-arrow"></div>
</a>
{% endif %}
</div>
</div>
<div class="footer">
<a href="/privacy" class="footer-link">개인정보 처리방침</a>
</div>
</div>
<!-- QR 스캐너 모달 -->
<div class="scanner-overlay" id="scannerOverlay">
<div class="scanner-header">
<div class="scanner-title">QR 코드 스캔</div>
<button class="btn-close-scanner" id="btnCloseScanner"></button>
</div>
<div id="qr-reader"></div>
<div class="scanner-hint">영수증의 QR 코드를 카메라에 비춰주세요</div>
</div>
<script src="https://unpkg.com/html5-qrcode@2.3.8/html5-qrcode.min.js"></script>
<script>
if('serviceWorker' in navigator){navigator.serviceWorker.register('/sw.js').catch(()=>{});}
(function() {
const overlay = document.getElementById('scannerOverlay');
const btnOpen = document.getElementById('btnOpenScanner');
const btnClose = document.getElementById('btnCloseScanner');
let scanner = null;
let scanning = false;
btnOpen.addEventListener('click', startScanner);
btnClose.addEventListener('click', stopScanner);
async function startScanner() {
overlay.classList.add('open');
if (scanner) {
scanner.clear();
}
scanner = new Html5Qrcode('qr-reader');
scanning = true;
try {
await scanner.start(
{ facingMode: 'environment' },
{ fps: 10, qrbox: { width: 250, height: 250 } },
onScanSuccess,
function() {} // ignore scan errors
);
} catch (err) {
document.querySelector('.scanner-hint').textContent =
'카메라 접근이 거부되었습니다. 설정에서 카메라 권한을 허용해주세요.';
}
}
async function stopScanner() {
if (scanner && scanning) {
try { await scanner.stop(); } catch(e) {}
scanning = false;
}
overlay.classList.remove('open');
}
function onScanSuccess(decodedText) {
// QR 코드 URL에서 /claim?t= 파라미터 추출
try {
let url;
if (decodedText.startsWith('http')) {
url = new URL(decodedText);
// 같은 도메인이거나 claim 경로면 이동
if (url.pathname.startsWith('/claim')) {
stopScanner();
window.location.href = url.pathname + url.search;
return;
}
}
// 직접 t= 값인 경우 (예: "12345:abc123")
if (decodedText.includes(':') && !decodedText.startsWith('http')) {
stopScanner();
window.location.href = '/claim?t=' + encodeURIComponent(decodedText);
return;
}
} catch(e) {}
// 인식은 됐지만 유효하지 않은 QR
document.querySelector('.scanner-hint').textContent =
'유효한 영수증 QR 코드가 아닙니다. 다시 시도해주세요.';
}
})();
</script>
</body>
</html>