chore: add notification telegram
This commit is contained in:
+1
-11
@@ -25,8 +25,7 @@
|
||||
"predictiveBackGestureEnabled": false,
|
||||
"package": "com.uberstup.clientpanel",
|
||||
"versionCode": 1,
|
||||
"permissions": ["android.permission.VIBRATE"],
|
||||
"googleServicesFile": "./google-services.json"
|
||||
"permissions": ["android.permission.VIBRATE"]
|
||||
},
|
||||
"web": {
|
||||
"favicon": "./assets/icon.png"
|
||||
@@ -34,15 +33,6 @@
|
||||
"plugins": [
|
||||
"expo-font",
|
||||
"expo-router",
|
||||
[
|
||||
"expo-notifications",
|
||||
{
|
||||
"icon": "./assets/icon.png",
|
||||
"color": "#ffffff",
|
||||
"androidMode": "default",
|
||||
"androidCollapsedTitle": "#{unread_notifications} nouvelles notifications"
|
||||
}
|
||||
],
|
||||
[
|
||||
"expo-build-properties",
|
||||
{
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "607244438046",
|
||||
"project_id": "frontend-client-f3354",
|
||||
"storage_bucket": "frontend-client-f3354.firebasestorage.app"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:607244438046:android:ce55d750fd0f1b98ac3c0f",
|
||||
"android_client_info": {
|
||||
"package_name": "com.uberstup.clientpanel"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyA2-og1AoVmwXXY4KTX-J-bt6E5cjiQvmM"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
+24
-5
@@ -813,17 +813,36 @@ export const getCryptoPaymentStatus = async (commandId: number): Promise<CryptoP
|
||||
}
|
||||
};
|
||||
|
||||
export const registerPushToken = async (token: string): Promise<void> => {
|
||||
// ============================================
|
||||
// TELEGRAM
|
||||
// ============================================
|
||||
|
||||
export const getTelegramStatus = async (): Promise<{ linked: boolean; enabled: boolean }> => {
|
||||
try {
|
||||
await apiClient.post(`${V1}/push-token`, { push_token: token });
|
||||
const { data } = await apiClient.get(`${V1}/telegram/status`);
|
||||
return data;
|
||||
} catch {
|
||||
// silencieux
|
||||
return { linked: false, enabled: false };
|
||||
}
|
||||
};
|
||||
|
||||
export const unregisterPushToken = async (): Promise<void> => {
|
||||
export const generateTelegramLinkToken = async (): Promise<{
|
||||
token?: string;
|
||||
link_url?: string;
|
||||
expires_in?: number;
|
||||
error?: string;
|
||||
}> => {
|
||||
try {
|
||||
await apiClient.delete(`${V1}/push-token`);
|
||||
const { data } = await apiClient.post(`${V1}/telegram/link-token`);
|
||||
return data;
|
||||
} catch (e: any) {
|
||||
return { error: e?.response?.data?.error || "Erreur" };
|
||||
}
|
||||
};
|
||||
|
||||
export const unlinkTelegram = async (): Promise<void> => {
|
||||
try {
|
||||
await apiClient.delete(`${V1}/telegram/unlink`);
|
||||
} catch {
|
||||
// silencieux
|
||||
}
|
||||
|
||||
@@ -7,11 +7,9 @@ import React, {
|
||||
useCallback,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import * as Notifications from "expo-notifications";
|
||||
import { getClientNotifications, markNotificationsRead } from "../api/api";
|
||||
import type { ClientNotification } from "../api/api";
|
||||
import { getToken } from "../auth/tokenStorage";
|
||||
import { registerForPushNotifications } from "../services/pushNotifications";
|
||||
|
||||
interface ToastData {
|
||||
message: string;
|
||||
@@ -130,30 +128,6 @@ export function NotificationProvider({ children }: { children: ReactNode }) {
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Enregistrement push token + listeners (pattern doc Expo)
|
||||
useEffect(() => {
|
||||
registerForPushNotifications().catch(console.error);
|
||||
|
||||
const notificationListener = Notifications.addNotificationReceivedListener((notif) => {
|
||||
const body = notif.request.content.body ?? "";
|
||||
const data = notif.request.content.data as any;
|
||||
showToast(body, getToastType(data?.type ?? ""));
|
||||
fetchNotifications();
|
||||
});
|
||||
|
||||
const responseListener = Notifications.addNotificationResponseReceivedListener((response) => {
|
||||
const data = response.notification.request.content.data as any;
|
||||
if (data?.command_id) {
|
||||
setNavigateToOrder(data.command_id);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
notificationListener.remove();
|
||||
responseListener.remove();
|
||||
};
|
||||
}, [fetchNotifications, showToast]);
|
||||
|
||||
// Polling toutes les 15 secondes
|
||||
useEffect(() => {
|
||||
fetchNotifications();
|
||||
|
||||
@@ -8,11 +8,12 @@ import {
|
||||
Alert,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
Linking,
|
||||
} from "react-native";
|
||||
import { useFocusEffect } from "@react-navigation/native";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { getMyProfile, updateMyProfile } from "../../api/api";
|
||||
import { getMyProfile, updateMyProfile, getTelegramStatus, generateTelegramLinkToken, unlinkTelegram } from "../../api/api";
|
||||
import TextInput from "../../components/ui/TextInput";
|
||||
import Button from "../../components/ui/Button";
|
||||
import { useTheme } from "../../context/ThemeContext";
|
||||
@@ -39,14 +40,22 @@ export default function ProfileScreen() {
|
||||
const [loadingProfile, setLoadingProfile] = useState(true);
|
||||
const [savingContact, setSavingContact] = useState(false);
|
||||
|
||||
// Telegram
|
||||
const [telegramLinked, setTelegramLinked] = useState(false);
|
||||
const [telegramEnabled, setTelegramEnabled] = useState(false);
|
||||
const [telegramLoading, setTelegramLoading] = useState(false);
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
setLoadingProfile(true);
|
||||
const [savedAddress, savedPhone, savedSignal, profileRes] = await Promise.all([
|
||||
const [savedAddress, savedPhone, savedSignal, profileRes, tgStatus] = await Promise.all([
|
||||
AsyncStorage.getItem(STORAGE_ADDRESS),
|
||||
AsyncStorage.getItem(STORAGE_PHONE),
|
||||
AsyncStorage.getItem(STORAGE_SIGNAL),
|
||||
getMyProfile(),
|
||||
getTelegramStatus(),
|
||||
]);
|
||||
setTelegramLinked(tgStatus.linked);
|
||||
setTelegramEnabled(tgStatus.enabled);
|
||||
|
||||
if (savedAddress !== null) setDefaultAddress(savedAddress);
|
||||
if (savedPhone !== null) setDefaultPhone(savedPhone);
|
||||
@@ -79,6 +88,42 @@ export default function ProfileScreen() {
|
||||
Alert.alert("Enregistré", "Informations par défaut sauvegardées");
|
||||
};
|
||||
|
||||
const handleLinkTelegram = async () => {
|
||||
setTelegramLoading(true);
|
||||
const res = await generateTelegramLinkToken();
|
||||
setTelegramLoading(false);
|
||||
if (res.error || !res.link_url) {
|
||||
Alert.alert("Erreur", res.error || "Service Telegram non disponible");
|
||||
return;
|
||||
}
|
||||
Alert.alert(
|
||||
"Lier Telegram",
|
||||
"Appuyez sur OK pour ouvrir le bot Telegram et envoyer le message de liaison.",
|
||||
[
|
||||
{ text: "Annuler", style: "cancel" },
|
||||
{ text: "Ouvrir Telegram", onPress: () => Linking.openURL(res.link_url!) },
|
||||
],
|
||||
);
|
||||
};
|
||||
|
||||
const handleUnlinkTelegram = async () => {
|
||||
Alert.alert(
|
||||
"Délier Telegram",
|
||||
"Vous ne recevrez plus de notifications Telegram. Continuer ?",
|
||||
[
|
||||
{ text: "Annuler", style: "cancel" },
|
||||
{
|
||||
text: "Délier",
|
||||
style: "destructive",
|
||||
onPress: async () => {
|
||||
await unlinkTelegram();
|
||||
setTelegramLinked(false);
|
||||
},
|
||||
},
|
||||
],
|
||||
);
|
||||
};
|
||||
|
||||
const saveContact = async () => {
|
||||
setSavingContact(true);
|
||||
const res = await updateMyProfile({ nom: nom.trim(), prenom: prenom.trim(), telephone: telephone.trim() });
|
||||
@@ -280,6 +325,45 @@ export default function ProfileScreen() {
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Carte Telegram */}
|
||||
{telegramEnabled && (
|
||||
<View style={styles.card}>
|
||||
<View style={styles.cardTitle}>
|
||||
<Ionicons name="paper-plane-outline" size={18} color="#2AABEE" />
|
||||
<Text style={styles.cardTitleText}>Notifications Telegram</Text>
|
||||
</View>
|
||||
<Text style={styles.hint}>
|
||||
Recevez vos notifications sur Telegram même quand l'application est fermée.
|
||||
</Text>
|
||||
{telegramLinked ? (
|
||||
<View>
|
||||
<View style={{ flexDirection: "row", alignItems: "center", gap: spacing.s, marginBottom: spacing.m }}>
|
||||
<Ionicons name="checkmark-circle" size={16} color="#10b981" />
|
||||
<Text style={{ color: "#10b981", fontSize: fontSize.sm }}>Compte Telegram lié</Text>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
style={[styles.saveBtn, { backgroundColor: "#ef444422", borderWidth: 1, borderColor: "#ef444466" }]}
|
||||
onPress={handleUnlinkTelegram}
|
||||
>
|
||||
<Ionicons name="unlink-outline" size={16} color="#ef4444" />
|
||||
<Text style={[styles.saveBtnText, { color: "#ef4444" }]}>Délier Telegram</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
) : (
|
||||
<TouchableOpacity
|
||||
style={[styles.saveBtn, { backgroundColor: "#2AABEE" }]}
|
||||
onPress={handleLinkTelegram}
|
||||
disabled={telegramLoading}
|
||||
>
|
||||
<Ionicons name="paper-plane-outline" size={16} color="#fff" />
|
||||
<Text style={styles.saveBtnText}>
|
||||
{telegramLoading ? "Génération du lien..." : "Lier mon compte Telegram"}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
import * as Notifications from "expo-notifications";
|
||||
import * as Device from "expo-device";
|
||||
import Constants from "expo-constants";
|
||||
import { Platform, Alert } from "react-native";
|
||||
import { registerPushToken } from "../api/api";
|
||||
|
||||
// Comportement des notifs reçues en foreground
|
||||
Notifications.setNotificationHandler({
|
||||
handleNotification: async () => ({
|
||||
shouldPlaySound: true,
|
||||
shouldSetBadge: true,
|
||||
shouldShowBanner: true,
|
||||
shouldShowList: true,
|
||||
}),
|
||||
});
|
||||
|
||||
function handleRegistrationError(errorMessage: string) {
|
||||
console.error("[PUSH]", errorMessage);
|
||||
Alert.alert("Push Notification Error", errorMessage);
|
||||
}
|
||||
|
||||
export async function registerForPushNotifications(): Promise<
|
||||
string | undefined
|
||||
> {
|
||||
if (Platform.OS === "android") {
|
||||
await Notifications.setNotificationChannelAsync("default", {
|
||||
name: "default",
|
||||
importance: Notifications.AndroidImportance.MAX,
|
||||
vibrationPattern: [0, 250, 250, 250],
|
||||
lightColor: "#FF231F7C",
|
||||
});
|
||||
await Notifications.setNotificationChannelAsync("orders", {
|
||||
name: "Commandes",
|
||||
importance: Notifications.AndroidImportance.MAX,
|
||||
vibrationPattern: [0, 250, 250, 250],
|
||||
lightColor: "#7c3aed",
|
||||
});
|
||||
}
|
||||
|
||||
if (Device.isDevice) {
|
||||
const { status: existingStatus } =
|
||||
await Notifications.getPermissionsAsync();
|
||||
let finalStatus = existingStatus;
|
||||
if (existingStatus !== "granted") {
|
||||
const { status } = await Notifications.requestPermissionsAsync();
|
||||
finalStatus = status;
|
||||
}
|
||||
if (finalStatus !== "granted") {
|
||||
handleRegistrationError(
|
||||
"Permission not granted to get push token for push notification!",
|
||||
);
|
||||
return;
|
||||
}
|
||||
const projectId =
|
||||
Constants?.expoConfig?.extra?.eas?.projectId ??
|
||||
Constants?.easConfig?.projectId;
|
||||
if (!projectId) {
|
||||
handleRegistrationError("Project ID not found");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const pushTokenString = (
|
||||
await Notifications.getExpoPushTokenAsync({ projectId })
|
||||
).data;
|
||||
console.log("[PUSH] Token obtenu:", pushTokenString);
|
||||
await registerPushToken(pushTokenString);
|
||||
return pushTokenString;
|
||||
} catch (e: unknown) {
|
||||
handleRegistrationError(`${e}`);
|
||||
}
|
||||
} else {
|
||||
handleRegistrationError(
|
||||
"Must use physical device for push notifications",
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user