feat: Implement new Notification, PaymentMode, and Profile screens, alongside Android manifest and package updates.

This commit is contained in:
Abin Toppo
2026-03-27 10:37:19 +05:30
parent 5ba1c5d3f0
commit 84d609d4ac
6 changed files with 431 additions and 240 deletions
+14 -13
View File
@@ -20,7 +20,7 @@ import { ThemeProvider } from './src/theme/ThemeProvider';
LogBox.ignoreAllLogs(true); LogBox.ignoreAllLogs(true);
const queryClient = new QueryClient({ export const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
queries: { queries: {
retry: 2, retry: 2,
@@ -29,8 +29,9 @@ const queryClient = new QueryClient({
}, },
}); });
function App() { import { GestureHandlerRootView } from 'react-native-gesture-handler';
function App() {
useEffect(() => { useEffect(() => {
requestPermission(); requestPermission();
@@ -53,18 +54,18 @@ function App() {
}; };
}, []); }, []);
return ( return (
<SafeAreaProvider> <GestureHandlerRootView style={{ flex: 1 }}>
<QueryClientProvider client={queryClient}> <SafeAreaProvider>
<ThemeProvider> <QueryClientProvider client={queryClient}>
<PaperProvider > <ThemeProvider>
<AppNavigator /> <PaperProvider >
</PaperProvider> <AppNavigator />
</ThemeProvider> </PaperProvider>
</QueryClientProvider> </ThemeProvider>
</SafeAreaProvider> </QueryClientProvider>
</SafeAreaProvider>
</GestureHandlerRootView>
); );
} }
+1
View File
@@ -19,6 +19,7 @@
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode" android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
android:launchMode="singleTask" android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize" android:windowSoftInputMode="adjustResize"
android:screenOrientation="portrait"
android:exported="true"> android:exported="true">
<intent-filter android:autoVerify="true"> <intent-filter android:autoVerify="true">
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
+1
View File
@@ -41,6 +41,7 @@
"react-native-dialog": "^9.3.0", "react-native-dialog": "^9.3.0",
"react-native-elements": "^3.4.3", "react-native-elements": "^3.4.3",
"react-native-fast-confetti": "^1.1.1", "react-native-fast-confetti": "^1.1.1",
"react-native-gesture-handler": "^2.28.0",
"react-native-image-picker": "^8.2.1", "react-native-image-picker": "^8.2.1",
"react-native-inset-shadow": "^1.0.3", "react-native-inset-shadow": "^1.0.3",
"react-native-keychain": "^10.0.0", "react-native-keychain": "^10.0.0",
+340 -204
View File
@@ -1,14 +1,16 @@
import React from 'react'; import React, { useState } from 'react';
import { import {
View, View,
Text, Text,
TouchableOpacity, TouchableOpacity,
FlatList, FlatList,
StyleSheet, StyleSheet,
Alert,
Dimensions,
} from 'react-native'; } from 'react-native';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { removeNotification } from '../../redux/notificationSlice'; import { removeNotification, clearAllNotifications, markAllAsRead } from '../../redux/notificationSlice';
import LinearGradient from 'react-native-linear-gradient'; import LinearGradient from 'react-native-linear-gradient';
import LucideIcon from '../../components/common/LucideIcon'; import LucideIcon from '../../components/common/LucideIcon';
@@ -16,13 +18,18 @@ import AppView from '../../components/AppView';
import Header from '../../components/common/Header'; import Header from '../../components/common/Header';
import { useNotificationHandler } from '../hook/useNotificationHandler'; import { useNotificationHandler } from '../hook/useNotificationHandler';
import { useTheme } from '../../theme/ThemeProvider'; import { useTheme } from '../../theme/ThemeProvider';
import Swipeable from 'react-native-gesture-handler/ReanimatedSwipeable'; import Swipeable from 'react-native-gesture-handler/ReanimatedSwipeable';
import Animated, { import Animated, {
useSharedValue, useSharedValue,
useAnimatedStyle, useAnimatedStyle,
withSpring, withSpring,
interpolate,
Extrapolate,
SharedValue,
} from 'react-native-reanimated'; } from 'react-native-reanimated';
import FontsFamily from '../../constents/FontsFamily';
const { width } = Dimensions.get('window');
type FilterType = type FilterType =
| 'all' | 'all'
@@ -52,6 +59,28 @@ const Notification = () => {
dispatch(removeNotification(messageId)); dispatch(removeNotification(messageId));
}; };
const handleClearAll = () => {
if (notifications.length === 0) return;
Alert.alert(
'Clear All',
'Are you sure you want to delete all notifications?',
[
{ text: 'Cancel', style: 'cancel' },
{
text: 'Clear All',
style: 'destructive',
onPress: () => dispatch(clearAllNotifications())
},
]
);
};
const handleMarkAllRead = () => {
if (notifications.length === 0) return;
dispatch(markAllAsRead());
};
const handleMarkAsRead = ( const handleMarkAsRead = (
messageId: string, messageId: string,
screen: string, screen: string,
@@ -72,36 +101,47 @@ const Notification = () => {
const formatTime = (timestamp: number) => { const formatTime = (timestamp: number) => {
const date = new Date(timestamp); const date = new Date(timestamp);
const now = new Date(); const now = new Date();
const diff = (now.getTime() - date.getTime()) / (1000 * 60 * 60); const diff = (now.getTime() - date.getTime()) / (1000 * 60);
if (diff < 1) return 'Just now'; if (diff < 1) return 'Just now';
if (diff < 24) return `${Math.floor(diff)}h ago`; if (diff < 60) return `${Math.floor(diff)}m ago`;
return date.toLocaleDateString(); if (diff < 1440) return `${Math.floor(diff / 60)}h ago`;
const dayDiff = Math.floor(diff / 1440);
if (dayDiff === 1) return 'Yesterday';
if (dayDiff < 7) return `${dayDiff}d ago`;
return date.toLocaleDateString('en-IN', { day: '2-digit', month: 'short' });
}; };
const getNotificationIcon = (type: string) => { const getNotificationIcon = (type: string) => {
const map: any = { const map: any = {
JOIN_REQUEST: 'person-add-outline', JOIN_REQUEST: 'person-add-outline',
CONTRIBUTION: 'cash-outline', CONTRIBUTION: 'cash-outline',
CONTRIBUTION_ACK: 'checkmark-done-outline', CONTRIBUTION_ACK: 'checkmark-circle-outline',
CYCLE_COMPLETED: 'trophy-outline', CYCLE_COMPLETED: 'trophy',
WITHDRAWAL: 'card-outline', WITHDRAWAL: 'wallet-outline',
WITHDRAWAL_NOTIFY: 'notifications-outline', WITHDRAWAL_NOTIFY: 'notifications-outline',
GROUP_STARTED: 'rocket-outline', GROUP_STARTED: 'sparkles-outline',
JOIN_APPROVED: 'checkmark-circle-outline',
JOIN_VIA_INVITE: 'mail-outline',
}; };
return map[type] || 'notifications-outline'; return map[type] || 'notifications-outline';
}; };
const getNotificationColor = (type: string) => { const getNotificationColor = (type: string) => {
const map: any = { const map: any = {
JOIN_REQUEST: '#FFB6C1', JOIN_REQUEST: '#FF6B6B', // Vibrant Red
CONTRIBUTION: '#98FB98', CONTRIBUTION: '#4ECDC4', // Modern Teal
CONTRIBUTION_ACK: '#ADD8E6', CONTRIBUTION_ACK: '#45B7D1', // Sky Blue
CYCLE_COMPLETED: '#D8BFD8', CYCLE_COMPLETED: '#FFD93D', // Golden Yellow
WITHDRAWAL: '#FFD700', WITHDRAWAL: '#6C5CE7', // Deep Purple
WITHDRAWAL_NOTIFY: '#FFA07A', WITHDRAWAL_NOTIFY: '#FF8066', // Coral
GROUP_STARTED: '#A8E6CF', // Mint Green
JOIN_APPROVED: '#32D74B', // iOS Green
JOIN_VIA_INVITE: '#007AFF', // iOS Blue
}; };
return map[type] || '#D3D3D3'; return map[type] || '#8E8E93';
}; };
const getFilterDisplayName = (filter: FilterType) => { const getFilterDisplayName = (filter: FilterType) => {
@@ -110,135 +150,162 @@ const Notification = () => {
const unreadCount = notifications.filter((i: any) => !i.read).length; const unreadCount = notifications.filter((i: any) => !i.read).length;
// 🔥 Swipe Delete UI
const renderRightActions = ( const renderRightActions = (
messageId: string, messageId: string,
onDeleteAction: (id: string) => void, onDeleteAction: (id: string) => void,
swipeableMethods?: { close?: () => void } swipeableMethods: { close: () => void },
drag: SharedValue<number>
) => { ) => {
const styleAnimation = useAnimatedStyle(() => {
const translateX = interpolate(
drag.value,
[-100, 0],
[0, 100],
Extrapolate.CLAMP
);
return {
transform: [{ translateX }],
};
});
return ( return (
<TouchableOpacity <TouchableOpacity
onPress={() => { onPress={() => {
onDeleteAction(messageId); onDeleteAction(messageId);
swipeableMethods?.close?.(); swipeableMethods.close();
}} }}
style={styles(theme).deleteSwipe} activeOpacity={0.8}
style={styles(theme).deleteSwipeContainer}
> >
<LucideIcon name="trash-outline" size={22} color="#fff" /> <Animated.View style={[styles(theme).deleteSwipe, styleAnimation]}>
<Text style={styles(theme).deleteText}>Delete</Text> <LucideIcon name="trash-outline" size={24} color="#fff" />
<Text style={styles(theme).deleteText}>Delete</Text>
</Animated.View>
</TouchableOpacity> </TouchableOpacity>
); );
}; };
const NotificationItem = ({ const NotificationItem = ({
item, item,
onDelete, onDelete,
onPressNotification, onPressNotification,
theme, theme,
}: { }: {
item: any; item: any;
onDelete: (id: string) => void; onDelete: (id: string) => void;
onPressNotification: ( onPressNotification: (
messageId: string, messageId: string,
screen: string, screen: string,
groupId: string, groupId: string,
cycleNumber: string, cycleNumber: string,
contributionId: string contributionId: string
) => void; ) => void;
theme: any; theme: any;
}) => { }) => {
const scale = useSharedValue(1); const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({ const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }], transform: [{ scale: scale.value }],
})); }));
return ( return (
<Swipeable <Swipeable
renderRightActions={(_, __, swipeableMethods) => friction={2}
renderRightActions(item.messageId, onDelete, swipeableMethods) enableTrackpadTwoFingerGesture
} rightThreshold={40}
> renderRightActions={(prog, drag, swipeableMethods) =>
<Animated.View style={animatedStyle}> renderRightActions(item.messageId, onDelete, swipeableMethods, drag)
<TouchableOpacity }
activeOpacity={0.9} >
onPressIn={() => (scale.value = withSpring(0.96))} <Animated.View style={animatedStyle}>
onPressOut={() => (scale.value = withSpring(1))} <TouchableOpacity
onPress={() => activeOpacity={0.9}
onPressNotification( onPressIn={() => (scale.value = withSpring(0.98))}
item.messageId, onPressOut={() => (scale.value = withSpring(1))}
item?.data?.msgType, onPress={() =>
item?.data?.groupId, onPressNotification(
item?.data?.cycleNumber, item.messageId,
item?.data?.contributionId item?.data?.msgType,
) item?.data?.groupId,
} item?.data?.cycleNumber,
> item?.data?.contributionId
<LinearGradient )
colors={
item.read
? [theme.colors.white, '#F8FAFF']
: ['#FFF7ED', '#EEF2FF']
} }
style={[
styles(theme).card,
!item.read && styles(theme).unread,
{
borderLeftColor: getNotificationColor(
item.data.msgType
),
},
]}
> >
<View style={styles(theme).row}> <View
<View style={styles(theme).iconBox}> style={[
<LucideIcon styles(theme).card,
name={getNotificationIcon(item.data.msgType)} !item.read && styles(theme).unreadCard,
size={22} ]}
color={getNotificationColor(item.data.msgType)} >
/> <View style={styles(theme).cardContent}>
</View> <View
style={[
<View style={styles(theme).content}> styles(theme).iconContainer,
<View style={styles(theme).topRow}> { backgroundColor: getNotificationColor(item.data.msgType) + '15' }
<Text ]}
style={[ >
styles(theme).title, <LucideIcon
!item.read && styles(theme).unreadTitle, name={getNotificationIcon(item.data.msgType)}
]} size={24}
> color={getNotificationColor(item.data.msgType)}
{item.data.title} />
</Text>
<Text style={styles(theme).time}>
{formatTime(item.sentTime)}
</Text>
</View> </View>
<Text style={styles(theme).message}>{item.data.body}</Text> <View style={styles(theme).textContent}>
<View style={styles(theme).headerRow}>
<Text
numberOfLines={1}
style={[
styles(theme).title,
!item.read && styles(theme).unreadTitle
]}
>
{item.data.title}
</Text>
<Text style={styles(theme).time}>
{formatTime(item.sentTime)}
</Text>
</View>
<View style={styles(theme).tagWrap}> <Text numberOfLines={2} style={styles(theme).bodyText}>
<Text {item.data.body}
style={[
styles(theme).tag,
{
backgroundColor: getNotificationColor(
item.data.msgType
),
},
]}
>
{getFilterDisplayName(item.data.msgType)}
</Text> </Text>
<View style={styles(theme).footerRow}>
<View
style={[
styles(theme).badge,
{ backgroundColor: getNotificationColor(item.data.msgType) + '20' }
]}
>
<View
style={[
styles(theme).badgeDot,
{ backgroundColor: getNotificationColor(item.data.msgType) }
]}
/>
<Text
style={[
styles(theme).badgeText,
{ color: getNotificationColor(item.data.msgType) }
]}
>
{getFilterDisplayName(item.data.msgType)}
</Text>
</View>
{!item.read && (
<View style={styles(theme).unreadIndicator} />
)}
</View>
</View> </View>
</View> </View>
{!item.read && <View style={styles(theme).dot} />}
</View> </View>
</LinearGradient> </TouchableOpacity>
</TouchableOpacity> </Animated.View>
</Animated.View> </Swipeable>
</Swipeable> );
); };
};
const renderItem = ({ item }: any) => ( const renderItem = ({ item }: any) => (
<NotificationItem <NotificationItem
@@ -252,9 +319,28 @@ const NotificationItem = ({
return ( return (
<AppView> <AppView>
<Header <Header
title={ title="Notifications"
'Notifications' + rightAction={
(unreadCount ? ` (${unreadCount})` : '') <View style={styles(theme).headerActions}>
{notifications.length > 0 && (
<TouchableOpacity
onPress={handleMarkAllRead}
style={styles(theme).headerIconButton}
accessibilityLabel="Mark all as read"
>
<LucideIcon name="checkmark-circle-outline" size={20} color={theme.colors.primary} />
</TouchableOpacity>
)}
{notifications.length > 0 && (
<TouchableOpacity
onPress={handleClearAll}
style={styles(theme).headerIconButton}
accessibilityLabel="Clear all notifications"
>
<LucideIcon name="trash-outline" size={20} color="#FF3B30" />
</TouchableOpacity>
)}
</View>
} }
/> />
@@ -263,16 +349,19 @@ const NotificationItem = ({
keyExtractor={(item) => item.messageId} keyExtractor={(item) => item.messageId}
renderItem={renderItem} renderItem={renderItem}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
contentContainerStyle={styles(theme).container} contentContainerStyle={styles(theme).listContainer}
ListEmptyComponent={ ListEmptyComponent={
<View style={styles(theme).empty}> <View style={styles(theme).emptyContainer}>
<LucideIcon <View style={styles(theme).emptyCircle}>
name="notifications-off-outline" <LucideIcon
size={64} name="notifications-off-outline"
color="#ccc" size={48}
/> color="#A1A1AA"
<Text style={styles(theme).emptyText}> />
No notifications </View>
<Text style={styles(theme).emptyTitle}>Stay Tuned!</Text>
<Text style={styles(theme).emptySubText}>
We'll notify you when something important happens.
</Text> </Text>
</View> </View>
} }
@@ -285,117 +374,164 @@ export default Notification;
const styles = (theme: any) => const styles = (theme: any) =>
StyleSheet.create({ StyleSheet.create({
container: { listContainer: {
padding: 12, padding: 16,
paddingBottom: 40, paddingBottom: 40,
}, },
card: { card: {
borderRadius: 16, backgroundColor: theme.colors.white || '#FFFFFF',
padding: 16, borderRadius: 20,
padding: 14,
marginBottom: 12, marginBottom: 12,
backgroundColor: 'rgba(255,255,255,0.95)',
shadowColor: '#000', shadowColor: '#000',
shadowOpacity: 0.08, shadowOffset: { width: 0, height: 2 },
shadowRadius: 10, shadowOpacity: 0.05,
elevation: 4, shadowRadius: 8,
borderLeftWidth: 5, elevation: 2,
},
unread: {
borderWidth: 1, borderWidth: 1,
borderColor: '#E0E7FF', borderColor: '#F4F4F5',
shadowColor: '#6366F1',
shadowOpacity: 0.15,
}, },
unreadCard: {
row: { borderColor: (theme.colors.primary || '#6366F1') + '20',
backgroundColor: (theme.colors.primary || '#6366F1') + '05',
},
cardContent: {
flexDirection: 'row', flexDirection: 'row',
gap: 12, alignItems: 'flex-start',
}, },
iconContainer: {
iconBox: { width: 48,
backgroundColor: '#F1F5F9', height: 48,
padding: 10, borderRadius: 14,
borderRadius: 12, justifyContent: 'center',
alignItems: 'center',
marginRight: 12,
}, },
textContent: {
content: {
flex: 1, flex: 1,
}, },
headerRow: {
topRow: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 4,
}, },
title: { title: {
fontWeight: '700', fontFamily: FontsFamily.nunitoBold,
fontSize: 15, fontSize: 15,
color: theme.colors.textPrimary, color: theme.colors.textPrimary || '#18181B',
flex: 1, flex: 1,
marginRight: 8,
}, },
unreadTitle: { unreadTitle: {
color: theme.colors.primary, color: theme.colors.primary || '#6366F1',
}, },
message: {
fontSize: 13,
color: theme.colors.textSecondary,
marginTop: 4,
},
time: { time: {
fontFamily: FontsFamily.nunitoRegular,
fontSize: 11, fontSize: 11,
color: '#6B7280', color: '#71717A',
}, },
bodyText: {
tagWrap: { fontFamily: FontsFamily.nunitoRegular,
marginTop: 6, fontSize: 13,
color: theme.colors.textSecondary || '#52525B',
lineHeight: 18,
marginBottom: 8,
}, },
footerRow: {
tag: { flexDirection: 'row',
fontSize: 10, justifyContent: 'space-between',
color: '#fff', alignItems: 'center',
paddingHorizontal: 10, },
badge: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 8,
paddingVertical: 4, paddingVertical: 4,
borderRadius: 20, borderRadius: 8,
fontWeight: '700',
alignSelf: 'flex-start',
}, },
badgeDot: {
dot: { width: 6,
height: 6,
borderRadius: 3,
marginRight: 6,
},
badgeText: {
fontFamily: FontsFamily.nunitoSemiBold,
fontSize: 10,
textTransform: 'capitalize',
},
unreadIndicator: {
width: 8, width: 8,
height: 8, height: 8,
borderRadius: 4, borderRadius: 4,
backgroundColor: theme.colors.primary, backgroundColor: theme.colors.primary || '#6366F1',
marginTop: 6, },
deleteSwipeContainer: {
width: 90,
height: '100%',
justifyContent: 'center',
alignItems: 'flex-end',
marginBottom: 12, // Match card margin
}, },
deleteSwipe: { deleteSwipe: {
backgroundColor: '#FF3B30', backgroundColor: '#FF3B30',
width: 80,
height: '84%', // A bit shorter than the container for visual balance
borderRadius: 20,
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
width: 80, shadowColor: '#FF3B30',
borderRadius: 16, shadowOffset: { width: 0, height: 4 },
marginBottom: 12, shadowOpacity: 0.3,
shadowRadius: 8,
elevation: 5,
}, },
deleteText: { deleteText: {
color: '#fff', color: '#FFFFFF',
fontFamily: FontsFamily.nunitoBold,
fontSize: 12, fontSize: 12,
marginTop: 4, marginTop: 4,
}, },
headerActions: {
empty: { flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
marginTop: 120, gap: 8,
}, },
headerIconButton: {
emptyText: { width: 36,
marginTop: 12, height: 36,
color: '#999', borderRadius: 10,
fontSize: 16, backgroundColor: '#F4F4F5',
justifyContent: 'center',
alignItems: 'center',
},
emptyContainer: {
alignItems: 'center',
justifyContent: 'center',
marginTop: width * 0.4,
paddingHorizontal: 40,
},
emptyCircle: {
width: 100,
height: 100,
borderRadius: 50,
backgroundColor: '#F4F4F5',
justifyContent: 'center',
alignItems: 'center',
marginBottom: 24,
},
emptyTitle: {
fontFamily: FontsFamily.nunitoBold,
fontSize: 20,
color: theme.colors.textPrimary,
marginBottom: 8,
},
emptySubText: {
fontFamily: FontsFamily.nunitoRegular,
fontSize: 14,
color: '#71717A',
textAlign: 'center',
lineHeight: 20,
}, },
}); });
+44 -17
View File
@@ -321,7 +321,7 @@ const PaymentModeScreen: React.FC = () => {
payload payload
) as any; ) as any;
console.log("payment res",res) console.log("payment res", res)
if (res?.status === 'success' || res?.statusCode === 200) { if (res?.status === 'success' || res?.statusCode === 200) {
const pgOrderNo = res?.data?.pgOrderNo; const pgOrderNo = res?.data?.pgOrderNo;
@@ -341,7 +341,7 @@ const PaymentModeScreen: React.FC = () => {
setPaying(false); setPaying(false);
} }
} }
const handlePaymentForRouteFormGroupDetails = async () => { const handlePaymentForRouteFormGroupDetails = async (PaymentMode: number) => {
try { try {
setPaying(true); setPaying(true);
@@ -359,35 +359,62 @@ const PaymentModeScreen: React.FC = () => {
payload payload
) as any; ) as any;
console.log("payment payload ",payload) console.log("payment payload ", payload)
console.log("payment res ",res) console.log("payment res ", res)
if (res?.status === 'success' || res?.statusCode === 200) { switch (PaymentMode) {
case 6:
{
if (res?.status === 'success' || res?.statusCode === 200) {
const pgOrderNo = res?.data?.pgOrderNo; const pgOrderNo = res?.data?.pgOrderNo;
if (pgOrderNo) { if (pgOrderNo) {
// Start polling for transaction status // Start polling for transaction status
pollTransactionStatus(pgOrderNo); pollTransactionStatus(pgOrderNo);
} else { } else {
ToastAndroid.show('Payment initiated but no order number received', ToastAndroid.SHORT); ToastAndroid.show('Payment initiated but no order number received', ToastAndroid.SHORT);
setPaying(false); setPaying(false);
} }
} else { } else {
ToastAndroid.show(res?.data?.message || res?.message || 'Payment failed', ToastAndroid.SHORT); ToastAndroid.show(res?.data?.message || res?.message || 'Payment failed', ToastAndroid.SHORT);
setPaying(false); setPaying(false);
}
}
break;
default:
{
if (res?.status === 'success' || res?.statusCode === 200) {
const pgOrderNo = res?.data?.pgOrderNo;
if (pgOrderNo) {
// Start polling for transaction status
pollTransactionStatus(pgOrderNo);
} else {
ToastAndroid.show('Payment initiated but no order number received', ToastAndroid.SHORT);
setPaying(false);
}
} else {
ToastAndroid.show(res?.data?.message || res?.message || 'Payment failed', ToastAndroid.SHORT);
setPaying(false);
}
}
break;
} }
} catch (error) { } catch (error) {
ToastAndroid.show('An error occurred during payment', ToastAndroid.SHORT); ToastAndroid.show('An error occurred during payment', ToastAndroid.SHORT);
setPaying(false); setPaying(false);
} }
} }
const handleConfirmPayment = async () => { const handleConfirmPayment = async () => {
console.log("selectedMode", selectedMode)
if (!selectedMode) { if (!selectedMode) {
ToastAndroid.show('Please select a payment method', ToastAndroid.SHORT); ToastAndroid.show('Please select a payment method', ToastAndroid.SHORT);
return; return;
} }
switch (routeFrom) { switch (routeFrom) {
case 'GroupDetails': case 'GroupDetails':
handlePaymentForRouteFormGroupDetails(); handlePaymentForRouteFormGroupDetails(selectedMode);
break; break;
case 'GroupScreen': case 'GroupScreen':
handlePaymentForRouteFormGroupScreen(); handlePaymentForRouteFormGroupScreen();
+27 -2
View File
@@ -39,6 +39,9 @@ import {
USER_MOBILE_MONEY_URL, USER_MOBILE_MONEY_URL,
} from "../../utils/ApiUrls"; } from "../../utils/ApiUrls";
import SwitchNavigator from "./SwitchNavigator"; import SwitchNavigator from "./SwitchNavigator";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { QueryClient } from "@tanstack/react-query";
import { queryClient } from "../../../App";
interface UserProfile { interface UserProfile {
id: string; id: string;
@@ -183,9 +186,12 @@ const Profile = () => {
refetchStatus(); refetchStatus();
}, [refetchProfile, refetchStatus]); }, [refetchProfile, refetchStatus]);
const handleLogout = async () => { const handleLogout = async () => {
setIsLoggingOut(true); setIsLoggingOut(true);
try { try {
// 🔹 Remove FCM Token
const fcmToken = await getFcmToken(user?.userId?.toString()); const fcmToken = await getFcmToken(user?.userId?.toString());
if (fcmToken) { if (fcmToken) {
await fetch(`${BASE_URL}token/remove?fcmToken=${fcmToken}`, { await fetch(`${BASE_URL}token/remove?fcmToken=${fcmToken}`, {
@@ -194,15 +200,34 @@ const Profile = () => {
Authorization: `Bearer ${(await Keychain.getGenericPassword() as any).password}`, Authorization: `Bearer ${(await Keychain.getGenericPassword() as any).password}`,
}, },
}); });
await deleteFcmToken(user?.userId?.toString()); await deleteFcmToken(user?.userId?.toString());
} }
// 🔹 Clear Redux State
dispatch(clearAllNotifications()); dispatch(clearAllNotifications());
await Keychain.resetGenericPassword();
dispatch(clearUser()); dispatch(clearUser());
navigation.replace("Login");
// 🔹 Clear AsyncStorage (IMPORTANT 🚨)
await AsyncStorage.clear();
// 🔹 Clear Keychain (secure storage)
await Keychain.resetGenericPassword();
// 🔹 Clear React Query / API Cache (if using)
if (queryClient) {
queryClient.clear();
}
// 🔹 Reset Navigation (avoid back navigation)
navigation.reset({
index: 0,
routes: [{ name: 'Login' }],
});
} catch (error) { } catch (error) {
console.error("Logout error:", error); console.error("Logout error:", error);
} finally {
setIsLoggingOut(false); setIsLoggingOut(false);
} }
}; };