feat: Implement alert system and network connectivity handling, refactor error handling across screens
This commit is contained in:
113
components/AlertComponent.tsx
Normal file
113
components/AlertComponent.tsx
Normal file
@ -0,0 +1,113 @@
|
||||
import React, { createContext, useContext, useState, ReactNode } from 'react';
|
||||
import { Modal, View, Text, TouchableOpacity, TouchableWithoutFeedback } from 'react-native';
|
||||
import { CheckCircle, XCircle, Info, AlertTriangle } from 'lucide-react-native';
|
||||
|
||||
type AlertType = 'success' | 'error' | 'info' | 'warning';
|
||||
|
||||
interface AlertContextData {
|
||||
showAlert: (type: AlertType, title: string, message: string) => void;
|
||||
hideAlert: () => void;
|
||||
}
|
||||
|
||||
const AlertContext = createContext<AlertContextData>({} as AlertContextData);
|
||||
|
||||
const ALERT_CONFIG = {
|
||||
success: {
|
||||
icon: CheckCircle,
|
||||
color: 'text-green-600',
|
||||
bgColor: 'bg-green-100',
|
||||
btnColor: 'bg-green-600',
|
||||
},
|
||||
error: {
|
||||
icon: XCircle,
|
||||
color: 'text-red-600',
|
||||
bgColor: 'bg-red-100',
|
||||
btnColor: 'bg-red-600',
|
||||
},
|
||||
info: {
|
||||
icon: Info,
|
||||
color: 'text-sky-600',
|
||||
bgColor: 'bg-sky-100',
|
||||
btnColor: 'bg-sky-600',
|
||||
},
|
||||
warning: {
|
||||
icon: AlertTriangle,
|
||||
color: 'text-orange-600',
|
||||
bgColor: 'bg-orange-100',
|
||||
btnColor: 'bg-orange-600',
|
||||
},
|
||||
};
|
||||
|
||||
export const AlertProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [title, setTitle] = useState('');
|
||||
const [message, setMessage] = useState('');
|
||||
const [type, setType] = useState<AlertType>('info');
|
||||
|
||||
const showAlert = (newType: AlertType, newTitle: string, newMessage: string) => {
|
||||
setType(newType);
|
||||
setTitle(newTitle);
|
||||
setMessage(newMessage);
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
const hideAlert = () => {
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
const { icon: Icon, color, bgColor, btnColor } = ALERT_CONFIG[type];
|
||||
|
||||
return (
|
||||
<AlertContext.Provider value={{ showAlert, hideAlert }}>
|
||||
{children}
|
||||
|
||||
{/* IL COMPONENTE ALERT MODALE */}
|
||||
<Modal
|
||||
transparent
|
||||
visible={visible}
|
||||
animationType="fade"
|
||||
onRequestClose={hideAlert}
|
||||
>
|
||||
{/* Backdrop scuro */}
|
||||
<TouchableOpacity
|
||||
activeOpacity={1}
|
||||
onPress={hideAlert} // Chiude se clicchi fuori (opzionale)
|
||||
className="flex-1 bg-black/60 justify-center items-center px-6"
|
||||
>
|
||||
{/* Contenitore Alert */}
|
||||
<TouchableWithoutFeedback>
|
||||
<View className="bg-white w-full max-w-sm rounded-3xl p-6 items-center shadow-2xl">
|
||||
|
||||
{/* Icona Cerchiata */}
|
||||
<View className={`${bgColor} p-4 rounded-full mb-4`}>
|
||||
<Icon size={32} className={color} strokeWidth={2.5} />
|
||||
</View>
|
||||
|
||||
{/* Testi */}
|
||||
<Text className="text-xl font-bold text-gray-900 text-center mb-2">
|
||||
{title}
|
||||
</Text>
|
||||
|
||||
<Text className="text-lg text-gray-500 text-center leading-relaxed mb-8">
|
||||
{message}
|
||||
</Text>
|
||||
|
||||
{/* Pulsante OK */}
|
||||
<TouchableOpacity
|
||||
onPress={hideAlert}
|
||||
className={`w-full py-3.5 rounded-xl ${btnColor} active:opacity-90 shadow-sm`}
|
||||
>
|
||||
<Text className="text-white text-center font-bold text-lg">
|
||||
Ok, ho capito
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</TouchableOpacity>
|
||||
</Modal>
|
||||
</AlertContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useAlert = () => useContext(AlertContext);
|
||||
48
components/OfflineScreen.tsx
Normal file
48
components/OfflineScreen.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import { View, Text, TouchableOpacity } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { WifiOff, RefreshCw } from 'lucide-react-native';
|
||||
|
||||
interface OfflineScreenProps {
|
||||
onRetry: () => void;
|
||||
isRetrying?: boolean;
|
||||
}
|
||||
|
||||
export default function OfflineScreen({ onRetry, isRetrying = false }: OfflineScreenProps) {
|
||||
return (
|
||||
<SafeAreaView className="flex-1 bg-white">
|
||||
<View className="flex-1 items-center justify-center px-8">
|
||||
{/* Icona con cerchio di sfondo */}
|
||||
<View className="bg-gray-100 p-6 rounded-full mb-6">
|
||||
<WifiOff size={64} className="text-gray-400" />
|
||||
</View>
|
||||
|
||||
<Text className="text-2xl font-bold text-gray-800 mb-2 text-center">
|
||||
Sei Offline
|
||||
</Text>
|
||||
|
||||
<Text className="text-base text-gray-500 text-center mb-10 leading-6">
|
||||
Sembra che non ci sia connessione a internet.{'\n'}Controlla il Wi-Fi o i dati mobili e riprova.
|
||||
</Text>
|
||||
|
||||
{/* Pulsante Riprova */}
|
||||
<TouchableOpacity
|
||||
onPress={onRetry}
|
||||
disabled={isRetrying}
|
||||
className={`flex-row items-center justify-center w-full py-4 rounded-xl gap-4 ${
|
||||
isRetrying ? 'bg-gray-300' : 'bg-[#099499] active:bg-[#077f83]'
|
||||
}`}
|
||||
>
|
||||
<RefreshCw
|
||||
size={20}
|
||||
color="white"
|
||||
className={`${isRetrying ? 'animate-spin' : ''}`} // TODO: animate-spin richiede config tailwind o gestiscilo manualmente
|
||||
/>
|
||||
<Text className="text-white font-bold text-lg">
|
||||
{isRetrying ? 'Controllo...' : 'Riprova'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
import { useAlert } from '@/components/AlertComponent';
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text, Modal, TouchableOpacity, TextInput, ScrollView, Alert } from 'react-native';
|
||||
import { View, Text, Modal, TouchableOpacity, TextInput, ScrollView } from 'react-native';
|
||||
import DateTimePicker, { DateType, useDefaultStyles } from 'react-native-ui-datepicker';
|
||||
import { TimeOffRequestType } from '@/types/types';
|
||||
import { X } from 'lucide-react-native';
|
||||
@ -16,6 +17,7 @@ interface RequestPermitModalProps {
|
||||
|
||||
export default function RequestPermitModal({ visible, types, onClose, onSubmit }: RequestPermitModalProps) {
|
||||
const defaultStyles = useDefaultStyles();
|
||||
const alert = useAlert();
|
||||
const [type, setType] = useState<TimeOffRequestType>(types[0]); // Default to first type
|
||||
const [date, setDate] = useState<string | null>();
|
||||
const [range, setRange] = useState<{
|
||||
@ -61,9 +63,9 @@ export default function RequestPermitModal({ visible, types, onClose, onSubmit }
|
||||
const response = await api.post('/time-off-request/save-request', requestData);
|
||||
|
||||
if (response.data.status === 'success') {
|
||||
Alert.alert('Successo', response.data.message || 'La tua richiesta è stata inviata con successo.');
|
||||
alert.showAlert('success', 'Successo', response.data.message || 'La tua richiesta è stata inviata con successo.');
|
||||
} else {
|
||||
Alert.alert('Errore', response.data.message || 'Impossibile inviare la richiesta.');
|
||||
alert.showAlert('error', 'Errore', response.data.message || 'Impossibile inviare la richiesta.');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Errore nell\'invio della richiesta:', error);
|
||||
@ -74,7 +76,10 @@ export default function RequestPermitModal({ visible, types, onClose, onSubmit }
|
||||
// Funzione per inviare la richiesta
|
||||
const handleSubmit = async () => {
|
||||
const error = validateRequest(type, date, range, startTime, endTime, message);
|
||||
if (error) return Alert.alert("Errore", error);
|
||||
if (error) {
|
||||
alert.showAlert("error", "Errore", error);
|
||||
return;
|
||||
}
|
||||
|
||||
const requestData = {
|
||||
id_type: type.id,
|
||||
@ -90,7 +95,7 @@ export default function RequestPermitModal({ visible, types, onClose, onSubmit }
|
||||
onSubmit(requestData); // TODO: Gestire risposta e controllare fetch in index?
|
||||
onClose();
|
||||
} catch (e) {
|
||||
Alert.alert("Errore", "Impossibile inviare la richiesta.");
|
||||
alert.showAlert("error", "Errore", "Impossibile inviare la richiesta.");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user