chore: add notification telegram
This commit is contained in:
@@ -36,15 +36,6 @@
|
|||||||
"plugins": [
|
"plugins": [
|
||||||
"expo-font",
|
"expo-font",
|
||||||
"expo-location",
|
"expo-location",
|
||||||
[
|
|
||||||
"expo-notifications",
|
|
||||||
{
|
|
||||||
"icon": "./assets/icon.png",
|
|
||||||
"color": "#ffffff",
|
|
||||||
"androidMode": "default",
|
|
||||||
"androidCollapsedTitle": "#{unread_notifications} nouvelles commandes"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
"expo-build-properties",
|
"expo-build-properties",
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -760,6 +760,8 @@ export interface AppSettings {
|
|||||||
nowpayments_api_key: string;
|
nowpayments_api_key: string;
|
||||||
nowpayments_ipn_secret: string;
|
nowpayments_ipn_secret: string;
|
||||||
nowpayments_currencies: string[];
|
nowpayments_currencies: string[];
|
||||||
|
telegram_bot_token: string;
|
||||||
|
telegram_bot_username: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getSettings = async (): Promise<{
|
export const getSettings = async (): Promise<{
|
||||||
@@ -778,25 +780,6 @@ export const getSettings = async (): Promise<{
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const registerAdminPushToken = async (
|
|
||||||
pushToken: string,
|
|
||||||
): Promise<void> => {
|
|
||||||
try {
|
|
||||||
await apiClient.post(`${V2}/admin/protected/push-token`, {
|
|
||||||
push_token: pushToken,
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
/* ignore */
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const unregisterAdminPushToken = async (): Promise<void> => {
|
|
||||||
try {
|
|
||||||
await apiClient.delete(`${V2}/admin/protected/push-token`);
|
|
||||||
} catch {
|
|
||||||
/* ignore */
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface AppNotification {
|
export interface AppNotification {
|
||||||
command_id: number;
|
command_id: number;
|
||||||
@@ -818,6 +801,37 @@ export const markAdminNotificationsRead = async (): Promise<void> => {
|
|||||||
await apiClient.post(`${V2}/admin/protected/notifications/read`);
|
await apiClient.post(`${V2}/admin/protected/notifications/read`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// TELEGRAM — ADMIN
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
export const getAdminTelegramStatus = async (): Promise<{ linked: boolean; enabled: boolean }> => {
|
||||||
|
try {
|
||||||
|
const { data } = await apiClient.get(`${V2}/admin/protected/telegram/status`);
|
||||||
|
return data;
|
||||||
|
} catch {
|
||||||
|
return { linked: false, enabled: false };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateAdminLinkToken = async (): Promise<{
|
||||||
|
link_url?: string;
|
||||||
|
error?: string;
|
||||||
|
}> => {
|
||||||
|
try {
|
||||||
|
const { data } = await apiClient.post(`${V2}/admin/protected/telegram/link-token`);
|
||||||
|
return data;
|
||||||
|
} catch (e: any) {
|
||||||
|
return { error: e?.response?.data?.error || "Erreur" };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const unlinkAdminTelegram = async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
await apiClient.delete(`${V2}/admin/protected/telegram/unlink`);
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
};
|
||||||
|
|
||||||
export const updateSettings = async (
|
export const updateSettings = async (
|
||||||
settings: AppSettings,
|
settings: AppSettings,
|
||||||
): Promise<{ success: boolean; settings?: AppSettings; error?: string }> => {
|
): Promise<{ success: boolean; settings?: AppSettings; error?: string }> => {
|
||||||
|
|||||||
@@ -354,24 +354,6 @@ export interface PublicSettings {
|
|||||||
pool_keys: string[];
|
pool_keys: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const registerCabinePushToken = async (
|
|
||||||
pushToken: string,
|
|
||||||
): Promise<void> => {
|
|
||||||
try {
|
|
||||||
await apiClient.post(`${API}/push-token`, { push_token: pushToken });
|
|
||||||
} catch {
|
|
||||||
/* ignore */
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const unregisterCabinePushToken = async (): Promise<void> => {
|
|
||||||
try {
|
|
||||||
await apiClient.delete(`${API}/push-token`);
|
|
||||||
} catch {
|
|
||||||
/* ignore */
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface AppNotification {
|
export interface AppNotification {
|
||||||
command_id: number;
|
command_id: number;
|
||||||
type: string;
|
type: string;
|
||||||
@@ -454,3 +436,36 @@ export const getAllAddresses = async (): Promise<
|
|||||||
correct_address: a.correct_address ?? a.CorrectAddress ?? "",
|
correct_address: a.correct_address ?? a.CorrectAddress ?? "",
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// TELEGRAM — CABINE
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
const CABINE_API = "https://5.181.0.112.nip.io/api/v1/cabine";
|
||||||
|
|
||||||
|
export const getCabineTelegramStatus = async (): Promise<{ linked: boolean; enabled: boolean }> => {
|
||||||
|
try {
|
||||||
|
const { data } = await apiClient.get(`${CABINE_API}/telegram/status`);
|
||||||
|
return data;
|
||||||
|
} catch {
|
||||||
|
return { linked: false, enabled: false };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateCabineLinkToken = async (): Promise<{
|
||||||
|
link_url?: string;
|
||||||
|
error?: string;
|
||||||
|
}> => {
|
||||||
|
try {
|
||||||
|
const { data } = await apiClient.post(`${CABINE_API}/telegram/link-token`);
|
||||||
|
return data;
|
||||||
|
} catch (e: any) {
|
||||||
|
return { error: e?.response?.data?.error || "Erreur" };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const unlinkCabineTelegram = async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
await apiClient.delete(`${CABINE_API}/telegram/unlink`);
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
};
|
||||||
|
|||||||
@@ -310,3 +310,36 @@ export const markLivreurNotificationsRead = async (): Promise<{
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// TELEGRAM — LIVREUR
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
export const getLivreurTelegramStatus = async (): Promise<{ linked: boolean; enabled: boolean }> => {
|
||||||
|
try {
|
||||||
|
const { data } = await apiClient.get(`${API}/telegram/status`);
|
||||||
|
return data;
|
||||||
|
} catch {
|
||||||
|
return { linked: false, enabled: false };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateLivreurLinkToken = async (): Promise<{
|
||||||
|
link_url?: string;
|
||||||
|
expires_in?: number;
|
||||||
|
error?: string;
|
||||||
|
}> => {
|
||||||
|
try {
|
||||||
|
const { data } = await apiClient.post(`${API}/telegram/link-token`);
|
||||||
|
return data;
|
||||||
|
} catch (e: any) {
|
||||||
|
return { error: e?.response?.data?.error || "Erreur" };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const unlinkLivreurTelegram = async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
await apiClient.delete(`${API}/telegram/unlink`);
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import {
|
|||||||
StyleSheet,
|
StyleSheet,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
RefreshControl,
|
RefreshControl,
|
||||||
|
TouchableOpacity,
|
||||||
|
Linking,
|
||||||
|
Alert,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import { spacing, fontSize, borderRadius } from "../../theme";
|
import { spacing, fontSize, borderRadius } from "../../theme";
|
||||||
@@ -16,10 +19,10 @@ import {
|
|||||||
getAllClients,
|
getAllClients,
|
||||||
getAvailableDeliveryPersons,
|
getAvailableDeliveryPersons,
|
||||||
getCommandCountByStatus,
|
getCommandCountByStatus,
|
||||||
registerAdminPushToken,
|
getAdminTelegramStatus,
|
||||||
unregisterAdminPushToken,
|
generateAdminLinkToken,
|
||||||
|
unlinkAdminTelegram,
|
||||||
} from "../../api/api_admin";
|
} from "../../api/api_admin";
|
||||||
import { getExpoPushToken } from "../../utils/pushTokenUtils";
|
|
||||||
import LoadingSpinner from "../../components/ui/LoadingSpinner";
|
import LoadingSpinner from "../../components/ui/LoadingSpinner";
|
||||||
|
|
||||||
interface StatCard {
|
interface StatCard {
|
||||||
@@ -35,14 +38,9 @@ export default function DashboardScreen() {
|
|||||||
const [stats, setStats] = useState<StatCard[]>([]);
|
const [stats, setStats] = useState<StatCard[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [refreshing, setRefreshing] = useState(false);
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
|
const [tgLinked, setTgLinked] = useState(false);
|
||||||
// Enregistrement push token admin au montage
|
const [tgEnabled, setTgEnabled] = useState(false);
|
||||||
useEffect(() => {
|
const [tgLoading, setTgLoading] = useState(false);
|
||||||
getExpoPushToken().then((token) => {
|
|
||||||
if (token) registerAdminPushToken(token);
|
|
||||||
});
|
|
||||||
return () => { unregisterAdminPushToken(); };
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const loadStats = useCallback(async () => {
|
const loadStats = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -106,8 +104,27 @@ export default function DashboardScreen() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadStats();
|
loadStats();
|
||||||
|
getAdminTelegramStatus().then((s) => { setTgLinked(s.linked); setTgEnabled(s.enabled); });
|
||||||
}, [loadStats]);
|
}, [loadStats]);
|
||||||
|
|
||||||
|
const handleLinkTelegram = async () => {
|
||||||
|
setTgLoading(true);
|
||||||
|
const res = await generateAdminLinkToken();
|
||||||
|
setTgLoading(false);
|
||||||
|
if (res.error || !res.link_url) {
|
||||||
|
Alert.alert("Erreur", res.error || "Service Telegram non disponible");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Linking.openURL(res.link_url);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUnlinkTelegram = () => {
|
||||||
|
Alert.alert("Délier Telegram", "Vous ne recevrez plus de notifications Telegram.", [
|
||||||
|
{ text: "Annuler", style: "cancel" },
|
||||||
|
{ text: "Délier", style: "destructive", onPress: async () => { await unlinkAdminTelegram(); setTgLinked(false); } },
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
const onRefresh = async () => {
|
const onRefresh = async () => {
|
||||||
setRefreshing(true);
|
setRefreshing(true);
|
||||||
await loadStats();
|
await loadStats();
|
||||||
@@ -202,6 +219,32 @@ export default function DashboardScreen() {
|
|||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{tgEnabled && (
|
||||||
|
<View style={{ margin: spacing.l, backgroundColor: colors.bgCard, borderRadius: borderRadius.md, padding: spacing.l, borderWidth: 1, borderColor: colors.borderLight }}>
|
||||||
|
<View style={{ flexDirection: "row", alignItems: "center", gap: spacing.s, marginBottom: spacing.m }}>
|
||||||
|
<Ionicons name="paper-plane-outline" size={18} color="#2AABEE" />
|
||||||
|
<Text style={{ color: colors.textPrimary, fontSize: fontSize.md, fontWeight: "600" }}>Notifications Telegram</Text>
|
||||||
|
</View>
|
||||||
|
{tgLinked ? (
|
||||||
|
<View>
|
||||||
|
<View style={{ flexDirection: "row", alignItems: "center", gap: spacing.s, marginBottom: spacing.s }}>
|
||||||
|
<Ionicons name="checkmark-circle" size={14} color={colors.success} />
|
||||||
|
<Text style={{ color: colors.success, fontSize: fontSize.sm }}>Compte Telegram lié</Text>
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity onPress={handleUnlinkTelegram} style={{ flexDirection: "row", alignItems: "center", gap: spacing.s, padding: spacing.s, borderRadius: borderRadius.sm, borderWidth: 1, borderColor: colors.danger + "66" }}>
|
||||||
|
<Ionicons name="unlink-outline" size={14} color={colors.danger} />
|
||||||
|
<Text style={{ color: colors.danger, fontSize: fontSize.sm }}>Délier Telegram</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<TouchableOpacity onPress={handleLinkTelegram} disabled={tgLoading} style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", gap: spacing.s, padding: spacing.m, borderRadius: borderRadius.sm, backgroundColor: "#2AABEE" }}>
|
||||||
|
<Ionicons name="paper-plane-outline" size={14} color="#fff" />
|
||||||
|
<Text style={{ color: "#fff", fontSize: fontSize.sm, fontWeight: "600" }}>{tgLoading ? "Génération..." : "Lier Telegram"}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -659,6 +659,8 @@ export default function SettingsScreen() {
|
|||||||
nowpayments_api_key: "",
|
nowpayments_api_key: "",
|
||||||
nowpayments_ipn_secret: "",
|
nowpayments_ipn_secret: "",
|
||||||
nowpayments_currencies: [],
|
nowpayments_currencies: [],
|
||||||
|
telegram_bot_token: "",
|
||||||
|
telegram_bot_username: "",
|
||||||
});
|
});
|
||||||
const [showApiKey, setShowApiKey] = useState(false);
|
const [showApiKey, setShowApiKey] = useState(false);
|
||||||
const [showIpnSecret, setShowIpnSecret] = useState(false);
|
const [showIpnSecret, setShowIpnSecret] = useState(false);
|
||||||
@@ -1300,6 +1302,53 @@ export default function SettingsScreen() {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* ============================================ */}
|
||||||
|
{/* 🤖 TELEGRAM */}
|
||||||
|
{/* ============================================ */}
|
||||||
|
<View style={s.section}>
|
||||||
|
<Text style={s.sectionTitle}>Notifications Telegram</Text>
|
||||||
|
<View style={{ paddingHorizontal: spacing.l, paddingBottom: spacing.l, gap: spacing.m }}>
|
||||||
|
<Text style={s.rowDesc}>
|
||||||
|
Configurez le bot Telegram pour envoyer des notifications aux utilisateurs qui ont lié leur compte.
|
||||||
|
</Text>
|
||||||
|
<View>
|
||||||
|
<Text style={[s.rowLabel, { marginBottom: spacing.xs }]}>Token du bot</Text>
|
||||||
|
<Text style={[s.rowDesc, { marginBottom: spacing.s }]}>
|
||||||
|
Obtenu via @BotFather avec la commande /newbot
|
||||||
|
</Text>
|
||||||
|
<TextInput
|
||||||
|
style={[s.input, { fontFamily: "monospace" }]}
|
||||||
|
value={settings.telegram_bot_token}
|
||||||
|
onChangeText={(v) => setSettings((p) => ({ ...p, telegram_bot_token: v }))}
|
||||||
|
placeholder="123456:ABCdefGHI..."
|
||||||
|
placeholderTextColor={colors.textMuted}
|
||||||
|
autoCapitalize="none"
|
||||||
|
secureTextEntry={true}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View>
|
||||||
|
<Text style={[s.rowLabel, { marginBottom: spacing.xs }]}>Username du bot</Text>
|
||||||
|
<Text style={[s.rowDesc, { marginBottom: spacing.s }]}>
|
||||||
|
Sans le @, ex : MonBotNotifications
|
||||||
|
</Text>
|
||||||
|
<TextInput
|
||||||
|
style={s.input}
|
||||||
|
value={settings.telegram_bot_username}
|
||||||
|
onChangeText={(v) => setSettings((p) => ({ ...p, telegram_bot_username: v }))}
|
||||||
|
placeholder="MonBotNotifications"
|
||||||
|
placeholderTextColor={colors.textMuted}
|
||||||
|
autoCapitalize="none"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
{settings.telegram_bot_token !== "" && settings.telegram_bot_username !== "" && (
|
||||||
|
<View style={{ flexDirection: "row", alignItems: "center", gap: spacing.s, padding: spacing.m, backgroundColor: "#10b98122", borderRadius: borderRadius.sm }}>
|
||||||
|
<Ionicons name="checkmark-circle" size={16} color="#10b981" />
|
||||||
|
<Text style={{ color: "#10b981", fontSize: fontSize.sm }}>Bot configuré — @{settings.telegram_bot_username}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={s.saveButton}
|
style={s.saveButton}
|
||||||
onPress={handleSave}
|
onPress={handleSave}
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import {
|
|||||||
StyleSheet,
|
StyleSheet,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
RefreshControl,
|
RefreshControl,
|
||||||
|
TouchableOpacity,
|
||||||
|
Linking,
|
||||||
|
Alert,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import { spacing, fontSize, borderRadius } from "../../theme";
|
import { spacing, fontSize, borderRadius } from "../../theme";
|
||||||
@@ -12,21 +15,21 @@ import { useTheme } from "../../context/ThemeContext";
|
|||||||
import { shadows } from "../../theme/shadows";
|
import { shadows } from "../../theme/shadows";
|
||||||
import { useAuth } from "../../auth/AuthContext";
|
import { useAuth } from "../../auth/AuthContext";
|
||||||
import { getAllCommands } from "../../api/api_admin";
|
import { getAllCommands } from "../../api/api_admin";
|
||||||
import { getAllDeliveryPersonsWithDetails, registerCabinePushToken, unregisterCabinePushToken } from "../../api/api_cabine";
|
import {
|
||||||
import { getExpoPushToken } from "../../utils/pushTokenUtils";
|
getAllDeliveryPersonsWithDetails,
|
||||||
|
getCabineTelegramStatus,
|
||||||
|
generateCabineLinkToken,
|
||||||
|
unlinkCabineTelegram,
|
||||||
|
} from "../../api/api_cabine";
|
||||||
import LoadingSpinner from "../../components/ui/LoadingSpinner";
|
import LoadingSpinner from "../../components/ui/LoadingSpinner";
|
||||||
|
|
||||||
export default function DashboardScreen() {
|
export default function DashboardScreen() {
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
const { username } = useAuth();
|
const { username } = useAuth();
|
||||||
|
|
||||||
// Enregistrement push token cabine au montage
|
const [tgLinked, setTgLinked] = useState(false);
|
||||||
useEffect(() => {
|
const [tgEnabled, setTgEnabled] = useState(false);
|
||||||
getExpoPushToken().then((token) => {
|
const [tgLoading, setTgLoading] = useState(false);
|
||||||
if (token) registerCabinePushToken(token);
|
|
||||||
});
|
|
||||||
return () => { unregisterCabinePushToken(); };
|
|
||||||
}, []);
|
|
||||||
const [stats, setStats] = useState<
|
const [stats, setStats] = useState<
|
||||||
Array<{
|
Array<{
|
||||||
label: string;
|
label: string;
|
||||||
@@ -97,7 +100,27 @@ export default function DashboardScreen() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadStats();
|
loadStats();
|
||||||
|
getCabineTelegramStatus().then((s) => { setTgLinked(s.linked); setTgEnabled(s.enabled); });
|
||||||
}, [loadStats]);
|
}, [loadStats]);
|
||||||
|
|
||||||
|
const handleLinkTelegram = async () => {
|
||||||
|
setTgLoading(true);
|
||||||
|
const res = await generateCabineLinkToken();
|
||||||
|
setTgLoading(false);
|
||||||
|
if (res.error || !res.link_url) {
|
||||||
|
Alert.alert("Erreur", res.error || "Service Telegram non disponible");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Linking.openURL(res.link_url);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUnlinkTelegram = () => {
|
||||||
|
Alert.alert("Délier Telegram", "Vous ne recevrez plus de notifications Telegram.", [
|
||||||
|
{ text: "Annuler", style: "cancel" },
|
||||||
|
{ text: "Délier", style: "destructive", onPress: async () => { await unlinkCabineTelegram(); setTgLinked(false); } },
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
const onRefresh = async () => {
|
const onRefresh = async () => {
|
||||||
setRefreshing(true);
|
setRefreshing(true);
|
||||||
await loadStats();
|
await loadStats();
|
||||||
@@ -191,6 +214,32 @@ export default function DashboardScreen() {
|
|||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{tgEnabled && (
|
||||||
|
<View style={{ margin: spacing.l, backgroundColor: colors.bgCard, borderRadius: borderRadius.md, padding: spacing.l, borderWidth: 1, borderColor: colors.borderLight }}>
|
||||||
|
<View style={{ flexDirection: "row", alignItems: "center", gap: spacing.s, marginBottom: spacing.m }}>
|
||||||
|
<Ionicons name="paper-plane-outline" size={18} color="#2AABEE" />
|
||||||
|
<Text style={{ color: colors.textPrimary, fontSize: fontSize.md, fontWeight: "600" }}>Notifications Telegram</Text>
|
||||||
|
</View>
|
||||||
|
{tgLinked ? (
|
||||||
|
<View>
|
||||||
|
<View style={{ flexDirection: "row", alignItems: "center", gap: spacing.s, marginBottom: spacing.s }}>
|
||||||
|
<Ionicons name="checkmark-circle" size={14} color={colors.success} />
|
||||||
|
<Text style={{ color: colors.success, fontSize: fontSize.sm }}>Compte Telegram lié</Text>
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity onPress={handleUnlinkTelegram} style={{ flexDirection: "row", alignItems: "center", gap: spacing.s, padding: spacing.s, borderRadius: borderRadius.sm, borderWidth: 1, borderColor: colors.danger + "66" }}>
|
||||||
|
<Ionicons name="unlink-outline" size={14} color={colors.danger} />
|
||||||
|
<Text style={{ color: colors.danger, fontSize: fontSize.sm }}>Délier Telegram</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<TouchableOpacity onPress={handleLinkTelegram} disabled={tgLoading} style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", gap: spacing.s, padding: spacing.m, borderRadius: borderRadius.sm, backgroundColor: "#2AABEE" }}>
|
||||||
|
<Ionicons name="paper-plane-outline" size={14} color="#fff" />
|
||||||
|
<Text style={{ color: "#fff", fontSize: fontSize.sm, fontWeight: "600" }}>{tgLoading ? "Génération..." : "Lier Telegram"}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ import {
|
|||||||
startDelivery,
|
startDelivery,
|
||||||
updateDeliveryStatus,
|
updateDeliveryStatus,
|
||||||
updateMyLocation,
|
updateMyLocation,
|
||||||
|
getLivreurTelegramStatus,
|
||||||
|
generateLivreurLinkToken,
|
||||||
|
unlinkLivreurTelegram,
|
||||||
} from "../../api/api_delivery";
|
} from "../../api/api_delivery";
|
||||||
import { geocodeAddress, calculateRoute } from "../../api/tomtom";
|
import { geocodeAddress, calculateRoute } from "../../api/tomtom";
|
||||||
import type { RouteInfo } from "../../api/tomtom";
|
import type { RouteInfo } from "../../api/tomtom";
|
||||||
@@ -106,6 +109,11 @@ export default function DashboardScreen() {
|
|||||||
|
|
||||||
const [detailsDelivery, setDetailsDelivery] =
|
const [detailsDelivery, setDetailsDelivery] =
|
||||||
useState<EnrichedDelivery | null>(null);
|
useState<EnrichedDelivery | null>(null);
|
||||||
|
|
||||||
|
// Telegram
|
||||||
|
const [tgLinked, setTgLinked] = useState(false);
|
||||||
|
const [tgEnabled, setTgEnabled] = useState(false);
|
||||||
|
const [tgLoading, setTgLoading] = useState(false);
|
||||||
const [cancelModal, setCancelModal] = useState<{
|
const [cancelModal, setCancelModal] = useState<{
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
deliveryId: number | null;
|
deliveryId: number | null;
|
||||||
@@ -279,6 +287,7 @@ export default function DashboardScreen() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadData();
|
loadData();
|
||||||
|
getLivreurTelegramStatus().then((s) => { setTgLinked(s.linked); setTgEnabled(s.enabled); });
|
||||||
}, [loadData]);
|
}, [loadData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -741,6 +750,23 @@ export default function DashboardScreen() {
|
|||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
// Header avec TomTomMap
|
// Header avec TomTomMap
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
const handleLinkTelegram = async () => {
|
||||||
|
setTgLoading(true);
|
||||||
|
const res = await generateLivreurLinkToken();
|
||||||
|
setTgLoading(false);
|
||||||
|
if (res.error || !res.link_url) {
|
||||||
|
showError("Erreur", res.error || "Service Telegram non disponible");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Linking.openURL(res.link_url);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUnlinkTelegram = async () => {
|
||||||
|
await unlinkLivreurTelegram();
|
||||||
|
setTgLinked(false);
|
||||||
|
showSuccess("Telegram délié", "Vous ne recevrez plus de notifications Telegram.");
|
||||||
|
};
|
||||||
|
|
||||||
const renderHeader = () => (
|
const renderHeader = () => (
|
||||||
<View>
|
<View>
|
||||||
{/* Carte TomTom — toujours montée pour que le ref soit disponible */}
|
{/* Carte TomTom — toujours montée pour que le ref soit disponible */}
|
||||||
@@ -863,6 +889,33 @@ export default function DashboardScreen() {
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Carte Telegram */}
|
||||||
|
{tgEnabled && (
|
||||||
|
<View style={{ marginHorizontal: spacing.l, marginBottom: spacing.m, backgroundColor: colors.bgCard, borderRadius: borderRadius.md, padding: spacing.l, borderWidth: 1, borderColor: colors.borderLight }}>
|
||||||
|
<View style={{ flexDirection: "row", alignItems: "center", gap: spacing.s, marginBottom: spacing.s }}>
|
||||||
|
<Ionicons name="paper-plane-outline" size={18} color="#2AABEE" />
|
||||||
|
<Text style={{ color: colors.textPrimary, fontSize: fontSize.md, fontWeight: "600" }}>Notifications Telegram</Text>
|
||||||
|
</View>
|
||||||
|
{tgLinked ? (
|
||||||
|
<View>
|
||||||
|
<View style={{ flexDirection: "row", alignItems: "center", gap: spacing.s, marginBottom: spacing.s }}>
|
||||||
|
<Ionicons name="checkmark-circle" size={14} color={colors.success} />
|
||||||
|
<Text style={{ color: colors.success, fontSize: fontSize.sm }}>Compte lié</Text>
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity onPress={handleUnlinkTelegram} style={{ flexDirection: "row", alignItems: "center", gap: spacing.s, padding: spacing.s, borderRadius: borderRadius.sm, borderWidth: 1, borderColor: colors.danger + "66" }}>
|
||||||
|
<Ionicons name="unlink-outline" size={14} color={colors.danger} />
|
||||||
|
<Text style={{ color: colors.danger, fontSize: fontSize.sm }}>Délier</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<TouchableOpacity onPress={handleLinkTelegram} disabled={tgLoading} style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", gap: spacing.s, padding: spacing.m, borderRadius: borderRadius.sm, backgroundColor: "#2AABEE" }}>
|
||||||
|
<Ionicons name="paper-plane-outline" size={14} color="#fff" />
|
||||||
|
<Text style={{ color: "#fff", fontSize: fontSize.sm, fontWeight: "600" }}>{tgLoading ? "Génération..." : "Lier Telegram"}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
<Text style={styles.sectionTitle}>
|
<Text style={styles.sectionTitle}>
|
||||||
Mes livraisons ({deliveries.length})
|
Mes livraisons ({deliveries.length})
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
import * as Notifications from "expo-notifications";
|
|
||||||
import * as Device from "expo-device";
|
|
||||||
import { Platform } from "react-native";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Demande les permissions et retourne le token Expo push.
|
|
||||||
* Retourne null si non supporté (simulateur, refus de permission).
|
|
||||||
*/
|
|
||||||
export async function getExpoPushToken(): Promise<string | null> {
|
|
||||||
if (!Device.isDevice) return null;
|
|
||||||
|
|
||||||
const { status: existing } = await Notifications.getPermissionsAsync();
|
|
||||||
let finalStatus = existing;
|
|
||||||
|
|
||||||
if (existing !== "granted") {
|
|
||||||
const { status } = await Notifications.requestPermissionsAsync();
|
|
||||||
finalStatus = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (finalStatus !== "granted") return null;
|
|
||||||
|
|
||||||
if (Platform.OS === "android") {
|
|
||||||
await Notifications.setNotificationChannelAsync("orders", {
|
|
||||||
name: "Nouvelles commandes",
|
|
||||||
importance: Notifications.AndroidImportance.MAX,
|
|
||||||
vibrationPattern: [0, 250, 250, 250],
|
|
||||||
sound: "default",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const tokenData = await Notifications.getExpoPushTokenAsync();
|
|
||||||
return tokenData.data;
|
|
||||||
} catch {
|
|
||||||
// Google Play Services absent (ex: GrapheneOS) — push non disponible
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-11
@@ -25,8 +25,7 @@
|
|||||||
"predictiveBackGestureEnabled": false,
|
"predictiveBackGestureEnabled": false,
|
||||||
"package": "com.uberstup.clientpanel",
|
"package": "com.uberstup.clientpanel",
|
||||||
"versionCode": 1,
|
"versionCode": 1,
|
||||||
"permissions": ["android.permission.VIBRATE"],
|
"permissions": ["android.permission.VIBRATE"]
|
||||||
"googleServicesFile": "./google-services.json"
|
|
||||||
},
|
},
|
||||||
"web": {
|
"web": {
|
||||||
"favicon": "./assets/icon.png"
|
"favicon": "./assets/icon.png"
|
||||||
@@ -34,15 +33,6 @@
|
|||||||
"plugins": [
|
"plugins": [
|
||||||
"expo-font",
|
"expo-font",
|
||||||
"expo-router",
|
"expo-router",
|
||||||
[
|
|
||||||
"expo-notifications",
|
|
||||||
{
|
|
||||||
"icon": "./assets/icon.png",
|
|
||||||
"color": "#ffffff",
|
|
||||||
"androidMode": "default",
|
|
||||||
"androidCollapsedTitle": "#{unread_notifications} nouvelles notifications"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
"expo-build-properties",
|
"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 {
|
try {
|
||||||
await apiClient.post(`${V1}/push-token`, { push_token: token });
|
const { data } = await apiClient.get(`${V1}/telegram/status`);
|
||||||
|
return data;
|
||||||
} catch {
|
} 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 {
|
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 {
|
} catch {
|
||||||
// silencieux
|
// silencieux
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,9 @@ import React, {
|
|||||||
useCallback,
|
useCallback,
|
||||||
type ReactNode,
|
type ReactNode,
|
||||||
} from "react";
|
} from "react";
|
||||||
import * as Notifications from "expo-notifications";
|
|
||||||
import { getClientNotifications, markNotificationsRead } from "../api/api";
|
import { getClientNotifications, markNotificationsRead } from "../api/api";
|
||||||
import type { ClientNotification } from "../api/api";
|
import type { ClientNotification } from "../api/api";
|
||||||
import { getToken } from "../auth/tokenStorage";
|
import { getToken } from "../auth/tokenStorage";
|
||||||
import { registerForPushNotifications } from "../services/pushNotifications";
|
|
||||||
|
|
||||||
interface ToastData {
|
interface ToastData {
|
||||||
message: string;
|
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
|
// Polling toutes les 15 secondes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchNotifications();
|
fetchNotifications();
|
||||||
|
|||||||
@@ -8,11 +8,12 @@ import {
|
|||||||
Alert,
|
Alert,
|
||||||
KeyboardAvoidingView,
|
KeyboardAvoidingView,
|
||||||
Platform,
|
Platform,
|
||||||
|
Linking,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { useFocusEffect } from "@react-navigation/native";
|
import { useFocusEffect } from "@react-navigation/native";
|
||||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
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 TextInput from "../../components/ui/TextInput";
|
||||||
import Button from "../../components/ui/Button";
|
import Button from "../../components/ui/Button";
|
||||||
import { useTheme } from "../../context/ThemeContext";
|
import { useTheme } from "../../context/ThemeContext";
|
||||||
@@ -39,14 +40,22 @@ export default function ProfileScreen() {
|
|||||||
const [loadingProfile, setLoadingProfile] = useState(true);
|
const [loadingProfile, setLoadingProfile] = useState(true);
|
||||||
const [savingContact, setSavingContact] = useState(false);
|
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 () => {
|
const loadData = useCallback(async () => {
|
||||||
setLoadingProfile(true);
|
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_ADDRESS),
|
||||||
AsyncStorage.getItem(STORAGE_PHONE),
|
AsyncStorage.getItem(STORAGE_PHONE),
|
||||||
AsyncStorage.getItem(STORAGE_SIGNAL),
|
AsyncStorage.getItem(STORAGE_SIGNAL),
|
||||||
getMyProfile(),
|
getMyProfile(),
|
||||||
|
getTelegramStatus(),
|
||||||
]);
|
]);
|
||||||
|
setTelegramLinked(tgStatus.linked);
|
||||||
|
setTelegramEnabled(tgStatus.enabled);
|
||||||
|
|
||||||
if (savedAddress !== null) setDefaultAddress(savedAddress);
|
if (savedAddress !== null) setDefaultAddress(savedAddress);
|
||||||
if (savedPhone !== null) setDefaultPhone(savedPhone);
|
if (savedPhone !== null) setDefaultPhone(savedPhone);
|
||||||
@@ -79,6 +88,42 @@ export default function ProfileScreen() {
|
|||||||
Alert.alert("Enregistré", "Informations par défaut sauvegardées");
|
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 () => {
|
const saveContact = async () => {
|
||||||
setSavingContact(true);
|
setSavingContact(true);
|
||||||
const res = await updateMyProfile({ nom: nom.trim(), prenom: prenom.trim(), telephone: telephone.trim() });
|
const res = await updateMyProfile({ nom: nom.trim(), prenom: prenom.trim(), telephone: telephone.trim() });
|
||||||
@@ -280,6 +325,45 @@ export default function ProfileScreen() {
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</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>
|
</ScrollView>
|
||||||
</KeyboardAvoidingView>
|
</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