feat: Implement new Notification, PaymentMode, and Profile screens, alongside Android manifest and package updates.
This commit is contained in:
@@ -20,7 +20,7 @@ import { ThemeProvider } from './src/theme/ThemeProvider';
|
||||
|
||||
|
||||
LogBox.ignoreAllLogs(true);
|
||||
const queryClient = new QueryClient({
|
||||
export const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: 2,
|
||||
@@ -29,8 +29,9 @@ const queryClient = new QueryClient({
|
||||
},
|
||||
});
|
||||
|
||||
function App() {
|
||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||||
|
||||
function App() {
|
||||
|
||||
useEffect(() => {
|
||||
requestPermission();
|
||||
@@ -53,18 +54,18 @@ function App() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<SafeAreaProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ThemeProvider>
|
||||
<PaperProvider >
|
||||
<AppNavigator />
|
||||
</PaperProvider>
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>
|
||||
</SafeAreaProvider>
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<SafeAreaProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ThemeProvider>
|
||||
<PaperProvider >
|
||||
<AppNavigator />
|
||||
</PaperProvider>
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>
|
||||
</SafeAreaProvider>
|
||||
</GestureHandlerRootView>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:screenOrientation="portrait"
|
||||
android:exported="true">
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"react-native-dialog": "^9.3.0",
|
||||
"react-native-elements": "^3.4.3",
|
||||
"react-native-fast-confetti": "^1.1.1",
|
||||
"react-native-gesture-handler": "^2.28.0",
|
||||
"react-native-image-picker": "^8.2.1",
|
||||
"react-native-inset-shadow": "^1.0.3",
|
||||
"react-native-keychain": "^10.0.0",
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
FlatList,
|
||||
StyleSheet,
|
||||
Alert,
|
||||
Dimensions,
|
||||
} from 'react-native';
|
||||
|
||||
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 LucideIcon from '../../components/common/LucideIcon';
|
||||
@@ -16,13 +18,18 @@ import AppView from '../../components/AppView';
|
||||
import Header from '../../components/common/Header';
|
||||
import { useNotificationHandler } from '../hook/useNotificationHandler';
|
||||
import { useTheme } from '../../theme/ThemeProvider';
|
||||
|
||||
import Swipeable from 'react-native-gesture-handler/ReanimatedSwipeable';
|
||||
import Animated, {
|
||||
useSharedValue,
|
||||
useAnimatedStyle,
|
||||
withSpring,
|
||||
interpolate,
|
||||
Extrapolate,
|
||||
SharedValue,
|
||||
} from 'react-native-reanimated';
|
||||
import FontsFamily from '../../constents/FontsFamily';
|
||||
|
||||
const { width } = Dimensions.get('window');
|
||||
|
||||
type FilterType =
|
||||
| 'all'
|
||||
@@ -52,6 +59,28 @@ const Notification = () => {
|
||||
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 = (
|
||||
messageId: string,
|
||||
screen: string,
|
||||
@@ -72,36 +101,47 @@ const Notification = () => {
|
||||
const formatTime = (timestamp: number) => {
|
||||
const date = new Date(timestamp);
|
||||
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 < 24) return `${Math.floor(diff)}h ago`;
|
||||
return date.toLocaleDateString();
|
||||
if (diff < 60) return `${Math.floor(diff)}m ago`;
|
||||
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 map: any = {
|
||||
JOIN_REQUEST: 'person-add-outline',
|
||||
CONTRIBUTION: 'cash-outline',
|
||||
CONTRIBUTION_ACK: 'checkmark-done-outline',
|
||||
CYCLE_COMPLETED: 'trophy-outline',
|
||||
WITHDRAWAL: 'card-outline',
|
||||
CONTRIBUTION_ACK: 'checkmark-circle-outline',
|
||||
CYCLE_COMPLETED: 'trophy',
|
||||
WITHDRAWAL: 'wallet-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';
|
||||
};
|
||||
|
||||
const getNotificationColor = (type: string) => {
|
||||
const map: any = {
|
||||
JOIN_REQUEST: '#FFB6C1',
|
||||
CONTRIBUTION: '#98FB98',
|
||||
CONTRIBUTION_ACK: '#ADD8E6',
|
||||
CYCLE_COMPLETED: '#D8BFD8',
|
||||
WITHDRAWAL: '#FFD700',
|
||||
WITHDRAWAL_NOTIFY: '#FFA07A',
|
||||
JOIN_REQUEST: '#FF6B6B', // Vibrant Red
|
||||
CONTRIBUTION: '#4ECDC4', // Modern Teal
|
||||
CONTRIBUTION_ACK: '#45B7D1', // Sky Blue
|
||||
CYCLE_COMPLETED: '#FFD93D', // Golden Yellow
|
||||
WITHDRAWAL: '#6C5CE7', // Deep Purple
|
||||
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) => {
|
||||
@@ -110,135 +150,162 @@ const Notification = () => {
|
||||
|
||||
const unreadCount = notifications.filter((i: any) => !i.read).length;
|
||||
|
||||
// 🔥 Swipe Delete UI
|
||||
const renderRightActions = (
|
||||
messageId: string,
|
||||
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 (
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
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" />
|
||||
<Text style={styles(theme).deleteText}>Delete</Text>
|
||||
<Animated.View style={[styles(theme).deleteSwipe, styleAnimation]}>
|
||||
<LucideIcon name="trash-outline" size={24} color="#fff" />
|
||||
<Text style={styles(theme).deleteText}>Delete</Text>
|
||||
</Animated.View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
const NotificationItem = ({
|
||||
item,
|
||||
onDelete,
|
||||
onPressNotification,
|
||||
theme,
|
||||
}: {
|
||||
item: any;
|
||||
onDelete: (id: string) => void;
|
||||
onPressNotification: (
|
||||
messageId: string,
|
||||
screen: string,
|
||||
groupId: string,
|
||||
cycleNumber: string,
|
||||
contributionId: string
|
||||
) => void;
|
||||
theme: any;
|
||||
}) => {
|
||||
const scale = useSharedValue(1);
|
||||
const animatedStyle = useAnimatedStyle(() => ({
|
||||
transform: [{ scale: scale.value }],
|
||||
}));
|
||||
const NotificationItem = ({
|
||||
item,
|
||||
onDelete,
|
||||
onPressNotification,
|
||||
theme,
|
||||
}: {
|
||||
item: any;
|
||||
onDelete: (id: string) => void;
|
||||
onPressNotification: (
|
||||
messageId: string,
|
||||
screen: string,
|
||||
groupId: string,
|
||||
cycleNumber: string,
|
||||
contributionId: string
|
||||
) => void;
|
||||
theme: any;
|
||||
}) => {
|
||||
const scale = useSharedValue(1);
|
||||
const animatedStyle = useAnimatedStyle(() => ({
|
||||
transform: [{ scale: scale.value }],
|
||||
}));
|
||||
|
||||
return (
|
||||
<Swipeable
|
||||
renderRightActions={(_, __, swipeableMethods) =>
|
||||
renderRightActions(item.messageId, onDelete, swipeableMethods)
|
||||
}
|
||||
>
|
||||
<Animated.View style={animatedStyle}>
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.9}
|
||||
onPressIn={() => (scale.value = withSpring(0.96))}
|
||||
onPressOut={() => (scale.value = withSpring(1))}
|
||||
onPress={() =>
|
||||
onPressNotification(
|
||||
item.messageId,
|
||||
item?.data?.msgType,
|
||||
item?.data?.groupId,
|
||||
item?.data?.cycleNumber,
|
||||
item?.data?.contributionId
|
||||
)
|
||||
}
|
||||
>
|
||||
<LinearGradient
|
||||
colors={
|
||||
item.read
|
||||
? [theme.colors.white, '#F8FAFF']
|
||||
: ['#FFF7ED', '#EEF2FF']
|
||||
return (
|
||||
<Swipeable
|
||||
friction={2}
|
||||
enableTrackpadTwoFingerGesture
|
||||
rightThreshold={40}
|
||||
renderRightActions={(prog, drag, swipeableMethods) =>
|
||||
renderRightActions(item.messageId, onDelete, swipeableMethods, drag)
|
||||
}
|
||||
>
|
||||
<Animated.View style={animatedStyle}>
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.9}
|
||||
onPressIn={() => (scale.value = withSpring(0.98))}
|
||||
onPressOut={() => (scale.value = withSpring(1))}
|
||||
onPress={() =>
|
||||
onPressNotification(
|
||||
item.messageId,
|
||||
item?.data?.msgType,
|
||||
item?.data?.groupId,
|
||||
item?.data?.cycleNumber,
|
||||
item?.data?.contributionId
|
||||
)
|
||||
}
|
||||
style={[
|
||||
styles(theme).card,
|
||||
!item.read && styles(theme).unread,
|
||||
{
|
||||
borderLeftColor: getNotificationColor(
|
||||
item.data.msgType
|
||||
),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<View style={styles(theme).row}>
|
||||
<View style={styles(theme).iconBox}>
|
||||
<LucideIcon
|
||||
name={getNotificationIcon(item.data.msgType)}
|
||||
size={22}
|
||||
color={getNotificationColor(item.data.msgType)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={styles(theme).content}>
|
||||
<View style={styles(theme).topRow}>
|
||||
<Text
|
||||
style={[
|
||||
styles(theme).title,
|
||||
!item.read && styles(theme).unreadTitle,
|
||||
]}
|
||||
>
|
||||
{item.data.title}
|
||||
</Text>
|
||||
<Text style={styles(theme).time}>
|
||||
{formatTime(item.sentTime)}
|
||||
</Text>
|
||||
<View
|
||||
style={[
|
||||
styles(theme).card,
|
||||
!item.read && styles(theme).unreadCard,
|
||||
]}
|
||||
>
|
||||
<View style={styles(theme).cardContent}>
|
||||
<View
|
||||
style={[
|
||||
styles(theme).iconContainer,
|
||||
{ backgroundColor: getNotificationColor(item.data.msgType) + '15' }
|
||||
]}
|
||||
>
|
||||
<LucideIcon
|
||||
name={getNotificationIcon(item.data.msgType)}
|
||||
size={24}
|
||||
color={getNotificationColor(item.data.msgType)}
|
||||
/>
|
||||
</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
|
||||
style={[
|
||||
styles(theme).tag,
|
||||
{
|
||||
backgroundColor: getNotificationColor(
|
||||
item.data.msgType
|
||||
),
|
||||
},
|
||||
]}
|
||||
>
|
||||
{getFilterDisplayName(item.data.msgType)}
|
||||
<Text numberOfLines={2} style={styles(theme).bodyText}>
|
||||
{item.data.body}
|
||||
</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>
|
||||
|
||||
{!item.read && <View style={styles(theme).dot} />}
|
||||
</View>
|
||||
</LinearGradient>
|
||||
</TouchableOpacity>
|
||||
</Animated.View>
|
||||
</Swipeable>
|
||||
);
|
||||
};
|
||||
</TouchableOpacity>
|
||||
</Animated.View>
|
||||
</Swipeable>
|
||||
);
|
||||
};
|
||||
|
||||
const renderItem = ({ item }: any) => (
|
||||
<NotificationItem
|
||||
@@ -252,9 +319,28 @@ const NotificationItem = ({
|
||||
return (
|
||||
<AppView>
|
||||
<Header
|
||||
title={
|
||||
'Notifications' +
|
||||
(unreadCount ? ` (${unreadCount})` : '')
|
||||
title="Notifications"
|
||||
rightAction={
|
||||
<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}
|
||||
renderItem={renderItem}
|
||||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={styles(theme).container}
|
||||
contentContainerStyle={styles(theme).listContainer}
|
||||
ListEmptyComponent={
|
||||
<View style={styles(theme).empty}>
|
||||
<LucideIcon
|
||||
name="notifications-off-outline"
|
||||
size={64}
|
||||
color="#ccc"
|
||||
/>
|
||||
<Text style={styles(theme).emptyText}>
|
||||
No notifications
|
||||
<View style={styles(theme).emptyContainer}>
|
||||
<View style={styles(theme).emptyCircle}>
|
||||
<LucideIcon
|
||||
name="notifications-off-outline"
|
||||
size={48}
|
||||
color="#A1A1AA"
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles(theme).emptyTitle}>Stay Tuned!</Text>
|
||||
<Text style={styles(theme).emptySubText}>
|
||||
We'll notify you when something important happens.
|
||||
</Text>
|
||||
</View>
|
||||
}
|
||||
@@ -285,117 +374,164 @@ export default Notification;
|
||||
|
||||
const styles = (theme: any) =>
|
||||
StyleSheet.create({
|
||||
container: {
|
||||
padding: 12,
|
||||
listContainer: {
|
||||
padding: 16,
|
||||
paddingBottom: 40,
|
||||
},
|
||||
|
||||
card: {
|
||||
borderRadius: 16,
|
||||
padding: 16,
|
||||
backgroundColor: theme.colors.white || '#FFFFFF',
|
||||
borderRadius: 20,
|
||||
padding: 14,
|
||||
marginBottom: 12,
|
||||
backgroundColor: 'rgba(255,255,255,0.95)',
|
||||
shadowColor: '#000',
|
||||
shadowOpacity: 0.08,
|
||||
shadowRadius: 10,
|
||||
elevation: 4,
|
||||
borderLeftWidth: 5,
|
||||
},
|
||||
|
||||
unread: {
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.05,
|
||||
shadowRadius: 8,
|
||||
elevation: 2,
|
||||
borderWidth: 1,
|
||||
borderColor: '#E0E7FF',
|
||||
shadowColor: '#6366F1',
|
||||
shadowOpacity: 0.15,
|
||||
borderColor: '#F4F4F5',
|
||||
},
|
||||
|
||||
row: {
|
||||
unreadCard: {
|
||||
borderColor: (theme.colors.primary || '#6366F1') + '20',
|
||||
backgroundColor: (theme.colors.primary || '#6366F1') + '05',
|
||||
},
|
||||
cardContent: {
|
||||
flexDirection: 'row',
|
||||
gap: 12,
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
|
||||
iconBox: {
|
||||
backgroundColor: '#F1F5F9',
|
||||
padding: 10,
|
||||
borderRadius: 12,
|
||||
iconContainer: {
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: 14,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginRight: 12,
|
||||
},
|
||||
|
||||
content: {
|
||||
textContent: {
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
topRow: {
|
||||
headerRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 4,
|
||||
},
|
||||
|
||||
title: {
|
||||
fontWeight: '700',
|
||||
fontFamily: FontsFamily.nunitoBold,
|
||||
fontSize: 15,
|
||||
color: theme.colors.textPrimary,
|
||||
color: theme.colors.textPrimary || '#18181B',
|
||||
flex: 1,
|
||||
marginRight: 8,
|
||||
},
|
||||
|
||||
unreadTitle: {
|
||||
color: theme.colors.primary,
|
||||
color: theme.colors.primary || '#6366F1',
|
||||
},
|
||||
|
||||
message: {
|
||||
fontSize: 13,
|
||||
color: theme.colors.textSecondary,
|
||||
marginTop: 4,
|
||||
},
|
||||
|
||||
time: {
|
||||
fontFamily: FontsFamily.nunitoRegular,
|
||||
fontSize: 11,
|
||||
color: '#6B7280',
|
||||
color: '#71717A',
|
||||
},
|
||||
|
||||
tagWrap: {
|
||||
marginTop: 6,
|
||||
bodyText: {
|
||||
fontFamily: FontsFamily.nunitoRegular,
|
||||
fontSize: 13,
|
||||
color: theme.colors.textSecondary || '#52525B',
|
||||
lineHeight: 18,
|
||||
marginBottom: 8,
|
||||
},
|
||||
|
||||
tag: {
|
||||
fontSize: 10,
|
||||
color: '#fff',
|
||||
paddingHorizontal: 10,
|
||||
footerRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
badge: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 20,
|
||||
fontWeight: '700',
|
||||
alignSelf: 'flex-start',
|
||||
borderRadius: 8,
|
||||
},
|
||||
|
||||
dot: {
|
||||
badgeDot: {
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: 3,
|
||||
marginRight: 6,
|
||||
},
|
||||
badgeText: {
|
||||
fontFamily: FontsFamily.nunitoSemiBold,
|
||||
fontSize: 10,
|
||||
textTransform: 'capitalize',
|
||||
},
|
||||
unreadIndicator: {
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: 4,
|
||||
backgroundColor: theme.colors.primary,
|
||||
marginTop: 6,
|
||||
backgroundColor: theme.colors.primary || '#6366F1',
|
||||
},
|
||||
deleteSwipeContainer: {
|
||||
width: 90,
|
||||
height: '100%',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'flex-end',
|
||||
marginBottom: 12, // Match card margin
|
||||
},
|
||||
|
||||
deleteSwipe: {
|
||||
backgroundColor: '#FF3B30',
|
||||
width: 80,
|
||||
height: '84%', // A bit shorter than the container for visual balance
|
||||
borderRadius: 20,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: 80,
|
||||
borderRadius: 16,
|
||||
marginBottom: 12,
|
||||
shadowColor: '#FF3B30',
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 8,
|
||||
elevation: 5,
|
||||
},
|
||||
|
||||
deleteText: {
|
||||
color: '#fff',
|
||||
color: '#FFFFFF',
|
||||
fontFamily: FontsFamily.nunitoBold,
|
||||
fontSize: 12,
|
||||
marginTop: 4,
|
||||
},
|
||||
|
||||
empty: {
|
||||
headerActions: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginTop: 120,
|
||||
gap: 8,
|
||||
},
|
||||
|
||||
emptyText: {
|
||||
marginTop: 12,
|
||||
color: '#999',
|
||||
fontSize: 16,
|
||||
headerIconButton: {
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: 10,
|
||||
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,
|
||||
},
|
||||
});
|
||||
@@ -321,7 +321,7 @@ const PaymentModeScreen: React.FC = () => {
|
||||
payload
|
||||
) as any;
|
||||
|
||||
console.log("payment res",res)
|
||||
console.log("payment res", res)
|
||||
|
||||
if (res?.status === 'success' || res?.statusCode === 200) {
|
||||
const pgOrderNo = res?.data?.pgOrderNo;
|
||||
@@ -341,7 +341,7 @@ const PaymentModeScreen: React.FC = () => {
|
||||
setPaying(false);
|
||||
}
|
||||
}
|
||||
const handlePaymentForRouteFormGroupDetails = async () => {
|
||||
const handlePaymentForRouteFormGroupDetails = async (PaymentMode: number) => {
|
||||
try {
|
||||
|
||||
setPaying(true);
|
||||
@@ -359,35 +359,62 @@ const PaymentModeScreen: React.FC = () => {
|
||||
payload
|
||||
) as any;
|
||||
|
||||
console.log("payment payload ",payload)
|
||||
console.log("payment res ",res)
|
||||
if (res?.status === 'success' || res?.statusCode === 200) {
|
||||
console.log("payment payload ", payload)
|
||||
console.log("payment res ", res)
|
||||
switch (PaymentMode) {
|
||||
case 6:
|
||||
{
|
||||
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);
|
||||
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;
|
||||
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) {
|
||||
ToastAndroid.show('An error occurred during payment', ToastAndroid.SHORT);
|
||||
setPaying(false);
|
||||
}
|
||||
}
|
||||
const handleConfirmPayment = async () => {
|
||||
console.log("selectedMode", selectedMode)
|
||||
if (!selectedMode) {
|
||||
|
||||
ToastAndroid.show('Please select a payment method', ToastAndroid.SHORT);
|
||||
return;
|
||||
}
|
||||
switch (routeFrom) {
|
||||
case 'GroupDetails':
|
||||
handlePaymentForRouteFormGroupDetails();
|
||||
handlePaymentForRouteFormGroupDetails(selectedMode);
|
||||
break;
|
||||
case 'GroupScreen':
|
||||
handlePaymentForRouteFormGroupScreen();
|
||||
|
||||
@@ -39,6 +39,9 @@ import {
|
||||
USER_MOBILE_MONEY_URL,
|
||||
} from "../../utils/ApiUrls";
|
||||
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 {
|
||||
id: string;
|
||||
@@ -183,9 +186,12 @@ const Profile = () => {
|
||||
refetchStatus();
|
||||
}, [refetchProfile, refetchStatus]);
|
||||
|
||||
|
||||
const handleLogout = async () => {
|
||||
setIsLoggingOut(true);
|
||||
|
||||
try {
|
||||
// 🔹 Remove FCM Token
|
||||
const fcmToken = await getFcmToken(user?.userId?.toString());
|
||||
if (fcmToken) {
|
||||
await fetch(`${BASE_URL}token/remove?fcmToken=${fcmToken}`, {
|
||||
@@ -194,15 +200,34 @@ const Profile = () => {
|
||||
Authorization: `Bearer ${(await Keychain.getGenericPassword() as any).password}`,
|
||||
},
|
||||
});
|
||||
|
||||
await deleteFcmToken(user?.userId?.toString());
|
||||
}
|
||||
|
||||
// 🔹 Clear Redux State
|
||||
dispatch(clearAllNotifications());
|
||||
await Keychain.resetGenericPassword();
|
||||
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) {
|
||||
console.error("Logout error:", error);
|
||||
} finally {
|
||||
setIsLoggingOut(false);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user