From 3ee7a6e918541599e3bed26ec6a16713657adf40 Mon Sep 17 00:00:00 2001 From: Mvjtb <1001.25854794.ucla@gmail.com> Date: Tue, 20 May 2025 20:55:07 -0400 Subject: [PATCH 1/7] Refactor NotificationsScreen to improve notification handling and UI updates --- src/screens/NotificationsScreen.tsx | 263 +++++++++++----------------- 1 file changed, 106 insertions(+), 157 deletions(-) diff --git a/src/screens/NotificationsScreen.tsx b/src/screens/NotificationsScreen.tsx index 382b5f6..009bd7f 100644 --- a/src/screens/NotificationsScreen.tsx +++ b/src/screens/NotificationsScreen.tsx @@ -9,7 +9,7 @@ import { ImageSourcePropType, TouchableOpacity, } from 'react-native'; -import { useNavigation } from '@react-navigation/native'; +import { useNavigation, useFocusEffect } from '@react-navigation/native'; import { formatDistanceToNow, parseISO, @@ -21,8 +21,6 @@ import { ChevronLeftIcon } from 'react-native-heroicons/solid'; import TopBar from '../components/TopBar'; import PoppinsText from '../components/PoppinsText'; import Alert from '../components/Alerts'; -// Importamos y casteamos explicitamente el ícono por defecto -import NotificationIconAsset from '../assets/images/favicon.png'; import { Colors, FontSizes } from '../styles/theme'; import { NotificationService } from '../services/notifications'; import { getUserIdFromSecureStore } from '../helper/jwtHelper'; @@ -33,43 +31,39 @@ import approvedImg from '../assets/images/notifications/image.jpg'; import canceledImg from '../assets/images/notifications/w.jpg'; import readyForPickupImg from '../assets/images/notifications/e.jpg'; import deliveryImg from '../assets/images/notifications/m.jpg'; +import defaultIcon from '../assets/images/favicon.png'; -// Mapeo de iconos por estado -const notificationIcons: Record = { - completed: completedImg as unknown as ImageSourcePropType, - in_progress: inProgressImg as unknown as ImageSourcePropType, - approved: approvedImg as unknown as ImageSourcePropType, - canceled: canceledImg as unknown as ImageSourcePropType, - ready_for_pickup: readyForPickupImg as unknown as ImageSourcePropType, - delivery: deliveryImg as unknown as ImageSourcePropType, +type NotificationItem = { + id: string; + title: string; + message: string; + createdAt: string; + isRead: boolean; + orderId: string; + status: string; }; -// Ícono por defecto, casteado a ImageSourcePropType -const NotificationIcon = - NotificationIconAsset as unknown as ImageSourcePropType; - -// Función auxiliar para extraer orderId del mensaje -const extractOrderIdFromMessage = (message: string): string | null => { - const match = message.match(/The order (\S+) has been updated to/i); - return match ? match[1] : null; +const notificationIcons: Record = { + completed: completedImg as ImageSourcePropType, + in_progress: inProgressImg as ImageSourcePropType, + approved: approvedImg as ImageSourcePropType, + canceled: canceledImg as ImageSourcePropType, + ready_for_pickup: readyForPickupImg as ImageSourcePropType, + delivery: deliveryImg as ImageSourcePropType, }; +const defaultIconSource = defaultIcon as ImageSourcePropType; + export default function NotificationsScreen() { const navigation = useNavigation(); const [notificationsList, setNotificationsList] = useState< - Array<{ - id: string; - title: string; - message: string; - createdAt: string; - isRead: boolean; - orderId: string; - }> + NotificationItem[] >([]); const [loading, setLoading] = useState(true); const [showErrorAlert, setShowErrorAlert] = useState(false); const [errorMessage, setErrorMessage] = useState(''); + // Carga inicial de notificaciones useEffect(() => { const fetchNotifications = async () => { setLoading(true); @@ -79,33 +73,28 @@ export default function NotificationsScreen() { if (!token) throw new Error('No se pudo obtener el token de usuario.'); const res = await NotificationService.getNotifications(); - if (res.success && Array.isArray(res.data)) { - console.log('Raw notifications payload:', res.data); + if (!res.success || !Array.isArray(res.data)) { + const errorMsg = + !res.success && 'error' in res && res.error + ? res.error + : 'Respuesta inesperada del servidor'; + throw new Error(errorMsg); + } - setNotificationsList( - res.data.map((nt) => { - const rawOrderId = - nt.orderId || - (nt as { order_id?: string }).order_id || // Specify type for `order_id` - (nt.data && - ((nt.data as { orderId?: string }).orderId || // Specify type for `data.orderId` - (nt.data as { order_id?: string }).order_id)) || // Specify type for `data.order_id` - extractOrderIdFromMessage(nt.message) || - ''; + const items = res.data.map((nt: Record) => { + const order = (nt.order || {}) as Record; + return { + id: nt.id as string, + title: nt.title as string, + message: (nt.message as string).trim(), + createdAt: nt.createdAt as string, + isRead: !!nt.isRead, + orderId: order.id as string, + status: order.status as string, + }; + }); - return { - id: nt.id, - title: nt.title, - message: nt.message, - createdAt: nt.createdAt, - isRead: !!nt.isRead, - orderId: rawOrderId, - }; - }), - ); - } else if (!res.success) { - throw new Error(res.error || 'Respuesta inesperada del servidor'); - } + setNotificationsList(items); } catch (err: unknown) { setErrorMessage( err instanceof Error ? err.message : 'Error desconocido', @@ -115,37 +104,42 @@ export default function NotificationsScreen() { setLoading(false); } }; + fetchNotifications(); }, []); - const handlePressNotification = useCallback( - async (nt: (typeof notificationsList)[0]) => { - console.log(`order ID: ${nt.orderId}`, nt); + // Marca todas como leídas al enfocar o desenfocar la pantalla + useFocusEffect( + useCallback(() => { + const markAllRead = async () => { + try { + const token = await getUserIdFromSecureStore(); + if (!token) return; - if (nt.isRead) return; + // Filtrar solo las no leídas + const toMark = notificationsList.filter((n) => !n.isRead); + if (toMark.length === 0) return; - try { - const token = await getUserIdFromSecureStore(); - if (!token) throw new Error('No se pudo obtener el token de usuario.'); - if (nt.orderId) { - await NotificationService.markAsRead(nt.orderId, token); + // Llamar al servicio en paralelo + await Promise.all( + toMark.map((n) => NotificationService.markAsRead(n.orderId, token)), + ); + + // Actualizar estado local setNotificationsList((prev) => - prev.map((n) => (n.id === nt.id ? { ...n, isRead: true } : n)), + prev.map((n) => ({ ...n, isRead: true })), + ); + } catch (e) { + console.warn( + 'Error al marcar todas las notificaciones como leídas:', + e, ); - } else { - throw new Error('ID de notificación no válido.'); } - } catch (err) { - const msg = - err instanceof Error - ? err.message - : 'Error desconocido al marcar la notificación como leída.'; - console.error(`Error marcando notificación ${nt.id}:`, msg); - setErrorMessage(msg); - setShowErrorAlert(true); - } - }, - [], + }; + + markAllRead(); + // no necesitamos cleanup + }, [notificationsList]), ); const formatRelativeDate = (dateString: string) => { @@ -160,48 +154,8 @@ export default function NotificationsScreen() { return format(date, 'yyyy-MM-dd', { locale: es }); }; - const extractStatusKey = (message: string): string | null => { - const match = message.match( - /The order \S+ has been updated to ([\w_]+)\.?/i, - ); - return match ? match[1].toLowerCase() : null; - }; - - const translateMessage = (message: string) => { - const match = message.match( - /The order (\S+) has been updated to ([\w_]+)\.?/i, - ); - if (!match) return message; - const [, orderId, rawStatus] = match; - const key = rawStatus.replace(/_/g, ' ').toLowerCase(); - const map: Record = { - completed: 'completada', - 'in progress': 'en progreso', - approved: 'aprobada', - canceled: 'cancelada', - 'ready for pickup': 'lista para recoger', - delivery: 'en entrega', - }; - return `La orden ${orderId} ha sido actualizada a ${map[key] || key}`; - }; - - const getTranslatedTitle = (nt: { title: string; message: string }) => { - const key = extractStatusKey(nt.message); - if (!key) return nt.title; - const map: Record = { - completed: 'Pedido Completado', - in_progress: 'Pedido en Progreso', - approved: 'Orden Aprobada', - canceled: 'Orden Cancelada', - ready_for_pickup: 'Pedido Listo para Recoger', - delivery: 'Pedido en Entrega', - }; - return map[key] || nt.title; - }; - - const getNotificationIcon = (message: string): ImageSourcePropType => { - const key = extractStatusKey(message); - return (key && notificationIcons[key]) || NotificationIcon; + const getNotificationIcon = (status: string): ImageSourcePropType => { + return notificationIcons[status] || defaultIconSource; }; return ( @@ -209,27 +163,15 @@ export default function NotificationsScreen() { navigation.goBack()} - style={{ - paddingHorizontal: 10, - marginBottom: -4, - flexDirection: 'row', - alignSelf: 'flex-start', - }} + style={styles.backButton} > - + Volver @@ -263,33 +205,24 @@ export default function NotificationsScreen() { ) : ( {notificationsList.map((nt) => ( - handlePressNotification(nt)} - activeOpacity={0.7} + style={[styles.item, !nt.isRead && styles.unreadBackground]} > - - - - - - {getTranslatedTitle(nt)} - - - {formatRelativeDate(nt.createdAt)} - - - - {translateMessage(nt.message)} + + + + {nt.title} + + {formatRelativeDate(nt.createdAt)} + {nt.message} - + ))} )} @@ -302,6 +235,18 @@ const styles = StyleSheet.create({ flex: 1, backgroundColor: Colors.bgColor, }, + backButton: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 10, + marginTop: 8, + marginBottom: -4, + }, + backText: { + fontSize: FontSizes.b1.size, + lineHeight: FontSizes.b1.lineHeight, + color: Colors.primary, + }, alertContainer: { position: 'absolute', top: 20, @@ -313,19 +258,23 @@ const styles = StyleSheet.create({ header: { flexDirection: 'row', paddingTop: 16, + paddingHorizontal: 20, }, title: { fontSize: FontSizes.s1.size, color: Colors.primary, - marginLeft: 20, }, - loader: { marginTop: 40 }, + loader: { + marginTop: 40, + }, emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', }, - listContainer: { paddingVertical: 10 }, + listContainer: { + paddingVertical: 10, + }, item: { flexDirection: 'row', alignItems: 'flex-start', From bb94a484d497ffed9d8514e3e71d8ff3f21aa7dc Mon Sep 17 00:00:00 2001 From: Mvjtb <1001.25854794.ucla@gmail.com> Date: Tue, 20 May 2025 21:43:21 -0400 Subject: [PATCH 2/7] Refactor NotificationsScreen to use SVG icons instead of images; improve notification rendering and cleanup code --- src/assets/images/notifications/e.jpg | Bin 3343 -> 0 bytes src/assets/images/notifications/e.svg | 9 ++ src/assets/images/notifications/f.jpg | Bin 3280 -> 0 bytes src/assets/images/notifications/f.svg | 9 ++ src/assets/images/notifications/image.jpg | Bin 3426 -> 0 bytes src/assets/images/notifications/image.svg | 9 ++ src/assets/images/notifications/m.jpg | Bin 3975 -> 0 bytes src/assets/images/notifications/m.svg | 9 ++ src/assets/images/notifications/r.jpg | Bin 2772 -> 0 bytes src/assets/images/notifications/r.svg | 9 ++ src/assets/images/notifications/w.jpg | Bin 3916 -> 0 bytes src/assets/images/notifications/w.svg | 9 ++ src/screens/NotificationsScreen.tsx | 119 +++++++++++----------- 13 files changed, 111 insertions(+), 62 deletions(-) delete mode 100644 src/assets/images/notifications/e.jpg create mode 100644 src/assets/images/notifications/e.svg delete mode 100644 src/assets/images/notifications/f.jpg create mode 100644 src/assets/images/notifications/f.svg delete mode 100644 src/assets/images/notifications/image.jpg create mode 100644 src/assets/images/notifications/image.svg delete mode 100644 src/assets/images/notifications/m.jpg create mode 100644 src/assets/images/notifications/m.svg delete mode 100644 src/assets/images/notifications/r.jpg create mode 100644 src/assets/images/notifications/r.svg delete mode 100644 src/assets/images/notifications/w.jpg create mode 100644 src/assets/images/notifications/w.svg diff --git a/src/assets/images/notifications/e.jpg b/src/assets/images/notifications/e.jpg deleted file mode 100644 index 5627eb74a65577eca8fb3dc254a251328920e9d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3343 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 PF?J?;$zz|-2xtBY3pPOv diff --git a/src/assets/images/notifications/e.svg b/src/assets/images/notifications/e.svg new file mode 100644 index 0000000..56e4734 --- /dev/null +++ b/src/assets/images/notifications/e.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/images/notifications/f.jpg b/src/assets/images/notifications/f.jpg deleted file mode 100644 index a6d4bdda93ea3cb6271869064fa055fc6c0ef7af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3280 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 + + + + + + + + diff --git a/src/assets/images/notifications/image.jpg b/src/assets/images/notifications/image.jpg deleted file mode 100644 index 1c86b1c61bb0e20d9af4720474579c5c79690790..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3426 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 diff --git a/src/assets/images/notifications/image.svg b/src/assets/images/notifications/image.svg new file mode 100644 index 0000000..2b74403 --- /dev/null +++ b/src/assets/images/notifications/image.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/images/notifications/m.jpg b/src/assets/images/notifications/m.jpg deleted file mode 100644 index 392082c1cacb7870420ff99f24fd9f6e8a42c68f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3975 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 wnvf= + + + + + + + + diff --git a/src/assets/images/notifications/r.jpg b/src/assets/images/notifications/r.jpg deleted file mode 100644 index fb2363585ae57a073d5694da49854eacc8c2b260..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2772 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 KUmXDy6aNHZ8aKuO diff --git a/src/assets/images/notifications/r.svg b/src/assets/images/notifications/r.svg new file mode 100644 index 0000000..abb5ee3 --- /dev/null +++ b/src/assets/images/notifications/r.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/images/notifications/w.jpg b/src/assets/images/notifications/w.jpg deleted file mode 100644 index 6e581e70ebf3e5c3e994e96c512ab871b9a69a43..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3916 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 + + + + + + + + diff --git a/src/screens/NotificationsScreen.tsx b/src/screens/NotificationsScreen.tsx index 009bd7f..b2b9021 100644 --- a/src/screens/NotificationsScreen.tsx +++ b/src/screens/NotificationsScreen.tsx @@ -4,9 +4,7 @@ import { View, StyleSheet, ScrollView, - Image, ActivityIndicator, - ImageSourcePropType, TouchableOpacity, } from 'react-native'; import { useNavigation, useFocusEffect } from '@react-navigation/native'; @@ -24,14 +22,15 @@ import Alert from '../components/Alerts'; import { Colors, FontSizes } from '../styles/theme'; import { NotificationService } from '../services/notifications'; import { getUserIdFromSecureStore } from '../helper/jwtHelper'; +import { SvgProps } from 'react-native-svg'; -import completedImg from '../assets/images/notifications/f.jpg'; -import inProgressImg from '../assets/images/notifications/r.jpg'; -import approvedImg from '../assets/images/notifications/image.jpg'; -import canceledImg from '../assets/images/notifications/w.jpg'; -import readyForPickupImg from '../assets/images/notifications/e.jpg'; -import deliveryImg from '../assets/images/notifications/m.jpg'; -import defaultIcon from '../assets/images/favicon.png'; +// Importa tus SVG como componentes React +import CompletedSvg from '../assets/images/notifications/f.svg'; +import InProgressSvg from '../assets/images/notifications/r.svg'; +import ApprovedSvg from '../assets/images/notifications/image.svg'; +import CanceledSvg from '../assets/images/notifications/w.svg'; +import ReadyForPickupSvg from '../assets/images/notifications/e.svg'; +import DeliverySvg from '../assets/images/notifications/m.svg'; type NotificationItem = { id: string; @@ -43,16 +42,27 @@ type NotificationItem = { status: string; }; -const notificationIcons: Record = { - completed: completedImg as ImageSourcePropType, - in_progress: inProgressImg as ImageSourcePropType, - approved: approvedImg as ImageSourcePropType, - canceled: canceledImg as ImageSourcePropType, - ready_for_pickup: readyForPickupImg as ImageSourcePropType, - delivery: deliveryImg as ImageSourcePropType, +type NotificationResponse = { + id: string; + title: string; + message: string; + createdAt: string; + isRead: boolean; + order?: { + id: string; + status: string; + }; }; -const defaultIconSource = defaultIcon as ImageSourcePropType; +// Mapeo de status → componente SVG +const notificationIcons: Record> = { + completed: CompletedSvg, + in_progress: InProgressSvg, + approved: ApprovedSvg, + canceled: CanceledSvg, + ready_for_pickup: ReadyForPickupSvg, + delivery: DeliverySvg, +}; export default function NotificationsScreen() { const navigation = useNavigation(); @@ -81,16 +91,16 @@ export default function NotificationsScreen() { throw new Error(errorMsg); } - const items = res.data.map((nt: Record) => { - const order = (nt.order || {}) as Record; + const items = res.data.map((nt: NotificationResponse) => { + const order = nt.order || { id: '', status: '' }; return { - id: nt.id as string, - title: nt.title as string, - message: (nt.message as string).trim(), - createdAt: nt.createdAt as string, + id: nt.id, + title: nt.title, + message: nt.message.trim(), + createdAt: nt.createdAt, isRead: !!nt.isRead, - orderId: order.id as string, - status: order.status as string, + orderId: order.id, + status: order.status, }; }); @@ -108,7 +118,7 @@ export default function NotificationsScreen() { fetchNotifications(); }, []); - // Marca todas como leídas al enfocar o desenfocar la pantalla + // Marca todas como leídas al entrar o salir de la pantalla useFocusEffect( useCallback(() => { const markAllRead = async () => { @@ -116,16 +126,12 @@ export default function NotificationsScreen() { const token = await getUserIdFromSecureStore(); if (!token) return; - // Filtrar solo las no leídas const toMark = notificationsList.filter((n) => !n.isRead); if (toMark.length === 0) return; - // Llamar al servicio en paralelo await Promise.all( toMark.map((n) => NotificationService.markAsRead(n.orderId, token)), ); - - // Actualizar estado local setNotificationsList((prev) => prev.map((n) => ({ ...n, isRead: true })), ); @@ -136,9 +142,7 @@ export default function NotificationsScreen() { ); } }; - markAllRead(); - // no necesitamos cleanup }, [notificationsList]), ); @@ -154,8 +158,25 @@ export default function NotificationsScreen() { return format(date, 'yyyy-MM-dd', { locale: es }); }; - const getNotificationIcon = (status: string): ImageSourcePropType => { - return notificationIcons[status] || defaultIconSource; + const renderNotification = (nt: NotificationItem) => { + const Icon = notificationIcons[nt.status]; + return ( + + + + + {nt.title} + + {formatRelativeDate(nt.createdAt)} + + + {nt.message} + + + ); }; return ( @@ -204,26 +225,7 @@ export default function NotificationsScreen() { ) : ( - {notificationsList.map((nt) => ( - - - - - {nt.title} - - {formatRelativeDate(nt.createdAt)} - - - {nt.message} - - - ))} + {notificationsList.map(renderNotification)} )} @@ -287,15 +289,9 @@ const styles = StyleSheet.create({ unreadBackground: { backgroundColor: '#FFFFFF', }, - icon: { - width: 32, - height: 32, - marginRight: 12, - marginTop: 4, - borderRadius: 16, - }, textContainer: { flex: 1, + marginLeft: 12, }, itemHeader: { flexDirection: 'row', @@ -309,7 +305,6 @@ const styles = StyleSheet.create({ message: { color: Colors.textLowContrast, fontSize: FontSizes.c1.size, - marginRight: 80, flexWrap: 'wrap', }, }); From 15d158c9bd6f637152af14f0933a4e041cbcaf8d Mon Sep 17 00:00:00 2001 From: Mvjtb <1001.25854794.ucla@gmail.com> Date: Fri, 23 May 2025 19:39:00 -0400 Subject: [PATCH 3/7] Implement SSE for real-time notifications and remove polling mechanism --- src/hooks/useNotifications.ts | 84 ++++++++++++++++++++++++++++------- 1 file changed, 68 insertions(+), 16 deletions(-) diff --git a/src/hooks/useNotifications.ts b/src/hooks/useNotifications.ts index 127bb31..3e60b77 100644 --- a/src/hooks/useNotifications.ts +++ b/src/hooks/useNotifications.ts @@ -1,5 +1,3 @@ -// src/context/NotificationsContext.ts - import React, { createContext, useContext, @@ -12,6 +10,9 @@ import { NotificationService } from '../services/notifications'; import { NotificationResponse } from '@pharmatech/sdk'; import { ServiceResponse } from '../types/api'; import { getUserIdFromSecureStore } from '../helper/jwtHelper'; +import EventSource, { EventSourceListener } from 'react-native-sse'; +import * as SecureStore from 'expo-secure-store'; +import { api } from '../lib/sdkConfig'; export type Notification = NotificationResponse & { isRead: boolean }; @@ -58,20 +59,6 @@ export function NotificationsProvider(props: { children: ReactNode }) { } }, []); - // carga inicial - useEffect(() => { - void refreshNotifications(); - }, [refreshNotifications]); - - // polling automático cada 30 segundos - useEffect(() => { - const interval = setInterval(() => { - void refreshNotifications(); - }, 30_000); // ajusta intervalo según necesidad - - return () => clearInterval(interval); - }, [refreshNotifications]); - const markAsRead = useCallback(async (notificationId: string) => { try { const token = await getUserIdFromSecureStore(); @@ -106,6 +93,71 @@ export function NotificationsProvider(props: { children: ReactNode }) { } }, []); + // SSE: Real-time notifications + useEffect(() => { + let es: EventSource | null = null; + + async function setupSSE() { + try { + const token = await SecureStore.getItemAsync('auth_token'); + if (!token) return; + + // Usa el api de sdkConfig para obtener el cliente y baseURL + const axiosClient = api.client['client']; + const baseURL: string | undefined = axiosClient?.getUri + ? axiosClient.getUri({ url: '' }) + : axiosClient?.defaults?.baseURL; + if (!baseURL) return; + + const url = `${baseURL.replace(/\/$/, '')}/notification/stream`; + + es = new EventSource(url, { + headers: { + Authorization: `Bearer ${token}`, + }, + lineEndingCharacter: '\n', + }); + + const listener: EventSourceListener = (event) => { + console.log('Evento SSE recibido:', event.type); + if (event.type === 'open') { + console.log('Conexión SSE abierta'); + } else if (event.type === 'message') { + console.log('[SSE] Tipo: message'); + console.log('[SSE] Event object:', event); + console.log('[SSE] Raw data:', event.data); + if (typeof event.data === 'string') { + const notif = JSON.parse(event.data) as NotificationResponse; + console.log('[SSE] Parsed notification:', notif); + setNotifications((prev) => { + void refreshNotifications(); + if (prev.some((n) => n.id === notif.id)) return prev; + return [{ ...notif, isRead: Boolean(notif.isRead) }, ...prev]; + }); + } + } else if (event.type === 'error') { + console.error('Error SSE:', event); + } + }; + + es.addEventListener('open', listener); + es.addEventListener('message', listener); + es.addEventListener('error', listener); + } catch (err) { + console.error('Error inicializando SSE para notificaciones:', err); + } + } + + setupSSE(); + + return () => { + if (es) { + es.removeAllEventListeners(); + es.close(); + } + }; + }, [refreshNotifications]); + return React.createElement( NotificationsContext.Provider, { From df00381cc0186cd507fb3984c91d4b864d6ab8aa Mon Sep 17 00:00:00 2001 From: Mvjtb <1001.25854794.ucla@gmail.com> Date: Fri, 23 May 2025 19:46:43 -0400 Subject: [PATCH 4/7] Add react-native-sse dependency for server-sent events support --- package-lock.json | 6 ++++++ package.json | 5 +++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 95c5fb0..63d0cd6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,6 +39,7 @@ "react-native-reanimated": "^3.16.2", "react-native-safe-area-context": "4.12.0", "react-native-screens": "~4.4.0", + "react-native-sse": "^1.2.1", "react-native-svg": "^15.11.2", "react-redux": "^9.2.0", "socket.io-client": "^4.8.1", @@ -14548,6 +14549,11 @@ "react-native": "*" } }, + "node_modules/react-native-sse": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/react-native-sse/-/react-native-sse-1.2.1.tgz", + "integrity": "sha512-zejanlScF+IB9tYnbdry0MT34qjBXbiV/E72qGz33W/tX1bx8MXsbB4lxiuPETc9v/008vYZ60yjIstW22VlVg==" + }, "node_modules/react-native-svg": { "version": "15.11.2", "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.11.2.tgz", diff --git a/package.json b/package.json index 62c765a..32f465f 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "date-fns": "^4.1.0", "expo": "~52.0.37", "expo-constants": "~17.0.7", + "expo-dev-client": "~5.0.20", "expo-font": "~13.0.4", "expo-image-picker": "~16.0.6", "expo-linking": "~7.0.5", @@ -48,11 +49,11 @@ "react-native-reanimated": "^3.16.2", "react-native-safe-area-context": "4.12.0", "react-native-screens": "~4.4.0", + "react-native-sse": "^1.2.1", "react-native-svg": "^15.11.2", "react-redux": "^9.2.0", "socket.io-client": "^4.8.1", - "tailwindcss": "^3.4.17", - "expo-dev-client": "~5.0.20" + "tailwindcss": "^3.4.17" }, "devDependencies": { "@babel/core": "^7.25.2", From bd39dca1c2da94f113bc661fc84ecfa70854cc4e Mon Sep 17 00:00:00 2001 From: Mvjtb <1001.25854794.ucla@gmail.com> Date: Thu, 29 May 2025 13:01:56 -0400 Subject: [PATCH 5/7] Refactor notifications handling in TopBar and HomeScreen components to refreshing --- src/components/TopBar.tsx | 3 +- src/hooks/useNotifications.ts | 136 +++++++++++++++------------------ src/screens/tab/HomeScreen.tsx | 31 +++++++- 3 files changed, 91 insertions(+), 79 deletions(-) diff --git a/src/components/TopBar.tsx b/src/components/TopBar.tsx index c12b78b..e4a4ae0 100644 --- a/src/components/TopBar.tsx +++ b/src/components/TopBar.tsx @@ -23,8 +23,7 @@ const TopBar = () => { useState(false); const router = useRouter(); const { cartItems } = useCart(); - const { notifications } = useNotifications(); - const unreadCount = notifications.filter((n) => !n.isRead).length; + const { unreadCount } = useNotifications(); // Calculate the total quantity of items in the cart const totalCartQuantity = cartItems.reduce( diff --git a/src/hooks/useNotifications.ts b/src/hooks/useNotifications.ts index 3e60b77..c4a31f3 100644 --- a/src/hooks/useNotifications.ts +++ b/src/hooks/useNotifications.ts @@ -5,12 +5,13 @@ import React, { useState, ReactNode, useCallback, + useMemo, } from 'react'; import { NotificationService } from '../services/notifications'; import { NotificationResponse } from '@pharmatech/sdk'; import { ServiceResponse } from '../types/api'; import { getUserIdFromSecureStore } from '../helper/jwtHelper'; -import EventSource, { EventSourceListener } from 'react-native-sse'; +import EventSource from 'react-native-sse'; import * as SecureStore from 'expo-secure-store'; import { api } from '../lib/sdkConfig'; @@ -18,6 +19,8 @@ export type Notification = NotificationResponse & { isRead: boolean }; export type NotificationsContextType = { notifications: Notification[]; + totalCount: number; + unreadCount: number; markAsRead: (notificationId: string) => Promise; markAsUnread: (notificationId: string) => Promise; refreshNotifications: () => Promise; @@ -33,8 +36,7 @@ export function NotificationsProvider(props: { children: ReactNode }) { const refreshNotifications = useCallback(async (): Promise => { try { const token = await getUserIdFromSecureStore(); - if (!token) - throw new Error('No se pudo obtener el token de autenticación.'); + if (!token) throw new Error('No se pudo obtener el token.'); const response: ServiceResponse< NotificationResponse | NotificationResponse[] @@ -45,12 +47,12 @@ export function NotificationsProvider(props: { children: ReactNode }) { ? response.data : [response.data]; - setNotifications( - rawArray.map((n: NotificationResponse) => ({ - ...n, - isRead: Boolean(n.isRead), - })), - ); + const mapped = rawArray.map((n) => ({ + ...n, + isRead: Boolean(n.isRead), + })); + console.log('Notificaciones recibidas:', mapped); + setNotifications(mapped); } else { console.error('Error cargando notificaciones:', response); } @@ -62,10 +64,9 @@ export function NotificationsProvider(props: { children: ReactNode }) { const markAsRead = useCallback(async (notificationId: string) => { try { const token = await getUserIdFromSecureStore(); - if (!token) - throw new Error('No se pudo obtener el token de autenticación.'); - + if (!token) throw new Error('No se pudo obtener el token.'); await NotificationService.markAsRead(notificationId, token); + // optimismo: actualizamos localmente setNotifications((prev) => prev.map((n) => (n.id === notificationId ? { ...n, isRead: true } : n)), ); @@ -78,74 +79,52 @@ export function NotificationsProvider(props: { children: ReactNode }) { }, []); const markAsUnread = useCallback(async (notificationId: string) => { - try { - // Si la API soporta 'unread', aquí iría NotificationService.markAsUnread(...) - setNotifications((prev) => - prev.map((n) => - n.id === notificationId ? { ...n, isRead: false } : n, - ), - ); - } catch (err) { - console.error( - `Error marcando notificación ${notificationId} como no leída:`, - err, - ); - } + // si tu API no ofrece un endpoint "unread", lo simulamos localmente + setNotifications((prev) => + prev.map((n) => (n.id === notificationId ? { ...n, isRead: false } : n)), + ); }, []); - // SSE: Real-time notifications + // Conteos derivados + const totalCount = notifications.length; + const unreadCount = useMemo( + () => notifications.filter((n) => !n.isRead).length, + [notifications], + ); + useEffect(() => { let es: EventSource | null = null; async function setupSSE() { - try { - const token = await SecureStore.getItemAsync('auth_token'); - if (!token) return; - - // Usa el api de sdkConfig para obtener el cliente y baseURL - const axiosClient = api.client['client']; - const baseURL: string | undefined = axiosClient?.getUri - ? axiosClient.getUri({ url: '' }) - : axiosClient?.defaults?.baseURL; - if (!baseURL) return; - - const url = `${baseURL.replace(/\/$/, '')}/notification/stream`; - - es = new EventSource(url, { - headers: { - Authorization: `Bearer ${token}`, - }, - lineEndingCharacter: '\n', - }); - - const listener: EventSourceListener = (event) => { - console.log('Evento SSE recibido:', event.type); - if (event.type === 'open') { - console.log('Conexión SSE abierta'); - } else if (event.type === 'message') { - console.log('[SSE] Tipo: message'); - console.log('[SSE] Event object:', event); - console.log('[SSE] Raw data:', event.data); - if (typeof event.data === 'string') { - const notif = JSON.parse(event.data) as NotificationResponse; - console.log('[SSE] Parsed notification:', notif); - setNotifications((prev) => { - void refreshNotifications(); - if (prev.some((n) => n.id === notif.id)) return prev; - return [{ ...notif, isRead: Boolean(notif.isRead) }, ...prev]; - }); - } - } else if (event.type === 'error') { - console.error('Error SSE:', event); - } - }; - - es.addEventListener('open', listener); - es.addEventListener('message', listener); - es.addEventListener('error', listener); - } catch (err) { - console.error('Error inicializando SSE para notificaciones:', err); - } + // 1) carga inicial + await refreshNotifications(); + + // 2) stream SSE + const token = await SecureStore.getItemAsync('auth_token'); + if (!token) return; + + const axiosClient = api.client['client']; + const baseURL: string | undefined = axiosClient.getUri + ? axiosClient.getUri({ url: '' }) + : axiosClient.defaults.baseURL; + if (!baseURL) return; + + const url = `${baseURL.replace(/\/$/, '')}/notification/stream`; + + es = new EventSource(url, { + headers: { Authorization: `Bearer ${token}` }, + lineEndingCharacter: '\n', + }); + + // Cada vez que llegue un mensaje, recargamos TODO el listado: + es.addEventListener('message', () => { + console.log('[SSE] mensaje recibido → refrescando notificaciones'); + void refreshNotifications(); + }); + + es.addEventListener('error', (err) => { + console.error('[SSE] error:', err); + }); } setupSSE(); @@ -161,7 +140,14 @@ export function NotificationsProvider(props: { children: ReactNode }) { return React.createElement( NotificationsContext.Provider, { - value: { notifications, markAsRead, markAsUnread, refreshNotifications }, + value: { + notifications, + totalCount, + unreadCount, + markAsRead, + markAsUnread, + refreshNotifications, + }, }, props.children, ); diff --git a/src/screens/tab/HomeScreen.tsx b/src/screens/tab/HomeScreen.tsx index ad91bf1..6c40d93 100644 --- a/src/screens/tab/HomeScreen.tsx +++ b/src/screens/tab/HomeScreen.tsx @@ -1,5 +1,11 @@ import React, { useState, useEffect } from 'react'; -import { View, StyleSheet, ScrollView, ActivityIndicator } from 'react-native'; +import { + View, + StyleSheet, + ScrollView, + ActivityIndicator, + RefreshControl, +} from 'react-native'; import { useRouter, useLocalSearchParams } from 'expo-router'; import * as SecureStore from 'expo-secure-store'; import { useCart } from '../../hooks/useCart'; @@ -10,6 +16,7 @@ import { ProductService } from '../../services/products'; import { Product } from '../../types/Product'; import EmailVerificationModal from './EmailVerificationModal'; import { decodeJWT } from '../../helper/jwtHelper'; +import { useNotifications } from '../../hooks/useNotifications'; export default function HomeScreen() { const [products, setProducts] = useState([]); @@ -17,10 +24,12 @@ export default function HomeScreen() { const [loading, setLoading] = useState(true); const [loadingRecommendations, setLoadingRecommendations] = useState(true); const [showEmailVerification, setShowEmailVerification] = useState(false); + const [refreshing, setRefreshing] = useState(false); const router = useRouter(); const { showEmailVerification: showEmailVerificationParam } = useLocalSearchParams(); const { cartItems, addToCart, updateCartQuantity, setCartUserId } = useCart(); + const { refreshNotifications } = useNotifications(); const getItemQuantity = (productId: string) => { const cartItem = cartItems.find((item) => item.id === productId.toString()); @@ -162,9 +171,27 @@ export default function HomeScreen() { } }; + const onRefresh = async () => { + setRefreshing(true); + await Promise.all([ + obtainProducts(), + obtainRecommendedProducts(), + refreshNotifications(), // <-- refresca notificaciones también + ]); + setRefreshing(false); + }; + return ( - + + } + > Ofertas especiales From 4448be3b991f36ef62ce7f2a229d6acefc84abef Mon Sep 17 00:00:00 2001 From: jsayago77 Date: Thu, 29 May 2025 21:13:13 -0400 Subject: [PATCH 6/7] Refactor notification service to handle token retrieval internally --- src/hooks/useNotifications.ts | 8 +------- src/services/notifications.ts | 9 +++++++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/hooks/useNotifications.ts b/src/hooks/useNotifications.ts index c4a31f3..9e93ba5 100644 --- a/src/hooks/useNotifications.ts +++ b/src/hooks/useNotifications.ts @@ -10,7 +10,6 @@ import React, { import { NotificationService } from '../services/notifications'; import { NotificationResponse } from '@pharmatech/sdk'; import { ServiceResponse } from '../types/api'; -import { getUserIdFromSecureStore } from '../helper/jwtHelper'; import EventSource from 'react-native-sse'; import * as SecureStore from 'expo-secure-store'; import { api } from '../lib/sdkConfig'; @@ -35,9 +34,6 @@ export function NotificationsProvider(props: { children: ReactNode }) { const refreshNotifications = useCallback(async (): Promise => { try { - const token = await getUserIdFromSecureStore(); - if (!token) throw new Error('No se pudo obtener el token.'); - const response: ServiceResponse< NotificationResponse | NotificationResponse[] > = await NotificationService.getNotifications(); @@ -63,9 +59,7 @@ export function NotificationsProvider(props: { children: ReactNode }) { const markAsRead = useCallback(async (notificationId: string) => { try { - const token = await getUserIdFromSecureStore(); - if (!token) throw new Error('No se pudo obtener el token.'); - await NotificationService.markAsRead(notificationId, token); + await NotificationService.markAsRead(notificationId); // optimismo: actualizamos localmente setNotifications((prev) => prev.map((n) => (n.id === notificationId ? { ...n, isRead: true } : n)), diff --git a/src/services/notifications.ts b/src/services/notifications.ts index ec305fc..a85d05b 100644 --- a/src/services/notifications.ts +++ b/src/services/notifications.ts @@ -24,9 +24,14 @@ export const NotificationService = { }; } }, - markAsRead: async (orderId: string, jwt: string): Promise => { + markAsRead: async (orderId: string): Promise => { try { - await api.notification.markAsRead(orderId, jwt); + const token = await SecureStore.getItemAsync('auth_token'); + if (!token) { + throw new Error('No se encontró el token de autenticación'); + } + + await api.notification.markAsRead(orderId, token); } catch (error) { throw new Error(extractErrorMessage(error)); } From 0f074f10fb6a8ea0ff08d12de98ba5e8253ced8b Mon Sep 17 00:00:00 2001 From: jsayago77 Date: Thu, 29 May 2025 21:17:58 -0400 Subject: [PATCH 7/7] Remove token retrieval from NotificationsScreen to streamline notification fetching --- src/screens/NotificationsScreen.tsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/screens/NotificationsScreen.tsx b/src/screens/NotificationsScreen.tsx index b2b9021..f644e49 100644 --- a/src/screens/NotificationsScreen.tsx +++ b/src/screens/NotificationsScreen.tsx @@ -21,7 +21,6 @@ import PoppinsText from '../components/PoppinsText'; import Alert from '../components/Alerts'; import { Colors, FontSizes } from '../styles/theme'; import { NotificationService } from '../services/notifications'; -import { getUserIdFromSecureStore } from '../helper/jwtHelper'; import { SvgProps } from 'react-native-svg'; // Importa tus SVG como componentes React @@ -79,9 +78,6 @@ export default function NotificationsScreen() { setLoading(true); setShowErrorAlert(false); try { - const token = await getUserIdFromSecureStore(); - if (!token) throw new Error('No se pudo obtener el token de usuario.'); - const res = await NotificationService.getNotifications(); if (!res.success || !Array.isArray(res.data)) { const errorMsg = @@ -123,14 +119,11 @@ export default function NotificationsScreen() { useCallback(() => { const markAllRead = async () => { try { - const token = await getUserIdFromSecureStore(); - if (!token) return; - const toMark = notificationsList.filter((n) => !n.isRead); if (toMark.length === 0) return; await Promise.all( - toMark.map((n) => NotificationService.markAsRead(n.orderId, token)), + toMark.map((n) => NotificationService.markAsRead(n.orderId)), ); setNotificationsList((prev) => prev.map((n) => ({ ...n, isRead: true })),