import { View, Text, StyleSheet, ScrollView, RefreshControl, Platform, Alert } from "react-native"; import { usePacksStore, useAuthStore, useSubscriptionStore } from "../../src/store"; import { Ionicons } from "@expo/vector-icons"; import { router, useLocalSearchParams } from "expo-router"; import { useEffect, useMemo, useState, useCallback } from "react"; import Animated, { FadeInDown, FadeIn, Layout } from "react-native-reanimated"; import { GradientBackground } from "../../src/components/ui"; import { Paywall } from "../../src/components/Paywall"; import { PackTeaser } from "../../src/components/PackTeaser"; import { CategoryTabs, FilterType } from "../../src/components/CategoryTabs"; import { BoutiquePackCard } from "../../src/components/BoutiquePackCard"; import { colors, spacing, typography } from "../../src/theme"; import type { QuestionPack } from "../../src/types"; export default function PacksScreen() { const { category } = useLocalSearchParams<{ category?: string }>(); const { packs, categories, enabledPackIds, togglePack, isLoading, fetchPacks } = usePacksStore(); const { user, partner } = useAuthStore(); const { subscription } = useSubscriptionStore(); const [selectedFilter, setSelectedFilter] = useState('all'); const [showPaywall, setShowPaywall] = useState(false); const [showTeaser, setShowTeaser] = useState(false); const [teaserPack, setTeaserPack] = useState(null); // Check if user or partner has premium access const hasPremiumAccess = useMemo(() => { return user?.is_premium || partner?.is_premium || subscription.isProUser; }, [user?.is_premium, partner?.is_premium, subscription.isProUser]); useEffect(() => { fetchPacks(); }, []); // Apply category filter from URL param useEffect(() => { if (category) { setSelectedFilter(category); } }, [category]); // Filter packs by selected filter (all, enabled, or category id) const filteredPacks = useMemo(() => { if (selectedFilter === 'all') return packs; if (selectedFilter === 'enabled') return packs.filter(p => enabledPackIds.includes(p.id)); // Otherwise it's a category id return packs.filter(p => p.category_id === selectedFilter); }, [packs, selectedFilter, enabledPackIds]); // Split packs into two columns for masonry layout const { leftColumn, rightColumn } = useMemo(() => { const left: { pack: QuestionPack; index: number }[] = []; const right: { pack: QuestionPack; index: number }[] = []; let leftHeight = 0; let rightHeight = 0; filteredPacks.forEach((pack, index) => { // Estimate card height based on description length const baseHeight = 180; const descLength = pack.description?.length || 0; const extraHeight = Math.min(Math.ceil(descLength / 40) * 10, 40); const cardHeight = baseHeight + extraHeight; if (leftHeight <= rightHeight) { left.push({ pack, index }); leftHeight += cardHeight; } else { right.push({ pack, index }); rightHeight += cardHeight; } }); return { leftColumn: left, rightColumn: right }; }, [filteredPacks]); // Handle toggling a pack - show teaser if premium pack without access const handleTogglePack = useCallback(async (pack: QuestionPack) => { if (pack.is_premium && !hasPremiumAccess) { setTeaserPack(pack); setShowTeaser(true); return; } const result = await togglePack(pack.id); if (!result.success && result.reason === "no_couple") { Alert.alert( "Pair with Partner", "You need to pair with your partner before you can customise question packs.", [ { text: "Later", style: "cancel" }, { text: "Pair Now", onPress: () => router.push("/pairing") } ] ); } }, [hasPremiumAccess, togglePack]); // Handle pack press - navigate or show teaser const handlePackPress = useCallback((pack: QuestionPack) => { const isPremiumLocked = pack.is_premium && !hasPremiumAccess; if (isPremiumLocked) { setTeaserPack(pack); setShowTeaser(true); } else { router.push(`/pack/${pack.id}`); } }, [hasPremiumAccess]); // Handle teaser unlock button - show paywall const handleTeaserUnlock = useCallback(() => { setShowTeaser(false); setShowPaywall(true); }, []); // Handle teaser close const handleTeaserClose = useCallback(() => { setShowTeaser(false); setTeaserPack(null); }, []); const enabledPacks = packs.filter(p => enabledPackIds.includes(p.id)); const enabledPackCount = enabledPacks.length; const totalQuestions = enabledPacks.reduce((sum, p) => sum + (p.questions?.[0]?.count || 0), 0); return ( {/* Header */} Question Packs {totalQuestions} {totalQuestions === 1 ? 'question' : 'questions'} from {enabledPackCount} {enabledPackCount === 1 ? 'pack' : 'packs'} enabled {/* Category Tabs */} {/* Masonry Grid */} } contentContainerStyle={styles.scrollContent} > {filteredPacks.length === 0 ? ( No packs available {selectedFilter === 'enabled' ? "No packs enabled yet. Enable some packs to see them here!" : selectedFilter !== 'all' ? "No packs in this category. Try selecting a different one." : "Check back later for new question packs!"} ) : ( {/* Left Column */} {leftColumn.map(({ pack, index }, columnIndex) => ( handlePackPress(pack)} onToggle={() => handleTogglePack(pack)} /> ))} {/* Right Column */} {rightColumn.map(({ pack, index }, columnIndex) => ( handlePackPress(pack)} onToggle={() => handleTogglePack(pack)} /> ))} )} {/* Modals */} {teaserPack && ( )} setShowPaywall(false)} onSuccess={() => { // Refresh packs after successful purchase fetchPacks(); }} /> ); } const styles = StyleSheet.create({ container: { flex: 1, }, header: { paddingTop: 60, paddingHorizontal: spacing.lg, paddingBottom: spacing.sm, }, title: { ...typography.title1, color: colors.text, }, subtitle: { ...typography.subhead, color: colors.textSecondary, marginTop: spacing.xs, }, scrollContent: { paddingBottom: Platform.OS === 'ios' ? 120 : 100, }, masonryContainer: { flexDirection: 'row', paddingHorizontal: spacing.md, gap: spacing.md, }, column: { flex: 1, gap: spacing.md, }, emptyContainer: { alignItems: "center", paddingVertical: spacing.xxl, paddingHorizontal: spacing.lg, }, emptyIconContainer: { width: 80, height: 80, borderRadius: 40, backgroundColor: colors.glass.background, justifyContent: "center", alignItems: "center", marginBottom: spacing.md, }, emptyTitle: { ...typography.title3, color: colors.text, marginBottom: spacing.xs, }, emptyText: { ...typography.body, color: colors.textSecondary, textAlign: "center", }, });