routes fix
extensive loging added
This commit is contained in:
1 parent
36315ea5cf
commit
22c274bb83
9 files changed
+1252
-508
No files matched your search
@@ -1,290 +1,392 @@
|
||||
import React, { useState, useCallback, useRef } from 'react';
|
||||
import { View, Text, StyleSheet, ScrollView, RefreshControl, TouchableOpacity, Animated, Alert } from 'react-native';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { theme } from '../../styles/theme';
|
||||
import { GoalProgressCard } from '../../components/GoalProgressCard';
|
||||
import { GoalCreationModal } from '../../components/GoalCreationModal';
|
||||
import React, { useState, useCallback, useRef } from "react";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
ScrollView,
|
||||
RefreshControl,
|
||||
TouchableOpacity,
|
||||
Animated,
|
||||
Alert,
|
||||
} from "react-native";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { LinearGradient } from "expo-linear-gradient";
|
||||
import { theme } from "../../styles/theme";
|
||||
import { GoalProgressCard } from "../../components/GoalProgressCard";
|
||||
import { GoalCreationModal } from "../../components/GoalCreationModal";
|
||||
import { useUser, useAuth } from "@clerk/clerk-expo";
|
||||
import { fitnessGoalsService, type FitnessGoal, type CreateGoalData } from '../../services/fitnessGoals';
|
||||
import { useFocusEffect } from 'expo-router';
|
||||
import {
|
||||
fitnessGoalsService,
|
||||
type FitnessGoal,
|
||||
type CreateGoalData,
|
||||
} from "../../services/fitnessGoals";
|
||||
import { useFocusEffect } from "expo-router";
|
||||
import * as SecureStore from "expo-secure-store";
|
||||
|
||||
export default function GoalsScreen() {
|
||||
const { user } = useUser();
|
||||
const { getToken } = useAuth();
|
||||
const [goals, setGoals] = useState<FitnessGoal[]>([]);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const fabScale = useRef(new Animated.Value(1)).current;
|
||||
const { user } = useUser();
|
||||
const { getToken } = useAuth();
|
||||
const [goals, setGoals] = useState<FitnessGoal[]>([]);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const fabScale = useRef(new Animated.Value(1)).current;
|
||||
|
||||
const loadGoals = useCallback(async () => {
|
||||
if (!user?.id) return;
|
||||
const loadGoals = useCallback(async () => {
|
||||
if (!user?.id) return;
|
||||
try {
|
||||
const token = await getToken();
|
||||
console.log(
|
||||
"Token obtained:",
|
||||
token ? "Yes (" + token.substring(0, 20) + "...)" : "No",
|
||||
);
|
||||
console.log("User ID:", user.id);
|
||||
|
||||
// Decode and log token details for debugging
|
||||
if (token) {
|
||||
try {
|
||||
const token = await getToken();
|
||||
const loadedGoals = await fitnessGoalsService.getGoals(user.id, token);
|
||||
setGoals(loadedGoals);
|
||||
} catch (error) {
|
||||
console.error('Error loading goals:', error);
|
||||
const parts = token.split(".");
|
||||
if (parts.length === 3) {
|
||||
const payload = JSON.parse(atob(parts[1]));
|
||||
console.log("Token issuer:", payload.iss);
|
||||
console.log(
|
||||
"Token kid from header:",
|
||||
JSON.parse(atob(parts[0])).kid,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Could not decode token");
|
||||
}
|
||||
}, [user?.id]); // Removed getToken from dependencies
|
||||
}
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
loadGoals();
|
||||
}, [loadGoals])
|
||||
);
|
||||
const loadedGoals = await fitnessGoalsService.getGoals(user.id, token);
|
||||
setGoals(loadedGoals);
|
||||
} catch (error) {
|
||||
console.error("Error loading goals:", error);
|
||||
}
|
||||
}, [user?.id, getToken]);
|
||||
|
||||
const onRefresh = async () => {
|
||||
setRefreshing(true);
|
||||
await loadGoals();
|
||||
setRefreshing(false);
|
||||
};
|
||||
const clearClerkCache = async () => {
|
||||
Alert.alert(
|
||||
"Clear Clerk Cache",
|
||||
"This will clear all cached Clerk tokens. You will need to sign out and sign back in.",
|
||||
[
|
||||
{ text: "Cancel", style: "cancel" },
|
||||
{
|
||||
text: "Clear Cache",
|
||||
style: "destructive",
|
||||
onPress: async () => {
|
||||
try {
|
||||
// Clear all possible Clerk token keys
|
||||
const keysToDelete = [
|
||||
"__clerk_client_jwt",
|
||||
"__clerk_db_jwt",
|
||||
"__clerk_client_uat",
|
||||
"__clerk_session_id",
|
||||
"__clerk_refresh_token",
|
||||
"__clerk_session_jwt",
|
||||
];
|
||||
|
||||
const handleCreateGoal = async (newGoal: CreateGoalData) => {
|
||||
const token = await getToken();
|
||||
await fitnessGoalsService.createGoal(newGoal, token);
|
||||
await loadGoals();
|
||||
setIsModalVisible(false);
|
||||
};
|
||||
|
||||
const handleCompleteGoal = async (goal: FitnessGoal) => {
|
||||
const token = await getToken();
|
||||
await fitnessGoalsService.completeGoal(goal.id, token);
|
||||
await loadGoals();
|
||||
};
|
||||
|
||||
const handleDeleteGoal = async (goalId: string) => {
|
||||
const token = await getToken();
|
||||
await fitnessGoalsService.deleteGoal(goalId, token);
|
||||
await loadGoals();
|
||||
};
|
||||
|
||||
const activeGoals = goals.filter(g => g.status === 'active');
|
||||
const completedGoals = goals.filter(g => g.status === 'completed');
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor={theme.colors.primary} />
|
||||
for (const key of keysToDelete) {
|
||||
try {
|
||||
await SecureStore.deleteItemAsync(key);
|
||||
} catch (e) {
|
||||
// Key might not exist
|
||||
}
|
||||
>
|
||||
<LinearGradient
|
||||
colors={theme.gradients.primary}
|
||||
style={styles.header}
|
||||
>
|
||||
<View>
|
||||
<Text style={styles.headerTitle}>Fitness Goals</Text>
|
||||
<Text style={styles.headerSubtitle}>
|
||||
Track your fitness journey progress
|
||||
</Text>
|
||||
</View>
|
||||
</LinearGradient>
|
||||
}
|
||||
|
||||
{/* Stats Summary */}
|
||||
{goals.length > 0 && (
|
||||
<View style={styles.statsContainer}>
|
||||
<View style={styles.statCard}>
|
||||
<Text style={styles.statValue}>{activeGoals.length}</Text>
|
||||
<Text style={styles.statLabel}>Active</Text>
|
||||
</View>
|
||||
<View style={styles.statCard}>
|
||||
<Text style={styles.statValue}>{completedGoals.length}</Text>
|
||||
<Text style={styles.statLabel}>Completed</Text>
|
||||
</View>
|
||||
<View style={styles.statCard}>
|
||||
<Text style={styles.statValue}>
|
||||
{activeGoals.length > 0
|
||||
? Math.round(
|
||||
activeGoals.reduce((sum, g) => sum + (g.progress || 0), 0) /
|
||||
activeGoals.length
|
||||
)
|
||||
: 0}%
|
||||
</Text>
|
||||
<Text style={styles.statLabel}>Avg Progress</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Active Goals */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>
|
||||
Active Goals ({activeGoals.length})
|
||||
</Text>
|
||||
{activeGoals.length === 0 ? (
|
||||
<View style={styles.emptyState}>
|
||||
<Ionicons name="flag-outline" size={48} color="#d1d5db" />
|
||||
<Text style={styles.emptyText}>No active goals yet</Text>
|
||||
<Text style={styles.emptySubtext}>
|
||||
Tap the + button to create your first goal
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
activeGoals.map((goal) => (
|
||||
<GoalProgressCard
|
||||
key={goal.id}
|
||||
goal={goal}
|
||||
onComplete={() => handleCompleteGoal(goal)}
|
||||
onDelete={() => handleDeleteGoal(goal.id)}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Completed Goals */}
|
||||
{completedGoals.length > 0 && (
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>
|
||||
Completed Goals ({completedGoals.length})
|
||||
</Text>
|
||||
{completedGoals.map((goal) => (
|
||||
<GoalProgressCard
|
||||
key={goal.id}
|
||||
goal={goal}
|
||||
onDelete={() => handleDeleteGoal(goal.id)}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={styles.footer} />
|
||||
</ScrollView>
|
||||
|
||||
{/* Floating Action Button */}
|
||||
<Animated.View style={[styles.fabContainer, { transform: [{ scale: fabScale }] }]}>
|
||||
<TouchableOpacity
|
||||
onPress={() => setIsModalVisible(true)}
|
||||
onPressIn={() => {
|
||||
Animated.spring(fabScale, {
|
||||
toValue: 0.9,
|
||||
friction: 8,
|
||||
tension: 100,
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
}}
|
||||
onPressOut={() => {
|
||||
Animated.spring(fabScale, {
|
||||
toValue: 1,
|
||||
friction: 8,
|
||||
tension: 100,
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
}}
|
||||
activeOpacity={0.9}
|
||||
>
|
||||
<LinearGradient
|
||||
colors={theme.gradients.primary}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
style={styles.fab}
|
||||
>
|
||||
<Ionicons name="add" size={28} color="#fff" />
|
||||
</LinearGradient>
|
||||
</TouchableOpacity>
|
||||
</Animated.View>
|
||||
|
||||
{/* Create Goal Modal */}
|
||||
<GoalCreationModal
|
||||
visible={isModalVisible}
|
||||
onClose={() => setIsModalVisible(false)}
|
||||
onSubmit={handleCreateGoal}
|
||||
/>
|
||||
</View>
|
||||
Alert.alert(
|
||||
"Success",
|
||||
"Cache cleared! Please sign out and sign back in.",
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error clearing cache:", error);
|
||||
Alert.alert("Error", "Failed to clear cache");
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
);
|
||||
};
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
loadGoals();
|
||||
}, [loadGoals]),
|
||||
);
|
||||
|
||||
const onRefresh = async () => {
|
||||
setRefreshing(true);
|
||||
await loadGoals();
|
||||
setRefreshing(false);
|
||||
};
|
||||
|
||||
const handleCreateGoal = async (newGoal: CreateGoalData) => {
|
||||
const token = await getToken();
|
||||
await fitnessGoalsService.createGoal(newGoal, token);
|
||||
await loadGoals();
|
||||
setIsModalVisible(false);
|
||||
};
|
||||
|
||||
const handleCompleteGoal = async (goal: FitnessGoal) => {
|
||||
const token = await getToken();
|
||||
await fitnessGoalsService.completeGoal(goal.id, token);
|
||||
await loadGoals();
|
||||
};
|
||||
|
||||
const handleDeleteGoal = async (goalId: string) => {
|
||||
const token = await getToken();
|
||||
await fitnessGoalsService.deleteGoal(goalId, token);
|
||||
await loadGoals();
|
||||
};
|
||||
|
||||
const activeGoals = goals.filter((g) => g.status === "active");
|
||||
const completedGoals = goals.filter((g) => g.status === "completed");
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
tintColor={theme.colors.primary}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<LinearGradient colors={theme.gradients.primary} style={styles.header}>
|
||||
<View style={styles.headerContent}>
|
||||
<View>
|
||||
<Text style={styles.headerTitle}>Fitness Goals</Text>
|
||||
<Text style={styles.headerSubtitle}>
|
||||
Track your fitness journey progress
|
||||
</Text>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
onPress={clearClerkCache}
|
||||
style={styles.debugButton}
|
||||
>
|
||||
<Ionicons name="refresh-circle-outline" size={24} color="#fff" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</LinearGradient>
|
||||
|
||||
{/* Stats Summary */}
|
||||
{goals.length > 0 && (
|
||||
<View style={styles.statsContainer}>
|
||||
<View style={styles.statCard}>
|
||||
<Text style={styles.statValue}>{activeGoals.length}</Text>
|
||||
<Text style={styles.statLabel}>Active</Text>
|
||||
</View>
|
||||
<View style={styles.statCard}>
|
||||
<Text style={styles.statValue}>{completedGoals.length}</Text>
|
||||
<Text style={styles.statLabel}>Completed</Text>
|
||||
</View>
|
||||
<View style={styles.statCard}>
|
||||
<Text style={styles.statValue}>
|
||||
{activeGoals.length > 0
|
||||
? Math.round(
|
||||
activeGoals.reduce(
|
||||
(sum, g) => sum + (g.progress || 0),
|
||||
0,
|
||||
) / activeGoals.length,
|
||||
)
|
||||
: 0}
|
||||
%
|
||||
</Text>
|
||||
<Text style={styles.statLabel}>Avg Progress</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Active Goals */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>
|
||||
Active Goals ({activeGoals.length})
|
||||
</Text>
|
||||
{activeGoals.length === 0 ? (
|
||||
<View style={styles.emptyState}>
|
||||
<Ionicons name="flag-outline" size={48} color="#d1d5db" />
|
||||
<Text style={styles.emptyText}>No active goals yet</Text>
|
||||
<Text style={styles.emptySubtext}>
|
||||
Tap the + button to create your first goal
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
activeGoals.map((goal) => (
|
||||
<GoalProgressCard
|
||||
key={goal.id}
|
||||
goal={goal}
|
||||
onComplete={() => handleCompleteGoal(goal)}
|
||||
onDelete={() => handleDeleteGoal(goal.id)}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Completed Goals */}
|
||||
{completedGoals.length > 0 && (
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>
|
||||
Completed Goals ({completedGoals.length})
|
||||
</Text>
|
||||
{completedGoals.map((goal) => (
|
||||
<GoalProgressCard
|
||||
key={goal.id}
|
||||
goal={goal}
|
||||
onDelete={() => handleDeleteGoal(goal.id)}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={styles.footer} />
|
||||
</ScrollView>
|
||||
|
||||
{/* Floating Action Button */}
|
||||
<Animated.View
|
||||
style={[styles.fabContainer, { transform: [{ scale: fabScale }] }]}
|
||||
>
|
||||
<TouchableOpacity
|
||||
onPress={() => setIsModalVisible(true)}
|
||||
onPressIn={() => {
|
||||
Animated.spring(fabScale, {
|
||||
toValue: 0.9,
|
||||
friction: 8,
|
||||
tension: 100,
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
}}
|
||||
onPressOut={() => {
|
||||
Animated.spring(fabScale, {
|
||||
toValue: 1,
|
||||
friction: 8,
|
||||
tension: 100,
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
}}
|
||||
activeOpacity={0.9}
|
||||
>
|
||||
<LinearGradient
|
||||
colors={theme.gradients.primary}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
style={styles.fab}
|
||||
>
|
||||
<Ionicons name="add" size={28} color="#fff" />
|
||||
</LinearGradient>
|
||||
</TouchableOpacity>
|
||||
</Animated.View>
|
||||
|
||||
{/* Create Goal Modal */}
|
||||
<GoalCreationModal
|
||||
visible={isModalVisible}
|
||||
onClose={() => setIsModalVisible(false)}
|
||||
onSubmit={handleCreateGoal}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
scrollContent: {
|
||||
paddingBottom: 20,
|
||||
},
|
||||
header: {
|
||||
padding: 24,
|
||||
paddingTop: 60,
|
||||
paddingBottom: 24,
|
||||
marginBottom: 10,
|
||||
borderBottomLeftRadius: theme.borderRadius.xl,
|
||||
borderBottomRightRadius: theme.borderRadius.xl,
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: theme.typography.fontSize['3xl'],
|
||||
fontWeight: theme.typography.fontWeight.bold,
|
||||
color: theme.colors.white,
|
||||
},
|
||||
headerSubtitle: {
|
||||
fontSize: theme.typography.fontSize.base,
|
||||
color: "rgba(255, 255, 255, 0.9)",
|
||||
marginTop: 4,
|
||||
},
|
||||
statsContainer: {
|
||||
flexDirection: "row",
|
||||
padding: 16,
|
||||
gap: 12,
|
||||
},
|
||||
statCard: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.colors.white,
|
||||
padding: 16,
|
||||
borderRadius: theme.borderRadius.xl,
|
||||
alignItems: 'center',
|
||||
...theme.shadows.medium,
|
||||
borderWidth: 1,
|
||||
borderColor: "rgba(59, 130, 246, 0.1)",
|
||||
},
|
||||
statValue: {
|
||||
fontSize: theme.typography.fontSize['2xl'],
|
||||
fontWeight: theme.typography.fontWeight.bold,
|
||||
color: theme.colors.primary,
|
||||
marginBottom: 4,
|
||||
},
|
||||
statLabel: {
|
||||
fontSize: 12,
|
||||
color: "#6b7280",
|
||||
fontWeight: "500",
|
||||
},
|
||||
section: {
|
||||
padding: 20,
|
||||
paddingTop: 10,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: "600",
|
||||
color: "#374151",
|
||||
marginBottom: 12,
|
||||
},
|
||||
emptyState: {
|
||||
alignItems: "center",
|
||||
paddingVertical: 40,
|
||||
},
|
||||
emptyText: {
|
||||
fontSize: 16,
|
||||
fontWeight: "500",
|
||||
color: "#6b7280",
|
||||
marginTop: 12,
|
||||
},
|
||||
emptySubtext: {
|
||||
fontSize: 14,
|
||||
color: "#9ca3af",
|
||||
marginTop: 4,
|
||||
},
|
||||
footer: {
|
||||
height: 100,
|
||||
},
|
||||
fabContainer: {
|
||||
position: "absolute",
|
||||
right: 20,
|
||||
bottom: 110, // Adjusted for tab bar height
|
||||
},
|
||||
fab: {
|
||||
width: 64,
|
||||
height: 64,
|
||||
borderRadius: 32,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
...theme.shadows.glow,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
scrollContent: {
|
||||
paddingBottom: 20,
|
||||
},
|
||||
header: {
|
||||
padding: 24,
|
||||
paddingTop: 60,
|
||||
paddingBottom: 24,
|
||||
marginBottom: 10,
|
||||
borderBottomLeftRadius: theme.borderRadius.xl,
|
||||
borderBottomRightRadius: theme.borderRadius.xl,
|
||||
},
|
||||
headerContent: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
},
|
||||
debugButton: {
|
||||
padding: 8,
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: theme.typography.fontSize["3xl"],
|
||||
fontWeight: theme.typography.fontWeight.bold,
|
||||
color: theme.colors.white,
|
||||
},
|
||||
headerSubtitle: {
|
||||
fontSize: theme.typography.fontSize.base,
|
||||
color: "rgba(255, 255, 255, 0.9)",
|
||||
marginTop: 4,
|
||||
},
|
||||
statsContainer: {
|
||||
flexDirection: "row",
|
||||
padding: 16,
|
||||
gap: 12,
|
||||
},
|
||||
statCard: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.colors.white,
|
||||
padding: 16,
|
||||
borderRadius: theme.borderRadius.xl,
|
||||
alignItems: "center",
|
||||
...theme.shadows.medium,
|
||||
borderWidth: 1,
|
||||
borderColor: "rgba(59, 130, 246, 0.1)",
|
||||
},
|
||||
statValue: {
|
||||
fontSize: theme.typography.fontSize["2xl"],
|
||||
fontWeight: theme.typography.fontWeight.bold,
|
||||
color: theme.colors.primary,
|
||||
marginBottom: 4,
|
||||
},
|
||||
statLabel: {
|
||||
fontSize: 12,
|
||||
color: "#6b7280",
|
||||
fontWeight: "500",
|
||||
},
|
||||
section: {
|
||||
padding: 20,
|
||||
paddingTop: 10,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: "600",
|
||||
color: "#374151",
|
||||
marginBottom: 12,
|
||||
},
|
||||
emptyState: {
|
||||
alignItems: "center",
|
||||
paddingVertical: 40,
|
||||
},
|
||||
emptyText: {
|
||||
fontSize: 16,
|
||||
fontWeight: "500",
|
||||
color: "#6b7280",
|
||||
marginTop: 12,
|
||||
},
|
||||
emptySubtext: {
|
||||
fontSize: 14,
|
||||
color: "#9ca3af",
|
||||
marginTop: 4,
|
||||
},
|
||||
footer: {
|
||||
height: 100,
|
||||
},
|
||||
fabContainer: {
|
||||
position: "absolute",
|
||||
right: 20,
|
||||
bottom: 110, // Adjusted for tab bar height
|
||||
},
|
||||
fab: {
|
||||
width: 64,
|
||||
height: 64,
|
||||
borderRadius: 32,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
...theme.shadows.glow,
|
||||
},
|
||||
});
|
||||
@@ -2,12 +2,26 @@ import { ClerkProvider, ClerkLoaded } from "@clerk/clerk-expo";
|
||||
import { Stack } from "expo-router";
|
||||
import * as SecureStore from "expo-secure-store";
|
||||
import { View, Text } from "react-native";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
console.log("========================================");
|
||||
console.log("🚀 _layout.tsx loaded at:", new Date().toISOString());
|
||||
console.log(
|
||||
"📦 EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY:",
|
||||
process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY,
|
||||
);
|
||||
console.log("========================================");
|
||||
|
||||
// Token cache for Clerk
|
||||
const tokenCache = {
|
||||
async getToken(key: string) {
|
||||
try {
|
||||
return SecureStore.getItemAsync(key);
|
||||
const value = await SecureStore.getItemAsync(key);
|
||||
console.log(`[TokenCache] Getting key: ${key}, exists: ${!!value}`);
|
||||
if (value && value.length > 50) {
|
||||
console.log(`[TokenCache] Value preview: ${value.substring(0, 50)}...`);
|
||||
}
|
||||
return value;
|
||||
} catch (err) {
|
||||
console.error("Error getting token:", err);
|
||||
return null;
|
||||
@@ -15,6 +29,10 @@ const tokenCache = {
|
||||
},
|
||||
async saveToken(key: string, value: string) {
|
||||
try {
|
||||
console.log(`[TokenCache] Saving key: ${key}`);
|
||||
if (value && value.length > 50) {
|
||||
console.log(`[TokenCache] Value preview: ${value.substring(0, 50)}...`);
|
||||
}
|
||||
return SecureStore.setItemAsync(key, value);
|
||||
} catch (err) {
|
||||
console.error("Error saving token:", err);
|
||||
@@ -24,8 +42,71 @@ const tokenCache = {
|
||||
|
||||
export default function RootLayout() {
|
||||
const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
|
||||
const [cacheCleared, setCacheCleared] = useState(false);
|
||||
|
||||
console.log(
|
||||
"🔑 [RootLayout] Component rendering with publishableKey:",
|
||||
publishableKey,
|
||||
);
|
||||
|
||||
// TEMPORARY: Clear Clerk cache on app start to fix instance mismatch
|
||||
useEffect(() => {
|
||||
console.log("⚡ [RootLayout] useEffect triggered - starting cache cleanup");
|
||||
|
||||
const clearOldClerkCache = async () => {
|
||||
try {
|
||||
console.log("🔍 [RootLayout] Checking for old Clerk tokens...");
|
||||
|
||||
const keysToCheck = [
|
||||
"__clerk_client_jwt",
|
||||
"__clerk_db_jwt",
|
||||
"__clerk_client_uat",
|
||||
"__clerk_session_id",
|
||||
"__clerk_refresh_token",
|
||||
"__clerk_session_jwt",
|
||||
];
|
||||
|
||||
for (const key of keysToCheck) {
|
||||
try {
|
||||
const value = await SecureStore.getItemAsync(key);
|
||||
if (value) {
|
||||
console.log(`📌 [RootLayout] Found token at ${key}`);
|
||||
console.log(
|
||||
`📝 [RootLayout] Token preview: ${value.substring(0, 80)}...`,
|
||||
);
|
||||
|
||||
// Check if it's from the old instance
|
||||
if (value.includes("pleasing-pheasant-20")) {
|
||||
console.log(`🗑️ [RootLayout] DELETING old token from ${key}`);
|
||||
await SecureStore.deleteItemAsync(key);
|
||||
} else if (value.includes("needed-elephant-64")) {
|
||||
console.log(
|
||||
`✅ [RootLayout] Token is from correct instance (needed-elephant-64)`,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
`⚠️ [RootLayout] Token doesn't match known instances`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`❌ [RootLayout] Error checking key ${key}:`, e);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("✅ [RootLayout] Old token cleanup complete");
|
||||
setCacheCleared(true);
|
||||
} catch (error) {
|
||||
console.error("❌ [RootLayout] Error clearing old cache:", error);
|
||||
setCacheCleared(true); // Continue anyway
|
||||
}
|
||||
};
|
||||
|
||||
clearOldClerkCache();
|
||||
}, []);
|
||||
|
||||
if (!publishableKey) {
|
||||
console.log("❌ [RootLayout] No publishable key found!");
|
||||
return (
|
||||
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
|
||||
<Text>Missing Clerk Publishable Key</Text>
|
||||
@@ -38,6 +119,23 @@ export default function RootLayout() {
|
||||
);
|
||||
}
|
||||
|
||||
if (!cacheCleared) {
|
||||
console.log("⏳ [RootLayout] Waiting for cache to clear...");
|
||||
return (
|
||||
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
|
||||
<Text>Clearing old authentication cache...</Text>
|
||||
<Text style={{ marginTop: 8, fontSize: 12, color: "#666" }}>
|
||||
Check the terminal for logs...
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
console.log(
|
||||
"🚀 [RootLayout] Rendering ClerkProvider with key:",
|
||||
publishableKey?.substring(0, 20) + "...",
|
||||
);
|
||||
|
||||
return (
|
||||
<ClerkProvider tokenCache={tokenCache} publishableKey={publishableKey}>
|
||||
<ClerkLoaded>
|
||||
|
||||
Reference in new issue
Block a user