- (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>
140 lines
5.1 KiB
TypeScript
140 lines
5.1 KiB
TypeScript
/*
|
|
* SCR-M5 계열 [모바일] 관람객(B2C) 홈 — 친근한 소비자 톤.
|
|
* 인사 + 활성 행사 요약 + 라이브 공지 피드(상단 요약 3건) + 빠른 이동 타일(티켓·행사·현장) + AI 관람 도우미 안내.
|
|
* design.md §4 SCR-M5. 라이브 공지 = 공개 API(cms_backlog_contract.md).
|
|
*/
|
|
import { Ionicons } from '@expo/vector-icons';
|
|
import { router } from 'expo-router';
|
|
import React 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 VisitorHomeScreen() {
|
|
const { t } = useTranslation();
|
|
const { user, activeWorkspace } = useAuth();
|
|
const eventId = activeWorkspace?.eventId ?? DEFAULT_PUBLIC_EVENT_ID;
|
|
|
|
const tiles: {
|
|
key: string;
|
|
icon: keyof typeof Ionicons.glyphMap;
|
|
label: string;
|
|
desc: string;
|
|
onPress: () => void;
|
|
}[] = [
|
|
{
|
|
key: 'tickets',
|
|
icon: 'ticket-outline',
|
|
label: t('vhome.tileTickets'),
|
|
desc: t('vhome.tileTicketsDesc'),
|
|
onPress: () => router.push('/(visitor)/tickets'),
|
|
},
|
|
{
|
|
key: 'events',
|
|
icon: 'calendar-outline',
|
|
label: t('vhome.tileEvents'),
|
|
desc: t('vhome.tileEventsDesc'),
|
|
onPress: () => router.push('/(visitor)/events'),
|
|
},
|
|
{
|
|
key: 'onsite',
|
|
icon: 'navigate-outline',
|
|
label: t('vhome.tileOnsite'),
|
|
desc: t('vhome.tileOnsiteDesc'),
|
|
onPress: () => router.push('/(visitor)/onsite'),
|
|
},
|
|
];
|
|
|
|
return (
|
|
<ScrollView contentContainerStyle={styles.scroll}>
|
|
<Text style={styles.hello}>
|
|
{t('vhome.hello', { name: user?.displayName ?? t('common.user') })}
|
|
</Text>
|
|
<Text style={styles.sub}>{t('vhome.subtitle')}</Text>
|
|
|
|
{/* 활성 행사 요약(참여 행사가 있을 때) */}
|
|
{activeWorkspace ? (
|
|
<Card accent="none">
|
|
<View style={styles.eventTop}>
|
|
<Text style={styles.eventName} numberOfLines={1}>
|
|
{activeWorkspace.eventName}
|
|
</Text>
|
|
<DdayChip dday={activeWorkspace.dday} />
|
|
</View>
|
|
<Text style={styles.eventMeta}>
|
|
{activeWorkspace.hallLabel} · {activeWorkspace.startDate} ~ {activeWorkspace.endDate}
|
|
</Text>
|
|
</Card>
|
|
) : (
|
|
<Banner tone="info">{t('vhome.noEvent')}</Banner>
|
|
)}
|
|
|
|
{/* 라이브 공지 요약(상단 3건) */}
|
|
<LiveNoticeFeed eventId={eventId} max={3} />
|
|
|
|
{/* 빠른 이동 타일 */}
|
|
<View style={styles.tiles}>
|
|
{tiles.map((tile) => (
|
|
<Pressable key={tile.key} style={styles.tile} onPress={tile.onPress}>
|
|
<View style={styles.tileIcon}>
|
|
<Ionicons name={tile.icon} size={24} color={colors.primary600} />
|
|
</View>
|
|
<Text style={styles.tileLabel}>{tile.label}</Text>
|
|
<Text style={styles.tileDesc}>{tile.desc}</Text>
|
|
</Pressable>
|
|
))}
|
|
</View>
|
|
|
|
{/* AI 관람 도우미 안내 */}
|
|
<Card accent="ai">
|
|
<View style={styles.aiRow}>
|
|
<Ionicons name="sparkles-outline" size={20} color={colors.aiAccent} />
|
|
<View style={{ flex: 1 }}>
|
|
<Text style={styles.aiTitle}>{t('vhome.aiTitle')}</Text>
|
|
<Text style={styles.aiDesc}>{t('vhome.aiDesc')}</Text>
|
|
</View>
|
|
</View>
|
|
</Card>
|
|
</ScrollView>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
scroll: { padding: spacing.md, gap: spacing.md, paddingBottom: 40 },
|
|
hello: { fontSize: type.h2.fontSize, fontWeight: '700', color: colors.neutral900 },
|
|
sub: { marginTop: -8, fontSize: type.caption.fontSize, color: colors.neutral500 },
|
|
eventTop: { flexDirection: 'row', alignItems: 'center', 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 },
|
|
tiles: { flexDirection: 'row', gap: spacing.sm },
|
|
tile: {
|
|
flex: 1,
|
|
backgroundColor: colors.white,
|
|
borderWidth: 1,
|
|
borderColor: colors.neutral200,
|
|
borderRadius: radius.md,
|
|
padding: spacing.sm,
|
|
gap: 6,
|
|
minHeight: 104,
|
|
},
|
|
tileIcon: {
|
|
width: 40,
|
|
height: 40,
|
|
borderRadius: radius.sm,
|
|
backgroundColor: colors.primary050,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
tileLabel: { fontSize: type.body.fontSize, fontWeight: '700', color: colors.neutral900 },
|
|
tileDesc: { fontSize: 11, color: colors.neutral500, lineHeight: 15 },
|
|
aiRow: { flexDirection: 'row', gap: 10, alignItems: 'flex-start' },
|
|
aiTitle: { fontSize: type.body.fontSize, fontWeight: '700', color: colors.aiAccent },
|
|
aiDesc: { marginTop: 2, fontSize: type.caption.fontSize, color: colors.neutral700, lineHeight: 18 },
|
|
});
|