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.
This commit is contained in:
DocNR 2025-04-01 21:28:50 -04:00
parent fdd17ef261
commit 755e86e30b
6 changed files with 79 additions and 77 deletions

View File

@ -15,8 +15,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added "Sign with Amber" option to login screen
- Added comprehensive documentation in docs/technical/nostr/external-signers.md
- Added technical documentation in docs/technical/nostr/amber-integration-fixes.md
- Standardized login experience across profile screens
- Created reusable NostrProfileLogin component for consistent UI
- Added customizable messaging for context-specific instructions
- Standardized button styling and text formatting
- Improved visual hierarchy with consistent spacing
### Fixed
- Profile tab login experience
- Fixed error when accessing profile feed without authentication
- Created standardized login component across all profile screens
- Added conditional hook calling to prevent "rendered fewer hooks than expected" error
- Improved state management during authentication transitions
- Enhanced profile data loading with better error handling
- Android: Fixed Amber external signer integration issues
- Added extensive logging to better diagnose communication issues
- Improved error handling in `AmberSignerModule.kt`

View File

@ -8,6 +8,7 @@ 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';
@ -54,25 +55,7 @@ export default function ActivityScreen() {
// Show different UI when not authenticated
if (!isAuthenticated) {
return (
<View className="flex-1 items-center justify-center p-6">
<Text className="text-center text-muted-foreground mb-8">
Login with your Nostr private key to view your activity and stats.
</Text>
<Button
onPress={() => setIsLoginSheetOpen(true)}
className="px-6"
>
<Text className="text-white">Login with Nostr</Text>
</Button>
{/* NostrLoginSheet */}
<NostrLoginSheet
open={isLoginSheetOpen}
onClose={() => setIsLoginSheetOpen(false)}
/>
</View>
);
return <NostrProfileLogin message="Login with your Nostr private key to view your activity and stats." />;
}
if (loading || workoutsLoading || templatesLoading) {

View File

@ -1,5 +1,5 @@
// app/(tabs)/profile/overview.tsx
import React, { useState, useCallback } from 'react';
import React, { useState, useCallback, useEffect } from 'react';
import { View, FlatList, RefreshControl, Pressable, TouchableOpacity, ImageBackground, Clipboard } from 'react-native';
import { Text } from '@/components/ui/text';
import { Button } from '@/components/ui/button';
@ -7,6 +7,7 @@ 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 EnhancedSocialPost from '@/components/social/EnhancedSocialPost';
import EmptyFeed from '@/components/social/EmptyFeed';
import { useSocialFeed } from '@/lib/hooks/useSocialFeed';
@ -44,20 +45,31 @@ export default function OverviewScreen() {
const theme = useTheme() as CustomTheme;
const { currentUser, isAuthenticated } = useNDKCurrentUser();
const [isLoginSheetOpen, setIsLoginSheetOpen] = useState(false);
// Always use useSocialFeed regardless of authentication state to avoid hook inconsistency
// This prevents the "Rendered fewer hooks than expected" error when auth state changes
const {
feedItems,
loading,
refresh,
isOffline
} = useSocialFeed({
// Initialize feed related state
const [feedItems, setFeedItems] = useState<any[]>([]);
const [feedLoading, setFeedLoading] = useState(false);
const [isOffline, setIsOffline] = useState(false);
// Only call useSocialFeed when authenticated to prevent the error
const socialFeed = isAuthenticated ? useSocialFeed({
feedType: 'profile',
// Always provide an array for authors, empty if not authenticated
// This way the hook is always called with the same pattern
authors: currentUser?.pubkey ? [currentUser.pubkey] : [],
limit: 30
});
}) : null;
// Extract values from socialFeed when authenticated
const loading = isAuthenticated ? socialFeed?.loading || false : feedLoading;
const refresh = isAuthenticated
? (socialFeed?.refresh ? socialFeed.refresh : () => Promise.resolve())
: () => Promise.resolve();
// Update feedItems when socialFeed.feedItems changes
useEffect(() => {
if (isAuthenticated && socialFeed) {
setFeedItems(socialFeed.feedItems);
setIsOffline(socialFeed.isOffline);
}
}, [isAuthenticated, socialFeed?.feedItems, socialFeed?.isOffline]);
// Convert to the format expected by the component
const entries = React.useMemo(() => {
@ -359,26 +371,9 @@ export default function OverviewScreen() {
// Render functions for different app states
const renderLoginScreen = useCallback(() => {
return (
<View className="flex-1 items-center justify-center p-6">
<Text className="text-center text-muted-foreground mb-8">
Login with your Nostr private key to view your profile and posts.
</Text>
<Button
onPress={() => setIsLoginSheetOpen(true)}
className="px-6 py-3"
style={{ backgroundColor: 'hsl(261 90% 66%)' }}
>
<Text className="text-white font-medium">Login with Nostr</Text>
</Button>
{/* NostrLoginSheet */}
<NostrLoginSheet
open={isLoginSheetOpen}
onClose={() => setIsLoginSheetOpen(false)}
/>
</View>
<NostrProfileLogin message="Login with your Nostr private key to view your profile and posts." />
);
}, [isLoginSheetOpen]);
}, []);
const renderLoadingScreen = useCallback(() => {
return (

View File

@ -1,5 +1,6 @@
// app/(tabs)/profile/progress.tsx
import React, { useState, useEffect } from 'react';
import NostrProfileLogin from '@/components/social/NostrProfileLogin';
import { View, ScrollView, Switch, TouchableOpacity } from 'react-native';
import { Text } from '@/components/ui/text';
import { Button } from '@/components/ui/button';
@ -167,13 +168,7 @@ export default function ProgressScreen() {
};
if (!isAuthenticated) {
return (
<View className="flex-1 items-center justify-center p-6">
<Text className="text-center text-muted-foreground">
Log in to view your progress
</Text>
</View>
);
return <NostrProfileLogin message="Login with your Nostr private key to view your progress." />;
}
if (loading) {

View File

@ -8,6 +8,7 @@ import { useNDKCurrentUser, useNDKAuth } 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 TermsOfServiceModal from '@/components/TermsOfServiceModal';
import { useTheme } from '@react-navigation/native';
import type { CustomTheme } from '@/lib/theme';
@ -25,26 +26,7 @@ export default function SettingsScreen() {
// Show different UI when not authenticated
if (!isAuthenticated) {
return (
<View className="flex-1 items-center justify-center p-6">
<Text className="text-center text-muted-foreground mb-8">
Login with your Nostr private key to access settings.
</Text>
<Button
variant="purple"
onPress={() => setIsLoginSheetOpen(true)}
className="px-6"
>
<Text className="text-white">Login with Nostr</Text>
</Button>
{/* NostrLoginSheet */}
<NostrLoginSheet
open={isLoginSheetOpen}
onClose={() => setIsLoginSheetOpen(false)}
/>
</View>
);
return <NostrProfileLogin message="Login with your Nostr private key to access settings." />;
}
return (

View File

@ -0,0 +1,36 @@
import React, { useState } from 'react';
import { View } from 'react-native';
import { Text } from '@/components/ui/text';
import { Button } from '@/components/ui/button';
import NostrLoginSheet from '@/components/sheets/NostrLoginSheet';
interface NostrProfileLoginProps {
message?: string;
}
export default function NostrProfileLogin({
message = "Login with your Nostr private key to access your profile data."
}: NostrProfileLoginProps) {
const [isLoginSheetOpen, setIsLoginSheetOpen] = useState(false);
return (
<View className="flex-1 items-center justify-center p-6">
<Text className="text-center text-muted-foreground mb-8">
{message}
</Text>
<Button
variant="purple"
onPress={() => setIsLoginSheetOpen(true)}
className="px-6 py-3"
>
<Text className="text-white font-medium">Login with Nostr</Text>
</Button>
{/* NostrLoginSheet */}
<NostrLoginSheet
open={isLoginSheetOpen}
onClose={() => setIsLoginSheetOpen(false)}
/>
</View>
);
}