chore: fix notif

This commit is contained in:
2026-03-02 20:46:07 +01:00
parent 0085a39537
commit d299601667
10 changed files with 348 additions and 576 deletions
+10 -96
View File
@@ -10,15 +10,6 @@ import React, {
import { getClientNotifications, markNotificationsRead } from "../api/api";
import type { ClientNotification } from "../api/api";
import { getToken } from "../auth/tokenStorage";
import {
registerForPushNotificationsAsync,
sendPushTokenToBackend,
setupNotificationChannel,
addNotificationReceivedListener,
addNotificationResponseListener,
setBadgeCount,
getLastNotificationResponse,
} from "../services/pushNotifications";
interface ToastData {
message: string;
@@ -32,7 +23,6 @@ interface NotificationContextType {
refreshNotifications: () => Promise<void>;
toast: ToastData | null;
clearToast: () => void;
pushToken: string | null;
navigateToOrder: number | null;
clearNavigateToOrder: () => void;
}
@@ -44,7 +34,6 @@ const NotificationContext = createContext<NotificationContextType>({
refreshNotifications: async () => {},
toast: null,
clearToast: () => {},
pushToken: null,
navigateToOrder: null,
clearNavigateToOrder: () => {},
});
@@ -57,35 +46,30 @@ function getToastType(
notifType: string,
): "success" | "error" | "warning" | "info" {
switch (notifType) {
case "order_confirmed":
case "order_approved":
case "assigned":
return "success";
case "order_en_route":
case "order_assigned":
case "en_route":
case "support":
return "info";
case "order_delivered":
return "warning";
case "livre":
return "success";
case "failed":
return "error";
default:
return "info";
}
}
export function NotificationProvider({ children }: { children: ReactNode }) {
const [notifications, setNotifications] = useState<ClientNotification[]>(
[],
);
const [notifications, setNotifications] = useState<ClientNotification[]>([]);
const [unreadCount, setUnreadCount] = useState(0);
const [toast, setToast] = useState<ToastData | null>(null);
const [pushToken, setPushToken] = useState<string | null>(null);
const [navigateToOrder, setNavigateToOrder] = useState<number | null>(null);
const seenIdsRef = useRef<Set<string>>(new Set());
const isFirstLoadRef = useRef(true);
const clearToast = useCallback(() => setToast(null), []);
const clearNavigateToOrder = useCallback(
() => setNavigateToOrder(null),
[],
);
const clearNavigateToOrder = useCallback(() => setNavigateToOrder(null), []);
const showToast = useCallback(
(message: string, type: ToastData["type"]) => {
@@ -95,68 +79,6 @@ export function NotificationProvider({ children }: { children: ReactNode }) {
[],
);
useEffect(() => {
let isMounted = true;
const initPush = async () => {
const token = await getToken();
if (!token) return;
await setupNotificationChannel();
const expoPushToken = await registerForPushNotificationsAsync();
if (expoPushToken && isMounted) {
setPushToken(expoPushToken);
await sendPushTokenToBackend(expoPushToken);
}
const lastResponse = await getLastNotificationResponse();
if (lastResponse && isMounted) {
const data = lastResponse.notification.request.content.data;
if (data?.command_id) {
setNavigateToOrder(data.command_id as number);
}
}
};
initPush();
return () => {
isMounted = false;
};
}, []);
useEffect(() => {
const receivedSub = addNotificationReceivedListener((notification) => {
const { title, body } = notification.request.content;
const data = notification.request.content.data;
// Afficher un toast pour la notification push en foreground
if (body) {
const notifType = (data?.type as string) || "";
showToast(body, getToastType(notifType));
}
// Rafraîchir la liste des notifications
fetchNotifications();
});
// L'utilisateur tap sur une notification
const responseSub = addNotificationResponseListener((response) => {
const data = response.notification.request.content.data;
if (data?.command_id) {
setNavigateToOrder(data.command_id as number);
}
// Rafraîchir les notifications
fetchNotifications();
});
return () => {
receivedSub.remove();
responseSub.remove();
};
}, [showToast]);
const fetchNotifications = useCallback(async () => {
const token = await getToken();
if (!token) return;
@@ -168,11 +90,7 @@ export function NotificationProvider({ children }: { children: ReactNode }) {
setNotifications(newNotifs);
setUnreadCount(response.unread_count);
// Mettre à jour le badge de l'app
await setBadgeCount(response.unread_count);
if (!isFirstLoadRef.current) {
// Détecter les NOUVELLES notifications non lues
for (const notif of newNotifs) {
if (notif.read) continue;
const key = `${notif.command_id}-${notif.type}-${notif.created_at}`;
@@ -182,7 +100,6 @@ export function NotificationProvider({ children }: { children: ReactNode }) {
}
}
} else {
// Premier chargement : enregistrer tous les IDs sans toast
for (const notif of newNotifs) {
const key = `${notif.command_id}-${notif.type}-${notif.created_at}`;
seenIdsRef.current.add(key);
@@ -191,7 +108,7 @@ export function NotificationProvider({ children }: { children: ReactNode }) {
}
}
} catch {
// Silencieux en cas d'erreur
// Silencieux
}
}, [showToast]);
@@ -206,8 +123,6 @@ export function NotificationProvider({ children }: { children: ReactNode }) {
setNotifications((prev) =>
prev.map((n) => ({ ...n, read: true })),
);
// Remettre le badge à 0
await setBadgeCount(0);
}
} catch {
// Silencieux
@@ -230,7 +145,6 @@ export function NotificationProvider({ children }: { children: ReactNode }) {
refreshNotifications: fetchNotifications,
toast,
clearToast,
pushToken,
navigateToOrder,
clearNavigateToOrder,
}}
@@ -18,7 +18,6 @@ import { useAuth } from "../auth/AuthContext";
import { useTheme } from "../context/ThemeContext";
import { logoutUser } from "../api/api";
import type { ClientNotification } from "../api/api";
import { removePushTokenFromBackend } from "../services/pushNotifications";
import { fontSize, spacing, borderRadius } from "../theme";
import Toast from "../components/ui/Toast";
import type { ClientTabParamList, ClientStackParamList } from "./types";
@@ -58,7 +57,6 @@ function ClientTabs() {
markAllRead,
toast,
clearToast,
pushToken,
navigateToOrder,
clearNavigateToOrder,
} = useNotifications();
@@ -76,7 +74,6 @@ function ClientTabs() {
}, [navigateToOrder, navigation, clearNavigateToOrder]);
const handleLogout = async () => {
try { await removePushTokenFromBackend(); } catch { /* silencieux */ }
await logoutUser();
await logout();
};
-134
View File
@@ -1,134 +0,0 @@
import * as Notifications from "expo-notifications";
import * as Device from "expo-device";
import Constants from "expo-constants";
import { Platform } from "react-native";
import apiClient from "../api/client";
const V1 = "https://uber-stup.club/api/v1";
// Configuration du comportement des notifications en foreground
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldPlaySound: true,
shouldSetBadge: true,
shouldShowBanner: true,
shouldShowList: true,
}),
});
export async function setupNotificationChannel(): Promise<void> {
if (Platform.OS === "android") {
await Notifications.setNotificationChannelAsync("orders", {
name: "Commandes",
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: "#7C3AED",
sound: "default",
enableVibrate: true,
showBadge: true,
});
}
}
// Enregistrer le device pour les push notifications et retourner le token
export async function registerForPushNotificationsAsync(): Promise<
string | null
> {
if (!Device.isDevice) {
console.log(
"⚠️ Push notifications ne fonctionnent pas sur un émulateur",
);
return null;
}
// Vérifier/demander les permissions
const { status: existingStatus } =
await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== "granted") {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== "granted") {
console.log("❌ Permission push notifications refusée");
return null;
}
// Récupérer le projectId Expo
const projectId =
Constants.expoConfig?.extra?.eas?.projectId ??
Constants.easConfig?.projectId;
if (!projectId) {
console.log("⚠️ projectId non trouvé - push notifications désactivées");
return null;
}
try {
const tokenData = await Notifications.getExpoPushTokenAsync({
projectId,
});
const pushToken = tokenData.data;
console.log("📱 Push token:", pushToken);
return pushToken;
} catch (error) {
console.error("❌ Erreur récupération push token:", error);
return null;
}
}
// Envoyer le push token au backend
export async function sendPushTokenToBackend(
pushToken: string,
): Promise<boolean> {
try {
const { data } = await apiClient.post(`${V1}/push-token`, {
push_token: pushToken,
platform: Platform.OS,
});
console.log("✅ Push token enregistré sur le backend");
return data.success === true;
} catch (error) {
console.error("❌ Erreur envoi push token au backend:", error);
return false;
}
}
// Supprimer le push token du backend (au logout)
export async function removePushTokenFromBackend(): Promise<void> {
try {
await apiClient.delete(`${V1}/push-token`);
console.log("✅ Push token supprimé du backend");
} catch (error) {
console.error("❌ Erreur suppression push token:", error);
}
}
// Listeners pour les notifications
export function addNotificationReceivedListener(
callback: (notification: Notifications.Notification) => void,
): Notifications.EventSubscription {
return Notifications.addNotificationReceivedListener(callback);
}
export function addNotificationResponseListener(
callback: (response: Notifications.NotificationResponse) => void,
): Notifications.EventSubscription {
return Notifications.addNotificationResponseReceivedListener(callback);
}
// Mettre à jour le badge
export async function setBadgeCount(count: number): Promise<void> {
try {
await Notifications.setBadgeCountAsync(count);
} catch {
// Silencieux - certains appareils ne supportent pas les badges
}
}
// Récupérer la dernière notification qui a ouvert l'app
export async function getLastNotificationResponse(): Promise<Notifications.NotificationResponse | null> {
return await Notifications.getLastNotificationResponseAsync();
}