/* * SCR-M14 연계 [모바일] 티켓 권종 선택 — SCR-M15 예매 진입(보조 화면). * 4단계 스텝(권종·예매자·결제·완료) 중 1단계. 권종 카드 + 수량 스테퍼 + 합계 스티키 바. * ※ 결제는 PG 위임 — 카드번호 입력 UI 없음(design.md 보안 불변). M10 미구현 → 샘플 권종. * "다음"은 후속 단계(예매자/결제) 미구현 안내(존재하지 않는 API 호출 금지). */ import { Ionicons } from '@expo/vector-icons'; import { router, Stack } from 'expo-router'; import React, { useMemo, useState } from 'react'; import { Alert, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'; import { colors, radius, spacing, touch, type } from '../../theme'; interface TicketType { id: string; name: string; price: number; // 원(0=무료) priceNote?: string; desc?: string; badge?: { text: string; tone: 'discount' | 'ai' }; soldOut?: boolean; accent: string; } const TICKET_TYPES: TicketType[] = [ { id: 'std', name: '일반권', price: 0, priceNote: '사전등록 할인가', accent: colors.primary600 }, { id: 'buyer', name: '바이어권', price: 0, desc: '무료 (자격심사 필요)', badge: { text: 'AI 추천', tone: 'ai' }, accent: colors.aiAccent, }, { id: 'group', name: '단체권', price: 9000, priceNote: '10인 이상 단체 구매시 적용', badge: { text: '10% OFF', tone: 'discount' }, accent: colors.success, }, { id: 'vip', name: 'VIP권', price: 50000, soldOut: true, accent: colors.neutral500 }, ]; const STEPS = ['권종', '예매자', '결제', '완료']; function won(n: number): string { return n === 0 ? '₩0' : `₩${n.toLocaleString('ko-KR')}`; } export default function TicketSelectScreen() { const [qty, setQty] = useState>({ group: 1 }); const totalCount = useMemo( () => Object.values(qty).reduce((a, b) => a + b, 0), [qty], ); const totalPrice = useMemo( () => TICKET_TYPES.reduce((sum, t) => sum + (qty[t.id] ?? 0) * t.price, 0), [qty], ); function change(id: string, delta: number) { setQty((q) => ({ ...q, [id]: Math.max(0, (q[id] ?? 0) + delta) })); } function next() { if (totalCount === 0) return; Alert.alert( '다음 단계', '예매자 정보·결제 단계는 티켓 백엔드(M10) 및 PG 어댑터 연동 후 제공됩니다.\n결제는 PG사 보안 페이지에서 진행되며 카드정보는 저장되지 않습니다.', ); } return ( {/* 행사 요약 칩 */} 스마트팩토리 코리아 2026 · 제2전시장 홀7 {/* 스텝 인디케이터 */} {STEPS.map((s, i) => ( {i + 1} {s} {i < STEPS.length - 1 ? : null} ))} {/* 권종 카드 */} {TICKET_TYPES.map((t) => { const count = qty[t.id] ?? 0; return ( {t.name} {t.badge ? ( {t.badge.tone === 'ai' ? ( ) : null} {t.badge.text} ) : null} {t.soldOut ? ( 매진 ) : null} {t.desc ? ( {t.desc} ) : ( {won(t.price)} )} {t.priceNote ? {t.priceNote} : null} {/* 수량 스테퍼 */} change(t.id, -1)} style={styles.stepperBtn} > {count} change(t.id, 1)} style={styles.stepperBtn} > {t.badge?.tone === 'ai' ? ( ※ 업종 및 직무 AI 매칭을 통해 추천된 권종입니다. ) : null} ); })} {/* 결제 수단 미리보기 */} 결제 수단 미리보기 {['신용카드', '간편결제', '계좌이체'].map((p) => ( {p} ))} 결제는 PG사 보안 페이지에서 진행되며 카드정보는 저장되지 않습니다. {/* 하단 스티키 합계 바 */} 선택한 티켓 {totalCount}매 합계 {won(totalPrice)} 다음 router.back()}> 내 티켓 지갑으로 돌아가기 ); } const styles = StyleSheet.create({ flex: { flex: 1, backgroundColor: colors.neutral050 }, scroll: { padding: spacing.md, gap: spacing.md, paddingBottom: 200 }, eventChip: { flexDirection: 'row', alignItems: 'center', gap: 8, alignSelf: 'flex-start', backgroundColor: colors.primary050, borderRadius: radius.pill, paddingHorizontal: 14, paddingVertical: 8, maxWidth: '100%', }, eventChipText: { flex: 1, fontSize: type.caption.fontSize, color: colors.neutral900, fontWeight: '600' }, steps: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 4 }, step: { alignItems: 'center', gap: 4 }, stepDot: { width: 32, height: 32, borderRadius: 16, backgroundColor: colors.neutral200, alignItems: 'center', justifyContent: 'center', }, stepDotOn: { backgroundColor: colors.primary600 }, stepNum: { fontSize: type.body.fontSize, fontWeight: '700', color: colors.neutral500 }, stepNumOn: { color: colors.white }, stepLabel: { fontSize: 11, color: colors.neutral500 }, stepLabelOn: { color: colors.primary700, fontWeight: '700' }, stepLine: { flex: 1, height: 1, backgroundColor: colors.neutral200, marginHorizontal: 6, marginBottom: 18 }, cardList: { gap: spacing.md }, card: { flexDirection: 'row', backgroundColor: colors.white, borderWidth: 1, borderColor: colors.neutral200, borderRadius: radius.md, overflow: 'hidden', }, cardSoldOut: { opacity: 0.6 }, cardAccent: { width: 4 }, cardBody: { flex: 1, padding: spacing.md, gap: 8 }, cardTop: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 }, cardInfo: { flex: 1, gap: 4 }, nameRow: { flexDirection: 'row', alignItems: 'center', gap: 8, flexWrap: 'wrap' }, typeName: { fontSize: type.h3.fontSize, fontWeight: '700', color: colors.neutral900 }, typeBadge: { flexDirection: 'row', alignItems: 'center', gap: 3, borderRadius: radius.sm, paddingHorizontal: 6, paddingVertical: 2, borderWidth: 1, }, badgeAi: { borderColor: colors.aiAccent, backgroundColor: colors.aiSurface }, badgeDiscount: { borderColor: colors.error, backgroundColor: '#FEF3F2' }, typeBadgeText: { fontSize: 10, fontWeight: '700' }, soldOutBadge: { backgroundColor: colors.neutral500, borderRadius: radius.sm, paddingHorizontal: 6, paddingVertical: 2 }, soldOutText: { color: colors.white, fontSize: 10, fontWeight: '700' }, typePrice: { fontSize: type.h2.fontSize, fontWeight: '700', color: colors.neutral900 }, typeDesc: { fontSize: type.body.fontSize, color: colors.neutral700 }, priceNote: { fontSize: type.caption.fontSize, color: colors.neutral500 }, aiNote: { fontSize: 11, color: colors.aiAccent, fontWeight: '500' }, stepper: { flexDirection: 'row', alignItems: 'center', backgroundColor: colors.neutral050, borderRadius: radius.sm, borderWidth: 1, borderColor: colors.neutral200, }, stepperBtn: { width: touch.min, height: touch.min, alignItems: 'center', justifyContent: 'center' }, stepperVal: { width: 28, textAlign: 'center', fontSize: type.h3.fontSize, fontWeight: '700', color: colors.neutral900 }, paySection: { gap: 8, marginTop: 4 }, payTitle: { fontSize: type.caption.fontSize, color: colors.neutral500, fontWeight: '600' }, payChips: { flexDirection: 'row', gap: 8 }, payChip: { backgroundColor: colors.white, borderWidth: 1, borderColor: colors.neutral200, borderRadius: radius.pill, paddingHorizontal: 14, paddingVertical: 8, }, payChipText: { fontSize: type.caption.fontSize, color: colors.neutral700 }, secureNote: { flexDirection: 'row', gap: 10, alignItems: 'flex-start', backgroundColor: colors.white, borderWidth: 1, borderColor: colors.neutral200, borderStyle: 'dashed', borderRadius: radius.md, padding: spacing.md, marginTop: 8, }, secureText: { flex: 1, fontSize: type.caption.fontSize, color: colors.neutral700, lineHeight: 18 }, footer: { position: 'absolute', left: 0, right: 0, bottom: 0, backgroundColor: colors.white, borderTopWidth: 1, borderTopColor: colors.neutral200, padding: spacing.md, paddingBottom: spacing.lg, gap: 10, }, totalRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, totalLabel: { fontSize: type.body.fontSize, color: colors.neutral500 }, totalPrice: { fontSize: type.h2.fontSize, fontWeight: '700', color: colors.primary700 }, nextBtn: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 6, minHeight: 56, backgroundColor: colors.primary600, borderRadius: radius.md, }, nextBtnOff: { opacity: 0.5 }, nextText: { color: colors.white, fontSize: type.h2.fontSize, fontWeight: '700' }, backLink: { alignItems: 'center', minHeight: 32, justifyContent: 'center' }, backLinkText: { fontSize: type.caption.fontSize, color: colors.neutral500, textDecorationLine: 'underline' }, });