kintex/mobile/app/(visitor)/events.tsx
zio 0933dbea41 feat(mobile): B2C visitor tab, congestion pill, live notice feed, ticket wallet, brand logo (v0.2.2 prep)
- (visitor) route group, visitor lib, tickets wallet
- CongestionPill + LiveNoticeFeed components
- login/tickets screens, i18n 4 locales, wordmark assets, splash

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 00:41:57 +09:00

95 lines
3.7 KiB
TypeScript

/*
* [모바일] 관람객(B2C) 행사 — 참여 행사 목록 + 선택 행사 라이브 공지 피드(전체).
* 참여 행사는 로그인 워크스페이스에서 수신(계약 발명 없음). 없으면 데모 라이브 행사로 공개 공지 노출.
* 라이브 공지 = 공개 API(cms_backlog_contract.md).
*/
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
import { Banner } from '../../components/Banner';
import { Card } from '../../components/Card';
import { DdayChip } from '../../components/DdayChip';
import { LiveNoticeFeed } from '../../components/LiveNoticeFeed';
import { useAuth } from '../../context/AuthContext';
import { DEFAULT_PUBLIC_EVENT_ID } from '../../lib/config';
import { colors, radius, spacing, type } from '../../theme';
export default function VisitorEventsScreen() {
const { t } = useTranslation();
const { workspaces, activeWorkspace } = useAuth();
const [selected, setSelected] = useState<string | null>(
activeWorkspace?.eventId ?? workspaces[0]?.eventId ?? null,
);
const selectedWs = workspaces.find((w) => w.eventId === selected) ?? null;
const feedEventId = selected ?? DEFAULT_PUBLIC_EVENT_ID;
return (
<ScrollView contentContainerStyle={styles.scroll}>
<Text style={styles.title}>{t('vevents.title')}</Text>
{workspaces.length === 0 ? (
<Banner tone="info">{t('vevents.empty')}</Banner>
) : (
<>
{/* 참여 행사 선택 칩 */}
<View style={styles.chips}>
{workspaces.map((w) => {
const on = w.eventId === selected;
return (
<Pressable
key={w.eventId}
onPress={() => setSelected(w.eventId)}
style={[styles.chip, on && styles.chipOn]}
>
<Text style={[styles.chipText, on && styles.chipTextOn]} numberOfLines={1}>
{w.eventName}
</Text>
</Pressable>
);
})}
</View>
{/* 선택 행사 요약 카드 */}
{selectedWs ? (
<Card accent="none">
<View style={styles.eventTop}>
<Text style={styles.eventName} numberOfLines={2}>
{selectedWs.eventName}
</Text>
<DdayChip dday={selectedWs.dday} />
</View>
<Text style={styles.eventMeta}>
{selectedWs.hallLabel} · {selectedWs.startDate} ~ {selectedWs.endDate}
</Text>
</Card>
) : null}
</>
)}
{/* 라이브 공지(전체) */}
<LiveNoticeFeed eventId={feedEventId} />
</ScrollView>
);
}
const styles = StyleSheet.create({
scroll: { padding: spacing.md, gap: spacing.md, paddingBottom: 40 },
title: { fontSize: type.h2.fontSize, fontWeight: '700', color: colors.neutral900 },
chips: { flexDirection: 'row', flexWrap: 'wrap', gap: 8 },
chip: {
paddingHorizontal: 12,
paddingVertical: 8,
borderRadius: radius.pill,
borderWidth: 1,
borderColor: colors.neutral200,
maxWidth: 240,
},
chipOn: { backgroundColor: colors.primary100, borderColor: colors.primary600 },
chipText: { color: colors.neutral700, fontSize: type.caption.fontSize },
chipTextOn: { color: colors.primary700, fontWeight: '700' },
eventTop: { flexDirection: 'row', alignItems: 'flex-start', justifyContent: 'space-between', gap: 8 },
eventName: { flex: 1, fontSize: type.h3.fontSize, fontWeight: '700', color: colors.neutral900 },
eventMeta: { marginTop: 4, fontSize: type.caption.fontSize, color: colors.neutral500 },
});