203 lines
5.6 KiB
TypeScript
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();
|
|
};
|