fix: graceful fallback for network graph API errors + Map fixes
Network Graph: - Add graceful fallback when API returns 401 or other errors - Falls back to showing room participants as nodes - Prevents error spam in console for unauthenticated users Map Shape (linter changes): - Add isFetchingNearby state for loading indicator - Improve addAnnotation to accept name/color options - Add logging for Find Nearby debugging 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
34d7fd71a6
commit
27c82246ef
|
|
@ -156,10 +156,36 @@ export function useNetworkGraph(options: UseNetworkGraphOptions = {}): UseNetwor
|
||||||
|
|
||||||
// Fetch graph, optionally scoped to room
|
// Fetch graph, optionally scoped to room
|
||||||
let graph: NetworkGraph;
|
let graph: NetworkGraph;
|
||||||
if (participantIds.length > 0) {
|
try {
|
||||||
graph = await getRoomNetworkGraph(participantIds);
|
if (participantIds.length > 0) {
|
||||||
} else {
|
graph = await getRoomNetworkGraph(participantIds);
|
||||||
graph = await getMyNetworkGraph();
|
} else {
|
||||||
|
graph = await getMyNetworkGraph();
|
||||||
|
}
|
||||||
|
} catch (apiError: any) {
|
||||||
|
// If API call fails (e.g., 401 Unauthorized), fall back to showing room participants
|
||||||
|
console.warn('Network graph API failed, falling back to room participants:', apiError.message);
|
||||||
|
const fallbackNodes: GraphNode[] = roomParticipants.map(participant => ({
|
||||||
|
id: participant.id,
|
||||||
|
username: participant.username,
|
||||||
|
displayName: participant.username,
|
||||||
|
avatarColor: participant.color,
|
||||||
|
isInRoom: true,
|
||||||
|
roomPresenceColor: participant.color,
|
||||||
|
isCurrentUser: participant.id === session.username || participant.id === roomParticipants[0]?.id,
|
||||||
|
isAnonymous: false,
|
||||||
|
trustLevelTo: undefined,
|
||||||
|
trustLevelFrom: undefined,
|
||||||
|
}));
|
||||||
|
|
||||||
|
setState({
|
||||||
|
nodes: fallbackNodes,
|
||||||
|
edges: [],
|
||||||
|
myConnections: [],
|
||||||
|
isLoading: false,
|
||||||
|
error: null, // Don't show error to user - graceful degradation
|
||||||
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enrich nodes with room status, current user flag, and anonymous status
|
// Enrich nodes with room status, current user flag, and anonymous status
|
||||||
|
|
|
||||||
|
|
@ -338,6 +338,7 @@ function MapComponent({ shape, editor, isSelected }: { shape: IMapShape; editor:
|
||||||
const [searchResults, setSearchResults] = useState<any[]>([]);
|
const [searchResults, setSearchResults] = useState<any[]>([]);
|
||||||
const [_nearbyPlaces, setNearbyPlaces] = useState<any[]>([]);
|
const [_nearbyPlaces, setNearbyPlaces] = useState<any[]>([]);
|
||||||
const [isSearching, setIsSearching] = useState(false);
|
const [isSearching, setIsSearching] = useState(false);
|
||||||
|
const [isFetchingNearby, setIsFetchingNearby] = useState(false);
|
||||||
const [observingUser, setObservingUser] = useState<string | null>(null);
|
const [observingUser, setObservingUser] = useState<string | null>(null);
|
||||||
|
|
||||||
const styleKey = (shape.props.styleKey || 'voyager') as StyleKey;
|
const styleKey = (shape.props.styleKey || 'voyager') as StyleKey;
|
||||||
|
|
@ -578,12 +579,12 @@ function MapComponent({ shape, editor, isSelected }: { shape: IMapShape; editor:
|
||||||
// Actions
|
// Actions
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
const addAnnotation = useCallback((type: Annotation['type'], coordinates: Coordinate[]) => {
|
const addAnnotation = useCallback((type: Annotation['type'], coordinates: Coordinate[], options?: { name?: string; color?: string }) => {
|
||||||
const newAnnotation: Annotation = {
|
const newAnnotation: Annotation = {
|
||||||
id: `ann-${Date.now()}`,
|
id: `ann-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
||||||
type,
|
type,
|
||||||
name: `${type.charAt(0).toUpperCase() + type.slice(1)} ${shape.props.annotations.length + 1}`,
|
name: options?.name || `${type.charAt(0).toUpperCase() + type.slice(1)} ${shape.props.annotations.length + 1}`,
|
||||||
color: selectedColor,
|
color: options?.color || selectedColor,
|
||||||
visible: true,
|
visible: true,
|
||||||
coordinates,
|
coordinates,
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
|
|
@ -680,11 +681,16 @@ function MapComponent({ shape, editor, isSelected }: { shape: IMapShape; editor:
|
||||||
const findNearby = useCallback(async (category: typeof NEARBY_CATEGORIES[0]) => {
|
const findNearby = useCallback(async (category: typeof NEARBY_CATEGORIES[0]) => {
|
||||||
if (!mapRef.current || !isMountedRef.current) return;
|
if (!mapRef.current || !isMountedRef.current) return;
|
||||||
|
|
||||||
|
console.log('🗺️ findNearby called for category:', category.label);
|
||||||
|
setIsFetchingNearby(true);
|
||||||
|
|
||||||
let bounds;
|
let bounds;
|
||||||
try {
|
try {
|
||||||
bounds = mapRef.current.getBounds();
|
bounds = mapRef.current.getBounds();
|
||||||
|
console.log('🗺️ Map bounds:', bounds.toString());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Map may have been destroyed
|
console.error('🗺️ Error getting bounds:', err);
|
||||||
|
setIsFetchingNearby(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -696,15 +702,21 @@ function MapComponent({ shape, editor, isSelected }: { shape: IMapShape; editor:
|
||||||
);
|
);
|
||||||
out body 10;
|
out body 10;
|
||||||
`;
|
`;
|
||||||
|
console.log('🗺️ Overpass query:', query);
|
||||||
|
|
||||||
const response = await fetch('https://overpass-api.de/api/interpreter', {
|
const response = await fetch('https://overpass-api.de/api/interpreter', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: query,
|
body: query,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isMountedRef.current) return;
|
if (!isMountedRef.current) {
|
||||||
|
setIsFetchingNearby(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🗺️ Overpass response status:', response.status);
|
||||||
const data = await response.json() as { elements: { id: number; lat: number; lon: number; tags?: { name?: string; amenity?: string } }[] };
|
const data = await response.json() as { elements: { id: number; lat: number; lon: number; tags?: { name?: string; amenity?: string } }[] };
|
||||||
|
console.log('🗺️ Found', data.elements.length, 'places');
|
||||||
|
|
||||||
const places = data.elements.slice(0, 10).map((el) => ({
|
const places = data.elements.slice(0, 10).map((el) => ({
|
||||||
id: el.id,
|
id: el.id,
|
||||||
|
|
@ -715,17 +727,26 @@ function MapComponent({ shape, editor, isSelected }: { shape: IMapShape; editor:
|
||||||
color: category.color,
|
color: category.color,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (!isMountedRef.current) return;
|
if (!isMountedRef.current) {
|
||||||
|
setIsFetchingNearby(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
setNearbyPlaces(places);
|
setNearbyPlaces(places);
|
||||||
|
|
||||||
// Add markers for nearby places
|
// Add markers for nearby places
|
||||||
|
console.log('🗺️ Adding', places.length, 'markers');
|
||||||
places.forEach((place: any) => {
|
places.forEach((place: any) => {
|
||||||
if (isMountedRef.current) {
|
if (isMountedRef.current) {
|
||||||
addAnnotation('marker', [{ lat: place.lat, lng: place.lng }]);
|
addAnnotation('marker', [{ lat: place.lat, lng: place.lng }], {
|
||||||
|
name: place.name,
|
||||||
|
color: place.color,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
setIsFetchingNearby(false);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Find nearby error:', err);
|
console.error('🗺️ Find nearby error:', err);
|
||||||
|
setIsFetchingNearby(false);
|
||||||
}
|
}
|
||||||
}, [addAnnotation]);
|
}, [addAnnotation]);
|
||||||
|
|
||||||
|
|
@ -972,19 +993,21 @@ function MapComponent({ shape, editor, isSelected }: { shape: IMapShape; editor:
|
||||||
|
|
||||||
{/* Find Nearby */}
|
{/* Find Nearby */}
|
||||||
<div style={styles.section}>
|
<div style={styles.section}>
|
||||||
<div style={styles.sectionTitle}>Find nearby</div>
|
<div style={styles.sectionTitle}>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 4 }}>
|
Find nearby {isFetchingNearby && <span style={{ marginLeft: 8, fontSize: 12 }}>⏳</span>}
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 4, opacity: isFetchingNearby ? 0.5 : 1 }}>
|
||||||
{NEARBY_CATEGORIES.map((cat) => (
|
{NEARBY_CATEGORIES.map((cat) => (
|
||||||
<div
|
<div
|
||||||
key={cat.key}
|
key={cat.key}
|
||||||
className="mapus-category"
|
className="mapus-category"
|
||||||
onClick={() => findNearby(cat)}
|
onClick={() => !isFetchingNearby && findNearby(cat)}
|
||||||
onPointerDown={stopPropagation}
|
onPointerDown={stopPropagation}
|
||||||
style={{
|
style={{
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
padding: '10px 4px',
|
padding: '10px 4px',
|
||||||
borderRadius: 6,
|
borderRadius: 6,
|
||||||
cursor: 'pointer',
|
cursor: isFetchingNearby ? 'wait' : 'pointer',
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: '#626C72',
|
color: '#626C72',
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue