From bf8fe05de421589faa441342d5560e078c64ba3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Tue, 10 Mar 2026 17:48:49 -0400 Subject: [PATCH 1/6] first go lets go claude --- frontend/app/(auth)/(tabs)/_layout.tsx | 10 +- frontend/app/(auth)/(tabs)/index.tsx | 546 +++-------------- .../app/components/icons/FilledHomeIcon.tsx | 22 + frontend/app/components/ui/CardContent.tsx | 312 ++++++++++ .../app/components/ui/CardSheetContent.tsx | 558 ++++++++++++++++++ frontend/app/components/ui/CardStack.tsx | 456 ++++++++++++++ frontend/app/components/ui/cardTypes.ts | 87 +++ frontend/app/data/mockCards.ts | 230 ++++++++ 8 files changed, 1761 insertions(+), 460 deletions(-) create mode 100644 frontend/app/components/icons/FilledHomeIcon.tsx create mode 100644 frontend/app/components/ui/CardContent.tsx create mode 100644 frontend/app/components/ui/CardSheetContent.tsx create mode 100644 frontend/app/components/ui/CardStack.tsx create mode 100644 frontend/app/components/ui/cardTypes.ts create mode 100644 frontend/app/data/mockCards.ts diff --git a/frontend/app/(auth)/(tabs)/_layout.tsx b/frontend/app/(auth)/(tabs)/_layout.tsx index ae63bbc9..870f0f8c 100644 --- a/frontend/app/(auth)/(tabs)/_layout.tsx +++ b/frontend/app/(auth)/(tabs)/_layout.tsx @@ -1,11 +1,11 @@ import FilledBellIcon from '@/app/components/icons/FilledBellIcon'; import FilledChatIcon from '@/app/components/icons/FilledChatIcon'; -import FilledHeartIcon from '@/app/components/icons/FilledHeartIcon'; +import FilledHomeIcon from '@/app/components/icons/FilledHomeIcon'; import FilledProfileIcon from '@/app/components/icons/FilledProfileIcon'; import { useNotifications } from '@/app/contexts/NotificationsContext'; import { useHapticFeedback } from '@/app/hooks/useHapticFeedback'; import { Tabs } from 'expo-router'; -import { Bell, Heart, MessageCircle, User } from 'lucide-react-native'; +import { Bell, Home, MessageCircle, User } from 'lucide-react-native'; import { useRef } from 'react'; import { Animated, Pressable } from 'react-native'; import { AppColors } from '../../components/AppColors'; @@ -90,12 +90,12 @@ export default function TabLayout() { focused ? ( - + ) : ( - + ), tabBarButton: (props) => , }} diff --git a/frontend/app/(auth)/(tabs)/index.tsx b/frontend/app/(auth)/(tabs)/index.tsx index cbca0c3f..02e606b1 100644 --- a/frontend/app/(auth)/(tabs)/index.tsx +++ b/frontend/app/(auth)/(tabs)/index.tsx @@ -1,5 +1,4 @@ -// Main Matches/Home Screen -import { sendNudge } from '@/app/api/nudgesApi'; +// Main Home Screen — Card-based daily engagement import { getActivePrompt, getBatchMatchData, @@ -11,21 +10,10 @@ import { AppColors } from '@/app/components/AppColors'; import AppInput from '@/app/components/ui/AppInput'; import AppText from '@/app/components/ui/AppText'; import Button from '@/app/components/ui/Button'; -import CountdownTimer from '@/app/components/ui/CountdownTimer'; -import EmptyState from '@/app/components/ui/EmptyState'; -import ListItemWrapper from '@/app/components/ui/ListItemWrapper'; +import CardStack from '@/app/components/ui/CardStack'; import Sheet from '@/app/components/ui/Sheet'; -import WeeklyMatchCard from '@/app/components/ui/WeeklyMatchCard'; import { useThemeAware } from '@/app/contexts/ThemeContext'; -import { - getMatchDropDescription, - shouldShowCountdown, -} from '@/app/utils/dateUtils'; -import { - cacheMatchData, - clearMatchCache, - getCachedMatchData, -} from '@/app/utils/matchCache'; +import { cacheMatchData, getCachedMatchData } from '@/app/utils/matchCache'; import { getProfileAge, NudgeStatusResponse, @@ -34,195 +22,108 @@ import { WeeklyPromptAnswerResponse, WeeklyPromptResponse, } from '@/types'; +import { MOCK_MATCH_CARDS, PREFERENCE_CARDS, PROFILE_ACTION_CARDS, WEEKLY_PROMPT_CARDS } from '@/app/data/mockCards'; +import { DailyCard } from '@/app/components/ui/cardTypes'; import { useFocusEffect } from '@react-navigation/native'; -import { useRouter } from 'expo-router'; -import { Check, Heart, Pencil } from 'lucide-react-native'; -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { - Animated, - Dimensions, - ScrollView, - StatusBar, - StyleSheet, - View, -} from 'react-native'; - -const { width } = Dimensions.get('window'); +import { Check, Pencil } from 'lucide-react-native'; +import React, { useCallback, useMemo, useRef, useState } from 'react'; +import { StatusBar, StyleSheet, View } from 'react-native'; interface MatchWithProfile { netid: string; profile: ProfileResponse | null; revealed: boolean; nudgeStatus?: NudgeStatusResponse; - promptId: string; // Store promptId with each match for nudging + promptId: string; } -export default function MatchesScreen() { + +export default function HomeScreen() { useThemeAware(); - const router = useRouter(); - const [showCountdown, setShowCountdown] = useState(false); - const [activePrompt, setActivePrompt] = useState( - null - ); +const [activePrompt, setActivePrompt] = useState(null); const [userAnswer, setUserAnswer] = useState(''); const [currentMatches, setCurrentMatches] = useState([]); const [showPromptSheet, setShowPromptSheet] = useState(false); const [tempAnswer, setTempAnswer] = useState(''); const lastLoadTime = useRef(0); - // Track local nudges for immediate UI feedback - const [localNudges, setLocalNudges] = useState>(new Set()); - - // Track scroll position for real-time dot animation - const scrollX = useRef(new Animated.Value(0)).current; const loadData = useCallback(async () => { - // Prevent rapid successive calls (rate limiting on client side) const now = Date.now(); - const timeSinceLastLoad = now - lastLoadTime.current; - - if (timeSinceLastLoad < 1000) { - // Minimum 1 second between loads - console.log('⚠️ Skipping load - too soon since last load'); - return; - } - + if (now - lastLoadTime.current < 1000) return; lastLoadTime.current = now; try { let prompt: WeeklyPromptResponse | null = null; - // Get active prompt (optional - matches can exist without an active prompt) try { prompt = await getActivePrompt(); setActivePrompt(prompt); - - // Set countdown visibility based on active prompt - if (prompt) { - setShowCountdown(shouldShowCountdown(prompt.matchDate)); - } else { - setShowCountdown(false); - } - } catch (promptError) { - console.error('Error fetching active prompt:', promptError); + } catch { setActivePrompt(null); - setShowCountdown(false); // Hide countdown if no active prompt - // Don't return early - continue loading matches even without an active prompt } - // Get user's answer to the prompt (if there's an active prompt) if (prompt) { try { - const answer: WeeklyPromptAnswerResponse = await getPromptAnswer( - prompt.promptId - ); + const answer: WeeklyPromptAnswerResponse = await getPromptAnswer(prompt.promptId); setUserAnswer(answer.answer); } catch { - // No answer yet setUserAnswer(''); } } - // Get all match history - backend filters by expiration date automatically try { const history: WeeklyMatchResponse[] = await getMatchHistory(10); if (history.length > 0) { - // Collect all matches from all active match records const allMatchesData: MatchWithProfile[] = []; for (const matchRecord of history) { if (matchRecord.matches.length === 0) continue; - // Try to get cached data first for the most recent match let batchData; if (matchRecord === history[0]) { const cachedData = await getCachedMatchData(matchRecord.promptId); if (cachedData) { batchData = cachedData; - console.log('✅ Using cached match data'); } else { - batchData = await getBatchMatchData( - matchRecord.promptId, - matchRecord.matches - ); + batchData = await getBatchMatchData(matchRecord.promptId, matchRecord.matches); await cacheMatchData(matchRecord.promptId, batchData); - console.log('✅ Fetched and cached fresh match data'); } } else { - // For older matches, just fetch the data - batchData = await getBatchMatchData( - matchRecord.promptId, - matchRecord.matches - ); + batchData = await getBatchMatchData(matchRecord.promptId, matchRecord.matches); } - // Map the batch data to matches with profiles - const matchesWithProfiles: MatchWithProfile[] = - matchRecord.matches.map((netid: string, index: number) => { - const profile = - batchData.profiles.find((p) => p.netid === netid) || null; - - // Only get nudge status for the most recent matches + const matchesWithProfiles: MatchWithProfile[] = matchRecord.matches.map( + (netid: string, index: number) => { + const profile = batchData.profiles.find((p) => p.netid === netid) || null; const nudgeStatus = matchRecord === history[0] - ? batchData.nudgeStatuses[index] || { - sent: false, - received: false, - mutual: false, - } + ? batchData.nudgeStatuses[index] || { sent: false, received: false, mutual: false } : undefined; - - return { - netid, - profile, - revealed: matchRecord.revealed[index], - nudgeStatus, - promptId: matchRecord.promptId, - }; - }); + return { netid, profile, revealed: matchRecord.revealed[index], nudgeStatus, promptId: matchRecord.promptId }; + } + ); allMatchesData.push(...matchesWithProfiles); } setCurrentMatches(allMatchesData); } else { - // No match history at all setCurrentMatches([]); } - } catch (error) { - console.error('Error loading matches:', error); + } catch { setCurrentMatches([]); } } catch (error) { console.error('Error loading data:', error); - if (error instanceof Error) { - console.error('Error details:', error.message); - } - } finally { - // Clear local nudges after data reload since server state is now current - setLocalNudges(new Set()); } - }, []); // Empty dependency array since we use refs for state that shouldn't trigger re-renders + }, []); - useEffect(() => { - loadData(); - - // Update countdown state every minute - const interval = setInterval(() => { - if (activePrompt) { - setShowCountdown(shouldShowCountdown(activePrompt.matchDate)); - } else { - setShowCountdown(false); - } - }, 60000); - - return () => clearInterval(interval); - }, [activePrompt, loadData]); // Update when activePrompt changes or loadData changes + useFocusEffect(useCallback(() => { loadData(); }, [loadData])); const handleSubmitAnswer = async () => { if (!activePrompt || !tempAnswer.trim()) return; - try { await submitPromptAnswer(activePrompt.promptId, tempAnswer); setUserAnswer(tempAnswer); @@ -233,262 +134,68 @@ export default function MatchesScreen() { } }; - const [animationTrigger, setAnimationTrigger] = useState(0); - - // Trigger animation every time screen is focused - useFocusEffect( - useCallback(() => { - setAnimationTrigger((prev) => prev + 1); - }, []) - ); - - const renderCountdownPeriod = () => ( - <> - - {activePrompt && ( - - - Weekly Prompt: - - {activePrompt.question} - -