POWR/app/(tabs)/profile/activity.tsx
DocNR 755e86e30b fix(profile): standardize login experience across profile screens
- Create reusable NostrProfileLogin component for consistent UI across all screens
- Fix profile feed error when not authenticated by conditionally calling useSocialFeed
- Resolve hook ordering inconsistencies to prevent React errors
- Improve feed loading state management during authentication transitions
- Update all profile screens (overview, activity, progress, settings) to use shared component
- Add proper error handling for profile data loading when not authenticated

This change resolves the console error that appeared when accessing the profile tab
while not logged in, while also providing a better user experience through
consistent styling and messaging across all profile screens.
2025-04-01 21:28:50 -04:00

201 lines
7.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// app/(tabs)/profile/activity.tsx
import React, { useState, useEffect } from 'react';
import { View, ScrollView, Pressable } from 'react-native';
import { Text } from '@/components/ui/text';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { useNDKCurrentUser } from '@/lib/hooks/useNDK';
import { ActivityIndicator } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import NostrLoginSheet from '@/components/sheets/NostrLoginSheet';
import NostrProfileLogin from '@/components/social/NostrProfileLogin';
import { useRouter } from 'expo-router';
import { useAnalytics } from '@/lib/hooks/useAnalytics';
import { PersonalRecord } from '@/lib/services/AnalyticsService';
import { formatDistanceToNow } from 'date-fns';
import { useWorkouts } from '@/lib/hooks/useWorkouts';
import { useTemplates } from '@/lib/hooks/useTemplates';
import WorkoutCard from '@/components/workout/WorkoutCard';
import { Dumbbell, BarChart2, Award, Calendar } from 'lucide-react-native';
export default function ActivityScreen() {
const insets = useSafeAreaInsets();
const router = useRouter();
const { isAuthenticated } = useNDKCurrentUser();
const analytics = useAnalytics();
const { workouts, loading: workoutsLoading } = useWorkouts();
const { templates, loading: templatesLoading } = useTemplates();
const [isLoginSheetOpen, setIsLoginSheetOpen] = useState(false);
const [records, setRecords] = useState<PersonalRecord[]>([]);
const [loading, setLoading] = useState(true);
// Stats
const completedWorkouts = workouts?.filter(w => w.isCompleted)?.length || 0;
const totalTemplates = templates?.length || 0;
const totalPrograms = 0; // Placeholder for programs count
// Load personal records
useEffect(() => {
async function loadRecords() {
if (!isAuthenticated) return;
try {
setLoading(true);
const personalRecords = await analytics.getPersonalRecords(undefined, 3);
setRecords(personalRecords);
} catch (error) {
console.error('Error loading personal records:', error);
} finally {
setLoading(false);
}
}
loadRecords();
}, [isAuthenticated, analytics]);
// Show different UI when not authenticated
if (!isAuthenticated) {
return <NostrProfileLogin message="Login with your Nostr private key to view your activity and stats." />;
}
if (loading || workoutsLoading || templatesLoading) {
return (
<View className="flex-1 items-center justify-center">
<ActivityIndicator />
</View>
);
}
return (
<ScrollView
className="flex-1"
contentContainerStyle={{
paddingBottom: insets.bottom + 20
}}
>
{/* Stats Cards - Row 1 */}
<View className="flex-row px-4 pt-4">
<View className="flex-1 pr-2">
<Card>
<CardContent className="p-4 items-center justify-center">
<Dumbbell size={24} className="text-primary mb-2" />
<Text className="text-2xl font-bold">{completedWorkouts}</Text>
<Text className="text-muted-foreground">Workouts</Text>
</CardContent>
</Card>
</View>
<View className="flex-1 pl-2">
<Card>
<CardContent className="p-4 items-center justify-center">
<Calendar size={24} className="text-primary mb-2" />
<Text className="text-2xl font-bold">{totalTemplates}</Text>
<Text className="text-muted-foreground">Templates</Text>
</CardContent>
</Card>
</View>
</View>
{/* Stats Cards - Row 2 */}
<View className="flex-row px-4 pt-4 mb-4">
<View className="flex-1 pr-2">
<Card>
<CardContent className="p-4 items-center justify-center">
<BarChart2 size={24} className="text-primary mb-2" />
<Text className="text-2xl font-bold">{totalPrograms}</Text>
<Text className="text-muted-foreground">Programs</Text>
</CardContent>
</Card>
</View>
<View className="flex-1 pl-2">
<Card>
<CardContent className="p-4 items-center justify-center">
<Award size={24} className="text-primary mb-2" />
<Text className="text-2xl font-bold">{records.length}</Text>
<Text className="text-muted-foreground">PRs</Text>
</CardContent>
</Card>
</View>
</View>
{/* Personal Records */}
<Card className="mx-4 mb-4">
<CardContent className="p-4">
<View className="flex-row justify-between items-center mb-4">
<Text className="text-lg font-semibold">Personal Records</Text>
<Pressable onPress={() => router.push('/profile/progress')}>
<Text className="text-primary">View All</Text>
</Pressable>
</View>
{records.length === 0 ? (
<Text className="text-muted-foreground text-center py-4">
No personal records yet. Complete more workouts to see your progress.
</Text>
) : (
records.map((record) => (
<View key={record.id} className="py-2 border-b border-border">
<Text className="font-medium">{record.exerciseName}</Text>
<Text>{record.value} {record.unit} × {record.reps} reps</Text>
<Text className="text-muted-foreground text-sm">
{formatDistanceToNow(new Date(record.date), { addSuffix: true })}
</Text>
</View>
))
)}
</CardContent>
</Card>
{/* Recent Workouts */}
<Card className="mx-4 mb-4">
<CardContent className="p-4">
<View className="flex-row justify-between items-center mb-4">
<Text className="text-lg font-semibold">Recent Workouts</Text>
<Pressable onPress={() => router.push('/history/workoutHistory')}>
<Text className="text-primary">View All</Text>
</Pressable>
</View>
{workouts && workouts.length > 0 ? (
workouts
.filter(workout => workout.isCompleted)
.slice(0, 2)
.map(workout => (
<View key={workout.id} className="mb-3">
<WorkoutCard
workout={workout}
showDate={true}
showExercises={false}
/>
</View>
))
) : (
<Text className="text-muted-foreground text-center py-4">
No recent workouts. Complete a workout to see it here.
</Text>
)}
</CardContent>
</Card>
{/* Quick Actions */}
<View className="p-4 gap-2">
<Button
variant="purple"
className="mb-2"
onPress={() => router.push('/')}
>
<Text className="text-white">Start a Workout</Text>
</Button>
<Button
variant="outline"
className="mb-2"
onPress={() => router.push('/profile/progress')}
>
<Text>View Progress</Text>
</Button>
</View>
</ScrollView>
);
}