fix: Comprehensive authentication and UI fixes
Authentication & Token Management: - Add deviceId to token refresh flow (backend requires both refreshToken and deviceId) - Fix React Strict Mode token clearing race condition with retry logic - Improve AuthContext to handle all token state combinations properly - Store deviceId in localStorage alongside tokens UI/UX Improvements: - Remove deprecated legacyBehavior from Next.js Link components - Update primary theme color to WCAG AA compliant #7c3aed - Fix nested button error in TabBar voice navigation - Fix invalid Tabs value error in DynamicChildDashboard Multi-Child Dashboard: - Load all children into Redux store properly - Fetch metrics for all children, not just selected one - Remove mock data to prevent unauthorized API calls 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -76,13 +76,58 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||
|
||||
try {
|
||||
const accessToken = tokenStorage.getAccessToken();
|
||||
if (!accessToken) {
|
||||
const refreshToken = tokenStorage.getRefreshToken();
|
||||
|
||||
console.log('[AuthContext] checkAuth - tokens present:', {
|
||||
hasAccess: !!accessToken,
|
||||
hasRefresh: !!refreshToken
|
||||
});
|
||||
|
||||
if (!accessToken && !refreshToken) {
|
||||
console.log('[AuthContext] No tokens found, user not authenticated');
|
||||
setUser(null);
|
||||
setToken(null);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set token in state
|
||||
setToken(accessToken);
|
||||
// If we only have refresh token but no access token, don't make /me call
|
||||
// The axios interceptor will handle getting a new access token when needed
|
||||
if (!accessToken && refreshToken) {
|
||||
console.log('[AuthContext] Only refresh token present, skipping /me call');
|
||||
setUser(null);
|
||||
setToken(null);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we only have access token but no refresh token, it might be a timing issue
|
||||
// during login (React Strict Mode). Give it a moment and check again.
|
||||
if (accessToken && !refreshToken) {
|
||||
console.log('[AuthContext] Only access token present, checking if this is temporary...');
|
||||
|
||||
// Wait a tiny bit and check again (for React Strict Mode race condition)
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
const refreshTokenRetry = tokenStorage.getRefreshToken();
|
||||
|
||||
if (!refreshTokenRetry) {
|
||||
console.log('[AuthContext] Still no refresh token after retry, clearing invalid state');
|
||||
tokenStorage.clearTokens();
|
||||
setUser(null);
|
||||
setToken(null);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
} else {
|
||||
console.log('[AuthContext] Refresh token found on retry, proceeding with auth check');
|
||||
// Update local variable for the rest of the function
|
||||
}
|
||||
}
|
||||
|
||||
// At this point we have both tokens - proceed with auth check
|
||||
// Set token in state if we have one
|
||||
if (accessToken) {
|
||||
setToken(accessToken);
|
||||
}
|
||||
|
||||
const response = await apiClient.get('/api/v1/auth/me');
|
||||
|
||||
@@ -104,9 +149,28 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||
throw new Error('Invalid response structure');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Auth check failed:', error);
|
||||
// Only clear tokens if it's an actual auth error (401, 403)
|
||||
if (error?.response?.status === 401 || error?.response?.status === 403) {
|
||||
console.error('[AuthContext] Auth check failed:', error);
|
||||
// Don't clear tokens on 401 during initial auth check
|
||||
// The axios interceptor in client.ts will handle token refresh automatically
|
||||
// Only clear tokens if the error is NOT a 401 (e.g., network error, 403, etc.)
|
||||
// Or if there's no refresh token available (meaning refresh already failed)
|
||||
const hasRefreshToken = tokenStorage.getRefreshToken();
|
||||
|
||||
if (!hasRefreshToken) {
|
||||
// No refresh token means we can't recover - clear everything
|
||||
console.log('[AuthContext] No refresh token available, clearing auth state');
|
||||
tokenStorage.clearTokens();
|
||||
setUser(null);
|
||||
setToken(null);
|
||||
} else if (error?.response?.status === 401) {
|
||||
// 401 with refresh token - let axios interceptor handle refresh
|
||||
console.log('[AuthContext] 401 error but refresh token exists, letting axios interceptor handle refresh');
|
||||
// Don't clear tokens - the axios interceptor will attempt refresh
|
||||
setUser(null);
|
||||
setToken(null);
|
||||
} else if (error?.response?.status === 403) {
|
||||
// 403 means forbidden - clear tokens
|
||||
console.log('[AuthContext] 403 Forbidden, clearing auth state');
|
||||
tokenStorage.clearTokens();
|
||||
setUser(null);
|
||||
setToken(null);
|
||||
@@ -118,8 +182,9 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||
|
||||
const login = async (credentials: LoginCredentials) => {
|
||||
try {
|
||||
const deviceId = generateDeviceFingerprint();
|
||||
const deviceInfo = {
|
||||
deviceId: generateDeviceFingerprint(),
|
||||
deviceId,
|
||||
platform: 'web',
|
||||
model: navigator.userAgent,
|
||||
osVersion: navigator.platform,
|
||||
@@ -141,7 +206,8 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||
eulaVersion: userData.eulaVersion,
|
||||
});
|
||||
|
||||
tokenStorage.setTokens(tokens.accessToken, tokens.refreshToken);
|
||||
// Store tokens and deviceId
|
||||
tokenStorage.setTokens(tokens.accessToken, tokens.refreshToken, deviceId);
|
||||
setToken(tokens.accessToken);
|
||||
setUser(userData);
|
||||
|
||||
@@ -154,8 +220,9 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||
|
||||
const register = async (data: RegisterData) => {
|
||||
try {
|
||||
const deviceId = generateDeviceFingerprint();
|
||||
const deviceInfo = {
|
||||
deviceId: generateDeviceFingerprint(),
|
||||
deviceId,
|
||||
platform: 'web',
|
||||
model: navigator.userAgent,
|
||||
osVersion: navigator.platform,
|
||||
@@ -205,7 +272,7 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||
}] : [],
|
||||
};
|
||||
|
||||
tokenStorage.setTokens(accessToken, refreshToken);
|
||||
tokenStorage.setTokens(accessToken, refreshToken, deviceId);
|
||||
setToken(accessToken);
|
||||
setUser(userWithFamily);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user