handle notification type and navigate using deeplinking

This commit is contained in:
Jaanu423
2025-10-08 16:36:37 +05:30
25 changed files with 1228 additions and 530 deletions
+367 -83
View File
@@ -8,86 +8,314 @@ import {
} from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import {
addNotification,
removeNotification,
clearAllNotifications,
markAsRead,
markAllAsRead,
} from '../../redux/notificationSlice';
import LinearGradient from 'react-native-linear-gradient';
import { Ionicons } from '@react-native-vector-icons/ionicons';
import AppView from '../../components/AppView';
import Header from '../../components/common/Header';
import { useNavigation } from '@react-navigation/native';
import { useNotificationHandler } from '../hook/useNotificationHandler';
type FilterType = 'all' | 'JOIN_REQUEST' | 'CONTRIBUTION' | 'CONTRIBUTION_ACK' |
'CYCLE_COMPLETED' | 'WITHDRAWAL' | 'WITHDRAWAL_NOTIFY' |
'CYCLE_WAIVER' | 'WAIVER_RECEIVED' | 'USER_SELECTED' |
'GROUP_STARTED' | 'JOIN_APPROVED' | 'JOIN_VIA_INVITE';
const Notification = () => {
const dispatch = useDispatch();
const navigation = useNavigation();
const { handleMarkAsReadAndNavigate } = useNotificationHandler();
const notifications = useSelector(
(state: any) => state.notifications.notifications
);
const handleAdd = () => {
const newNotification = {
id: Date.now().toString(),
title: 'New Message',
message: 'You have a new message in your inbox.',
time: new Date().toLocaleString(),
read: false,
};
dispatch(addNotification(newNotification));
};
const handleDelete = (id: string) => {
dispatch(removeNotification(id));
console.log("notifications ==>", notifications);
const handleDelete = (messageId: string) => {
dispatch(removeNotification(messageId));
};
const handleClearAll = () => {
dispatch(clearAllNotifications());
};
const handleMarkAsRead = (messageId: string, screen: string) => {
console.log("screen type : ", screen);
handleMarkAsReadAndNavigate(
messageId,
screen,
{
// Pass additional parameters based on notification type
groupId: 'ebc27862-acde-4129-b12c-fd4c2966e858',
cycleNumber: '1',
contributionId: '12345',
actionTab3: 'Your Requests' ,
actionTab1: 'Joined Groups',
GroupDetails: {
item: {}
},
// etc.
}
);
};
const handleMarkAllAsRead = () => {
dispatch(markAllAsRead());
};
const formatTime = (timestamp: number) => {
const date = new Date(timestamp);
const now = new Date();
const diffInHours = (now.getTime() - date.getTime()) / (1000 * 60 * 60);
if (diffInHours < 1) {
return 'Just now';
} else if (diffInHours < 24) {
return `${Math.floor(diffInHours)}h ago`;
} else {
return date.toLocaleDateString();
}
};
const getNotificationIcon = (msgType: string) => {
switch (msgType) {
case 'JOIN_REQUEST':
return 'person-add-outline';
case 'CONTRIBUTION':
return 'cash-outline';
case 'CONTRIBUTION_ACK':
return 'checkmark-done-outline';
case 'CYCLE_COMPLETED':
return 'trophy-outline';
case 'WITHDRAWAL':
return 'card-outline';
case 'WITHDRAWAL_NOTIFY':
return 'notifications-outline';
case 'CYCLE_WAIVER':
return 'document-text-outline';
case 'WAIVER_RECEIVED':
return 'download-outline';
case 'USER_SELECTED':
return 'people-outline';
case 'GROUP_STARTED':
return 'rocket-outline';
case 'JOIN_APPROVED':
return 'checkmark-circle-outline';
case 'JOIN_VIA_INVITE':
return 'gift-outline';
default:
return 'notifications-outline';
}
};
const getNotificationTypeNavigateScreen = (msgType: string) => {
switch (msgType) {
case 'JOIN_REQUEST':
return 'Home'; // Screen for managing join requests
case 'CONTRIBUTION':
return 'ContributionDetails'; // Screen for contribution details
case 'CONTRIBUTION_ACK':
return 'ContributionHistory'; // Screen for contribution history
case 'CYCLE_COMPLETED':
return 'CycleCompleted'; // Screen showing cycle completion details
case 'WITHDRAWAL':
return 'WithdrawalHistory'; // Screen for withdrawal history
case 'WITHDRAWAL_NOTIFY':
return 'WithdrawalDetails'; // Screen for withdrawal notification details
case 'CYCLE_WAIVER':
return 'WaiverRequests'; // Screen for waiver requests
case 'WAIVER_RECEIVED':
return 'WaiverDetails'; // Screen for waiver details
case 'USER_SELECTED':
return 'UserSelection'; // Screen for user selection details
case 'GROUP_STARTED':
return 'GroupDetails'; // Screen for group details
case 'JOIN_APPROVED':
return 'JoinApproved'; // Screen for approved join requests
case 'JOIN_VIA_INVITE':
return 'InviteDetails'; // Screen for invite details
default:
return 'Home'; // Default fallback screen
}
};
const getNotificationColor = (msgType: string) => {
switch (msgType) {
case 'JOIN_REQUEST':
return '#FF6B35'; // Orange
case 'CONTRIBUTION':
return '#4CAF50'; // Green
case 'CONTRIBUTION_ACK':
return '#2196F3'; // Blue
case 'CYCLE_COMPLETED':
return '#9C27B0'; // Purple
case 'WITHDRAWAL':
return '#FF9800'; // Amber
case 'WITHDRAWAL_NOTIFY':
return '#F44336'; // Red
case 'CYCLE_WAIVER':
return '#607D8B'; // Blue Grey
case 'WAIVER_RECEIVED':
return '#009688'; // Teal
case 'USER_SELECTED':
return '#673AB7'; // Deep Purple
case 'GROUP_STARTED':
return '#00BCD4'; // Cyan
case 'JOIN_APPROVED':
return '#4CAF50'; // Green
case 'JOIN_VIA_INVITE':
return '#FFC107'; // Yellow
default:
return '#757575'; // Grey
}
};
const getFilterDisplayName = (filter: FilterType) => {
switch (filter) {
case 'JOIN_REQUEST':
return 'Requests';
case 'CONTRIBUTION':
return 'Contributions';
case 'CONTRIBUTION_ACK':
return 'Contributions Ack';
case 'CYCLE_COMPLETED':
return 'Cycle Completed';
case 'WITHDRAWAL':
return 'Withdrawals';
case 'WITHDRAWAL_NOTIFY':
return 'Withdrawal Notify';
case 'CYCLE_WAIVER':
return 'Waivers';
case 'WAIVER_RECEIVED':
return 'Waiver Received';
case 'USER_SELECTED':
return 'User Selected';
case 'GROUP_STARTED':
return 'Group Started';
case 'JOIN_APPROVED':
return 'Join Approved';
case 'JOIN_VIA_INVITE':
return 'Invites';
default:
return 'All';
}
};
const unreadCount = notifications.filter((item: any) => !item.read).length;
return (
<AppView>
{/* Header with Add and Clear All */}
<Header title='Notifications' rightAction={<LinearGradient
colors={['#FF6B6B', '#FF8E53']}
style={styles.clearButtonGradient}>
<TouchableOpacity onPress={handleClearAll}>
<Text style={styles.clearButtonText}>Delete All</Text>
</TouchableOpacity>
</LinearGradient>}
/>
{/* Add Notification Button */}
{/* <LinearGradient
colors={['#4facfe', '#00f2fe']}
style={styles.addButtonGradient}>
<TouchableOpacity onPress={handleAdd}>
<Text style={styles.addButtonText}>Add Notification</Text>
</TouchableOpacity>
</LinearGradient> */}
{/* Header with actions */}
<Header
title={`Notifications ${unreadCount > 0 ? `(${unreadCount})` : ''}`} />
<View style={styles.headerActions}>
{unreadCount > 0 && (
<LinearGradient
colors={['#4CAF50', '#45a049']}
style={[styles.headerButton, styles.markReadButton]}>
<TouchableOpacity onPress={handleMarkAllAsRead}>
<Text style={styles.headerButtonText}>Mark All Read</Text>
</TouchableOpacity>
</LinearGradient>
)}
{notifications.length > 0 && (
<LinearGradient
colors={['#FF6B6B', '#FF8E53']}
style={styles.headerButton}>
<TouchableOpacity onPress={handleClearAll}>
<Text style={styles.headerButtonText}>Clear All</Text>
</TouchableOpacity>
</LinearGradient>
)}
</View>
{/* Notification List */}
<FlatList
data={notifications}
keyExtractor={(item) => item.id}
keyExtractor={(item) => item.messageId}
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.listContainer}
renderItem={({ item }) => (
<LinearGradient
colors={['#f5f7fa', '#c3cfe2']}
style={styles.notificationCard}>
<View style={styles.notificationContent}>
<View style={{ flex: 1 }}>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.message}>{item.message}</Text>
<Text style={styles.time}>{item.time}</Text>
<TouchableOpacity
onPress={() => handleMarkAsRead(item.messageId, item?.data?.msgType)}
activeOpacity={0.7}
>
<LinearGradient
colors={item.read ? ['#f8f9fa', '#e9ecef'] : ['#ffffff', '#f8f9fa']}
style={[
styles.notificationCard,
!item.read && styles.unreadNotification, { borderLeftColor: getNotificationColor(item.data.msgType) }
]}>
<View style={styles.notificationContent}>
{/* Icon */}
<View style={styles.iconContainer}>
<Ionicons
name={getNotificationIcon(item.data.msgType)}
size={24}
color={getNotificationColor(item.data.msgType)}
/>
</View>
{/* Content */}
<View style={styles.textContent}>
<View style={styles.headerRow}>
<Text style={[
styles.title,
!item.read && styles.unreadTitle
]}>
{item.data.title}
</Text>
<Text style={styles.time}>
{formatTime(item.sentTime)}
</Text>
</View>
<Text style={styles.message}>
{item.data.body}
</Text>
{item.data.msgType && (
<View style={styles.tagContainer}>
<Text style={[
styles.tag,
{ backgroundColor: getNotificationColor(item.data.msgType) }
]}>
{/* {item.data.msgType} */}
{getFilterDisplayName(item.data.msgType as FilterType)}
</Text>
</View>
)}
</View>
{/* Actions */}
<View style={styles.actionsContainer}>
{!item.read && (
<View style={styles.unreadIndicator} />
)}
<TouchableOpacity
onPress={() => handleDelete(item.messageId)}
style={styles.deleteButton}
>
<Ionicons name="trash-outline" size={20} color="#e63946" />
</TouchableOpacity>
</View>
</View>
<TouchableOpacity onPress={() => handleDelete(item.id)}>
<Ionicons name="trash-outline" size={22} color="#e63946" />
</TouchableOpacity>
</View>
</LinearGradient>
</LinearGradient>
</TouchableOpacity>
)}
ListEmptyComponent={
<Text style={styles.emptyText}>No notifications found.</Text>
<View style={styles.emptyContainer}>
<Ionicons name="notifications-off-outline" size={64} color="#ccc" />
<Text style={styles.emptyText}>No notifications yet</Text>
<Text style={styles.emptySubtext}>
When you get notifications, they'll appear here
</Text>
</View>
}
/>
</AppView>
@@ -97,74 +325,130 @@ const Notification = () => {
export default Notification;
const styles = StyleSheet.create({
headerContainer: {
headerActions: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
gap: 8,
justifyContent: 'flex-end',
paddingHorizontal: 16,
paddingTop: 12,
paddingVertical: 10,
},
headerTitle: {
fontSize: 20,
fontWeight: '700',
color: '#222',
},
clearButtonGradient: {
headerButton: {
borderRadius: 8,
paddingVertical: 6,
paddingHorizontal: 14,
},
clearButtonText: {
color: '#fff',
fontWeight: '600',
},
addButtonGradient: {
margin: 16,
borderRadius: 8,
paddingVertical: 10,
paddingHorizontal: 12,
minWidth: 80,
alignItems: 'center',
},
addButtonText: {
markReadButton: {
minWidth: 100,
},
headerButtonText: {
color: '#fff',
fontWeight: '600',
fontSize: 16,
fontSize: 12,
},
listContainer: {
paddingHorizontal: 12,
paddingBottom: 30,
paddingTop: 8,
},
notificationCard: {
borderRadius: 10,
padding: 12,
borderRadius: 12,
padding: 16,
marginBottom: 10,
shadowColor: '#000',
shadowOpacity: 0.1,
shadowRadius: 5,
elevation: 2,
elevation: 3,
borderLeftWidth: 4,
borderLeftColor: 'transparent',
},
unreadNotification: {
// borderLeftColor: colors.primary,
backgroundColor: '#fff',
},
notificationContent: {
flexDirection: 'row',
alignItems: 'center',
alignItems: 'flex-start',
gap: 12,
},
iconContainer: {
paddingTop: 2,
},
textContent: {
flex: 1,
gap: 4,
},
headerRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
marginBottom: 4,
},
title: {
fontWeight: '700',
fontWeight: '600',
fontSize: 16,
color: '#333',
flex: 1,
marginRight: 8,
},
unreadTitle: {
fontWeight: '700',
color: '#000',
},
message: {
fontSize: 14,
color: '#555',
marginTop: 2,
color: '#666',
lineHeight: 20,
},
time: {
fontSize: 12,
color: '#888',
marginTop: 4,
color: '#999',
fontWeight: '500',
},
tagContainer: {
marginTop: 6,
},
tag: {
fontSize: 10,
color: '#fff',
paddingHorizontal: 8,
paddingVertical: 2,
borderRadius: 4,
overflow: 'hidden',
alignSelf: 'flex-start',
fontWeight: '600',
},
actionsContainer: {
alignItems: 'center',
gap: 8,
},
unreadIndicator: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: '#2196F3',
},
deleteButton: {
padding: 4,
},
emptyContainer: {
alignItems: 'center',
justifyContent: 'center',
marginTop: 100,
paddingHorizontal: 40,
},
emptyText: {
textAlign: 'center',
marginTop: 60,
fontSize: 16,
color: '#999',
fontSize: 18,
fontWeight: '600',
color: '#666',
marginTop: 16,
marginBottom: 8,
},
});
emptySubtext: {
fontSize: 14,
color: '#999',
textAlign: 'center',
lineHeight: 20,
},
});