Fix remaining PRD tasks: constants, AbortController, BLE cleanup, displayName fallback
- Add ONLINE_THRESHOLD_MS constant for magic number (30 min threshold) - Add AbortController to cancel requests when screen loses focus - Register BLE cleanup callback for logout in BLEContext - Add 'Unknown User' fallback for displayName in all locations - Add null safety guard in handleBeneficiaryPress
This commit is contained in:
parent
b5014fa680
commit
d499d9d62a
8
PRD.md
8
PRD.md
@ -141,10 +141,10 @@
|
||||
- [x] Нет hardcoded credentials в коде
|
||||
- [x] BLE соединения отключаются при logout
|
||||
- [x] WiFi пароли зашифрованы
|
||||
- [ ] Нет race conditions при быстром переключении
|
||||
- [ ] Console.logs удалены
|
||||
- [ ] Avatar caching исправлен
|
||||
- [ ] Role-based доступ работает корректно
|
||||
- [x] Нет race conditions при быстром переключении
|
||||
- [x] Console.logs удалены
|
||||
- [x] Avatar caching исправлен
|
||||
- [x] Role-based доступ работает корректно
|
||||
|
||||
## ✅ Статус
|
||||
|
||||
|
||||
@ -202,21 +202,33 @@ export default function HomeScreen() {
|
||||
|
||||
|
||||
// Load beneficiaries when screen is focused (after editing profile, etc.)
|
||||
// Use AbortController to cancel pending requests when screen loses focus
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
loadBeneficiaries();
|
||||
const abortController = new AbortController();
|
||||
loadBeneficiaries(abortController.signal);
|
||||
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, [])
|
||||
);
|
||||
|
||||
const loadBeneficiaries = async () => {
|
||||
const loadBeneficiaries = async (signal?: AbortSignal) => {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const onboardingCompleted = await api.isOnboardingCompleted();
|
||||
|
||||
// Check if request was aborted
|
||||
if (signal?.aborted) return;
|
||||
|
||||
// Get beneficiaries from WellNuo API
|
||||
const response = await api.getAllBeneficiaries();
|
||||
|
||||
// Check if request was aborted before updating state
|
||||
if (signal?.aborted) return;
|
||||
|
||||
if (response.ok && response.data) {
|
||||
setBeneficiaries(response.data);
|
||||
|
||||
@ -244,11 +256,17 @@ export default function HomeScreen() {
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
// Ignore abort errors
|
||||
if (err instanceof Error && err.name === 'AbortError') return;
|
||||
if (signal?.aborted) return;
|
||||
|
||||
setError('Failed to load beneficiaries');
|
||||
setBeneficiaries([]);
|
||||
} finally {
|
||||
if (!signal?.aborted) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleRefresh = async () => {
|
||||
@ -258,6 +276,9 @@ export default function HomeScreen() {
|
||||
};
|
||||
|
||||
const handleBeneficiaryPress = (beneficiary: Beneficiary) => {
|
||||
// Null safety guard
|
||||
if (!beneficiary?.id) return;
|
||||
|
||||
setCurrentBeneficiary(beneficiary);
|
||||
// Always go to beneficiary detail page (which includes MockDashboard)
|
||||
router.push(`/(tabs)/beneficiaries/${beneficiary.id}`);
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
// BLE Context - Global state for Bluetooth management
|
||||
|
||||
import React, { createContext, useContext, useState, useCallback, ReactNode } from 'react';
|
||||
import React, { createContext, useContext, useState, useCallback, useEffect, ReactNode } from 'react';
|
||||
import { bleManager, WPDevice, WiFiNetwork, WiFiStatus, isBLEAvailable } from '@/services/ble';
|
||||
import { setOnLogoutBLECleanupCallback } from '@/services/api';
|
||||
|
||||
interface BLEContextType {
|
||||
// State
|
||||
@ -161,6 +162,15 @@ export function BLEProvider({ children }: { children: ReactNode }) {
|
||||
}
|
||||
}, [isScanning, stopScan]);
|
||||
|
||||
// Register BLE cleanup callback for logout
|
||||
useEffect(() => {
|
||||
setOnLogoutBLECleanupCallback(cleanupBLE);
|
||||
|
||||
return () => {
|
||||
setOnLogoutBLECleanupCallback(null);
|
||||
};
|
||||
}, [cleanupBLE]);
|
||||
|
||||
const value: BLEContextType = {
|
||||
foundDevices,
|
||||
isScanning,
|
||||
|
||||
@ -23,6 +23,9 @@ export function setOnLogoutBLECleanupCallback(callback: (() => Promise<void>) |
|
||||
const API_BASE_URL = 'https://eluxnetworks.net/function/well-api/api';
|
||||
const CLIENT_ID = 'MA_001';
|
||||
|
||||
// Threshold for considering a beneficiary "online" (30 minutes in milliseconds)
|
||||
const ONLINE_THRESHOLD_MS = 30 * 60 * 1000;
|
||||
|
||||
// WellNuo Backend API (our own API for auth, OTP, etc.)
|
||||
// TODO: Update to production URL when deployed
|
||||
const WELLNUO_API_URL = 'https://wellnuo.smartlaunchhub.com/api';
|
||||
@ -639,7 +642,7 @@ class ApiService {
|
||||
const data = response.data;
|
||||
// Determine if beneficiary is "online" based on last_detected_time
|
||||
const lastDetected = data.last_detected_time ? new Date(data.last_detected_time) : null;
|
||||
const isRecent = lastDetected && (Date.now() - lastDetected.getTime()) < 30 * 60 * 1000; // 30 min
|
||||
const isRecent = lastDetected && (Date.now() - lastDetected.getTime()) < ONLINE_THRESHOLD_MS;
|
||||
|
||||
const deploymentId = parseInt(data.deployment_id, 10);
|
||||
const beneficiary: Beneficiary = {
|
||||
@ -732,7 +735,7 @@ class ApiService {
|
||||
id: item.id,
|
||||
name: item.originalName || item.name || item.email, // Original name from server
|
||||
customName: item.customName || null, // User's custom name for this beneficiary
|
||||
displayName: item.displayName || item.customName || item.name || item.email, // Server-provided displayName
|
||||
displayName: item.displayName || item.customName || item.name || item.email || 'Unknown User', // Server-provided displayName
|
||||
originalName: item.originalName || item.name, // Original name from beneficiaries table
|
||||
avatar: bustImageCache(item.avatarUrl) || undefined, // Use uploaded avatar from server with cache-busting
|
||||
status: 'offline' as const,
|
||||
@ -778,7 +781,7 @@ class ApiService {
|
||||
id: data.id,
|
||||
name: data.originalName || data.name || data.email, // Original name from server
|
||||
customName: data.customName || null, // User's custom name for this beneficiary
|
||||
displayName: data.displayName || data.customName || data.name || data.email, // Server-provided displayName
|
||||
displayName: data.displayName || data.customName || data.name || data.email || 'Unknown User', // Server-provided displayName
|
||||
originalName: data.originalName || data.name, // Original name from beneficiaries table
|
||||
avatar: bustImageCache(data.avatarUrl) || undefined, // Cache-bust avatar URL
|
||||
status: 'offline' as const,
|
||||
@ -835,7 +838,7 @@ class ApiService {
|
||||
const beneficiary: Beneficiary = {
|
||||
id: data.beneficiary.id,
|
||||
name: data.beneficiary.name || data.beneficiary.email,
|
||||
displayName: data.beneficiary.displayName || data.beneficiary.name || data.beneficiary.email,
|
||||
displayName: data.beneficiary.displayName || data.beneficiary.name || data.beneficiary.email || 'Unknown User',
|
||||
email: data.beneficiary.email,
|
||||
status: 'offline' as const,
|
||||
};
|
||||
@ -877,7 +880,7 @@ class ApiService {
|
||||
const beneficiary: Beneficiary = {
|
||||
id: result.beneficiary.id,
|
||||
name: result.beneficiary.name || '',
|
||||
displayName: result.beneficiary.name || '', // For UI display
|
||||
displayName: result.beneficiary.name || 'Unknown User', // For UI display
|
||||
status: 'offline' as const,
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user