Skip to content

Commit 2da8d69

Browse files
author
Midoriya-w
committed
fix: correct AuthContextType definition to match provider implementation
1 parent 98a48df commit 2da8d69

1 file changed

Lines changed: 105 additions & 36 deletions

File tree

Lines changed: 105 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,155 @@
1-
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
1+
import React, { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react';
22
import AsyncStorage from '@react-native-async-storage/async-storage';
3+
import { get } from '../services/api';
4+
import { DEMO_TOKEN } from '../services/api';
35

4-
const TOKEN_KEY = 'devcard_auth_token';
5-
const API_BASE_URL = process.env.EXPO_PUBLIC_API_URL ?? 'http://localhost:3000';
6+
// ── Storage Keys ──────────────────────────────────────────────────────────
67

7-
type User = {
8+
const TOKEN_KEY = 'devcard.auth.token';
9+
const FIRST_LAUNCH_KEY = 'devcard.firstLaunch';
10+
11+
// ── Types ────────────────────────────────────────────────────────────
12+
13+
interface User {
814
id: string;
9-
username: string;
1015
email: string;
11-
};
16+
username: string;
17+
displayName: string;
18+
bio: string | null;
19+
pronouns: string | null;
20+
role: string | null;
21+
company: string | null;
22+
avatarUrl: string | null;
23+
accentColor: string;
24+
defaultCardId: string | null;
25+
}
1226

13-
type AuthContextType = {
27+
interface AuthContextType {
1428
user: User | null;
1529
token: string | null;
30+
isAuthenticated: boolean;
1631
isLoading: boolean;
32+
isFirstLaunch: boolean;
1733
login: (token: string) => Promise<void>;
1834
logout: () => Promise<void>;
19-
};
35+
refreshUser: () => Promise<void>;
36+
enterDemoMode: () => Promise<void>;
37+
}
38+
39+
// ── Context ───────────────────────────────────────────────────────────
2040

2141
const AuthContext = createContext<AuthContextType | undefined>(undefined);
2242

2343
export function AuthProvider({ children }: { children: ReactNode }) {
2444
const [user, setUser] = useState<User | null>(null);
2545
const [token, setToken] = useState<string | null>(null);
2646
const [isLoading, setIsLoading] = useState(true);
47+
const [isFirstLaunch, setIsFirstLaunch] = useState(false);
48+
49+
// ── Hydrate token from AsyncStorage on mount ──
2750

2851
useEffect(() => {
29-
const loadToken = async () => {
52+
const hydrate = async () => {
3053
try {
31-
const savedToken = await AsyncStorage.getItem(TOKEN_KEY);
32-
if (savedToken) {
33-
setToken(savedToken);
34-
const res = await fetch(`${API_BASE_URL}/api/profiles/me`, {
35-
headers: { Authorization: `Bearer ${savedToken}` },
36-
});
37-
if (res.ok) {
38-
const userData = await res.json();
54+
const [storedToken, launchFlag] = await Promise.all([
55+
AsyncStorage.getItem(TOKEN_KEY),
56+
AsyncStorage.getItem(FIRST_LAUNCH_KEY),
57+
]);
58+
59+
if (launchFlag === null) {
60+
setIsFirstLaunch(true);
61+
await AsyncStorage.setItem(FIRST_LAUNCH_KEY, 'false');
62+
}
63+
64+
if (storedToken) {
65+
setToken(storedToken);
66+
// Validate token by fetching profile
67+
const userData = await get<User>('/api/profiles/me', storedToken).catch(() => null);
68+
if (userData) {
3969
setUser(userData);
4070
} else {
71+
// Token expired or invalid — clear it
4172
await AsyncStorage.removeItem(TOKEN_KEY);
73+
setToken(null);
4274
}
4375
}
44-
} catch (err) {
45-
console.error('Failed to restore session:', err);
76+
} catch (error) {
77+
console.error('Auth hydration failed:', error);
4678
} finally {
4779
setIsLoading(false);
4880
}
4981
};
50-
loadToken();
82+
83+
hydrate();
5184
}, []);
5285

53-
const login = async (newToken: string) => {
86+
// ── Login ──
87+
88+
const login = useCallback(async (newToken: string) => {
5489
setToken(newToken);
55-
await AsyncStorage.setItem(TOKEN_KEY, newToken);
5690
try {
57-
const res = await fetch(`${API_BASE_URL}/api/profiles/me`, {
58-
headers: { Authorization: `Bearer ${newToken}` },
59-
});
60-
if (res.ok) {
61-
const userData = await res.json();
91+
await AsyncStorage.setItem(TOKEN_KEY, newToken);
92+
const userData = await get<User>('/api/profiles/me', newToken).catch(() => null);
93+
if (userData) {
6294
setUser(userData);
6395
}
64-
} catch (err) {
65-
console.error('Failed to fetch user:', err);
96+
} catch (error) {
97+
console.error('Failed to persist token or fetch user:', error);
6698
}
67-
};
99+
}, []);
100+
101+
// ── Logout ──
68102

69-
const logout = async () => {
103+
const logout = useCallback(async () => {
70104
setToken(null);
71105
setUser(null);
72-
await AsyncStorage.removeItem(TOKEN_KEY);
73-
};
106+
try {
107+
await AsyncStorage.removeItem(TOKEN_KEY);
108+
} catch (error) {
109+
console.error('Failed to clear stored token:', error);
110+
}
111+
}, []);
112+
113+
// ── Refresh User ──
114+
115+
const refreshUser = useCallback(async () => {
116+
if (!token) return;
117+
try {
118+
const userData = await get<User>('/api/profiles/me', token).catch(() => null);
119+
if (userData) {
120+
setUser(userData);
121+
}
122+
} catch (error) {
123+
console.error('Failed to refresh user:', error);
124+
}
125+
}, [token]);
126+
127+
const enterDemoMode = useCallback(async () => {
128+
await login(DEMO_TOKEN);
129+
}, [login]);
74130

75131
return (
76-
<AuthContext.Provider value={{ user, token, isLoading, login, logout }}>
132+
<AuthContext.Provider
133+
value={{
134+
user,
135+
token,
136+
isAuthenticated: !!token && !!user,
137+
isLoading,
138+
isFirstLaunch,
139+
login,
140+
logout,
141+
refreshUser,
142+
enterDemoMode,
143+
}}>
77144
{children}
78145
</AuthContext.Provider>
79146
);
80147
}
81148

82-
export function useAuth() {
149+
export function useAuth(): AuthContextType {
83150
const context = useContext(AuthContext);
84-
if (!context) throw new Error('useAuth must be used within AuthProvider');
151+
if (!context) {
152+
throw new Error('useAuth must be used within an AuthProvider');
153+
}
85154
return context;
86155
}

0 commit comments

Comments
 (0)