diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4fcb841..27b6e49 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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`
diff --git a/app/(tabs)/profile/activity.tsx b/app/(tabs)/profile/activity.tsx
index d99508a..d44fd4c 100644
--- a/app/(tabs)/profile/activity.tsx
+++ b/app/(tabs)/profile/activity.tsx
@@ -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 (
-
-
- Login with your Nostr private key to view your activity and stats.
-
-
-
- {/* NostrLoginSheet */}
- setIsLoginSheetOpen(false)}
- />
-
- );
+ return ;
}
if (loading || workoutsLoading || templatesLoading) {
diff --git a/app/(tabs)/profile/overview.tsx b/app/(tabs)/profile/overview.tsx
index 63c0e68..c7bb338 100644
--- a/app/(tabs)/profile/overview.tsx
+++ b/app/(tabs)/profile/overview.tsx
@@ -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([]);
+ 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 (
-
-
- Login with your Nostr private key to view your profile and posts.
-
-
-
- {/* NostrLoginSheet */}
- setIsLoginSheetOpen(false)}
- />
-
+
);
- }, [isLoginSheetOpen]);
+ }, []);
const renderLoadingScreen = useCallback(() => {
return (
diff --git a/app/(tabs)/profile/progress.tsx b/app/(tabs)/profile/progress.tsx
index a95f897..ec36c42 100644
--- a/app/(tabs)/profile/progress.tsx
+++ b/app/(tabs)/profile/progress.tsx
@@ -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 (
-
-
- Log in to view your progress
-
-
- );
+ return ;
}
if (loading) {
diff --git a/app/(tabs)/profile/settings.tsx b/app/(tabs)/profile/settings.tsx
index 5ede01c..9027ead 100644
--- a/app/(tabs)/profile/settings.tsx
+++ b/app/(tabs)/profile/settings.tsx
@@ -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 (
-
-
- Login with your Nostr private key to access settings.
-
-
-
- {/* NostrLoginSheet */}
- setIsLoginSheetOpen(false)}
- />
-
- );
+ return ;
}
return (
diff --git a/components/social/NostrProfileLogin.tsx b/components/social/NostrProfileLogin.tsx
new file mode 100644
index 0000000..0c84ccf
--- /dev/null
+++ b/components/social/NostrProfileLogin.tsx
@@ -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 (
+
+
+ {message}
+
+
+
+ {/* NostrLoginSheet */}
+ setIsLoginSheetOpen(false)}
+ />
+
+ );
+}