Amba-Agent-App/lib/utils/alertUtils.ts
2026-01-16 00:22:35 +03:00

203 lines
5.6 KiB
TypeScript

import { Alert, Platform } from 'react-native';
// Track active alerts to prevent stacking
const activeAlerts = new Set<string>();
export interface AlertButton {
text?: string;
onPress?: () => void;
style?: 'default' | 'cancel' | 'destructive';
}
export interface ShowAlertOptions {
title: string;
message?: string;
buttons?: AlertButton[];
cancelable?: boolean;
onDismiss?: () => void;
alertId?: string;
}
/**
* Cross-platform alert that works on web, iOS, and Android
* On web, uses window.confirm() for confirmation dialogs and window.alert() for simple alerts
* On native platforms, uses React Native's Alert.alert()
*/
export const showAlert = (options: ShowAlertOptions) => {
const {
title,
message,
buttons,
cancelable,
onDismiss,
alertId,
} = options;
// Generate unique ID for this alert
const id = alertId || `${title}-${message || ''}`;
// Prevent stacking
if (activeAlerts.has(id)) {
return;
}
activeAlerts.add(id);
if (Platform.OS === 'web') {
// Web implementation
handleWebAlert(id, title, message, buttons, onDismiss);
} else {
// Native implementation
handleNativeAlert(id, title, message, buttons, cancelable, onDismiss);
}
};
/**
* Handle alerts on web using window.confirm/alert
*/
const handleWebAlert = (
id: string,
title: string,
message?: string,
buttons?: AlertButton[],
onDismiss?: () => void
) => {
const fullMessage = message ? `${title}\n\n${message}` : title;
// Use setTimeout to ensure the alert runs after current execution
setTimeout(() => {
try {
if (!buttons || buttons.length === 0) {
// Simple alert with OK button
window.alert(fullMessage);
activeAlerts.delete(id);
onDismiss?.();
} else if (buttons.length === 1) {
// Single button - use alert
window.alert(fullMessage);
activeAlerts.delete(id);
buttons[0].onPress?.();
onDismiss?.();
} else if (buttons.length === 2) {
// Two buttons - use confirm
// Find cancel and confirm buttons
const cancelButton = buttons.find(b => b.style === 'cancel');
const confirmButton = buttons.find(b => b.style !== 'cancel') || buttons[1];
const result = window.confirm(fullMessage);
activeAlerts.delete(id);
if (result) {
// User clicked OK - trigger the non-cancel button
confirmButton?.onPress?.();
} else {
// User clicked Cancel
cancelButton?.onPress?.();
}
onDismiss?.();
} else {
// More than 2 buttons - use confirm for first two, warn about limitation
console.warn('Web alerts only support up to 2 buttons. Additional buttons will be ignored.');
const cancelButton = buttons.find(b => b.style === 'cancel');
const confirmButton = buttons.find(b => b.style === 'destructive') ||
buttons.find(b => b.style !== 'cancel') ||
buttons[0];
const result = window.confirm(fullMessage);
activeAlerts.delete(id);
if (result) {
confirmButton?.onPress?.();
} else {
cancelButton?.onPress?.();
}
onDismiss?.();
}
} catch (error) {
activeAlerts.delete(id);
console.error('Error showing web alert:', error);
}
}, 0);
};
/**
* Handle alerts on native platforms using React Native Alert
*/
const handleNativeAlert = (
id: string,
title: string,
message?: string,
buttons?: AlertButton[],
cancelable?: boolean,
onDismiss?: () => void
) => {
// Wrap button callbacks to clear the alert ID
const wrappedButtons = buttons?.map(button => ({
...button,
onPress: () => {
activeAlerts.delete(id);
button.onPress?.();
},
}));
// Wrap onDismiss to clear the alert ID
const wrappedOptions = {
cancelable,
onDismiss: () => {
activeAlerts.delete(id);
onDismiss?.();
},
};
Alert.alert(title, message, wrappedButtons, wrappedOptions);
};
/**
* Simple alert with just an OK button
* Works on all platforms
*/
export const alertOk = (title: string, message?: string, onOk?: () => void) => {
showAlert({
title,
message,
buttons: [{ text: 'OK', onPress: onOk }],
});
};
/**
* Confirmation dialog with Cancel and Confirm buttons
* Works on all platforms
*/
export const alertConfirm = (
title: string,
message: string,
onConfirm: () => void,
onCancel?: () => void,
confirmText: string = 'Confirm',
cancelText: string = 'Cancel',
destructive: boolean = false
) => {
showAlert({
title,
message,
buttons: [
{ text: cancelText, style: 'cancel', onPress: onCancel },
{ text: confirmText, style: destructive ? 'destructive' : 'default', onPress: onConfirm },
],
});
};
/**
* Clear a specific alert from the active set (useful for manual cleanup)
*/
export const clearAlert = (alertId: string) => {
activeAlerts.delete(alertId);
};
/**
* Clear all active alerts (useful for cleanup on unmount)
*/
export const clearAllAlerts = () => {
activeAlerts.clear();
};