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);
|
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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -319,9 +319,9 @@ const PaymentModeScreen: React.FC = () => {
|
|||||||
`${BASE_URL}payments/pg/entry-fee/${groupId}?groupId=${groupId}&paymentId=${selectedMode}&mobileNumber=`,
|
`${BASE_URL}payments/pg/entry-fee/${groupId}?groupId=${groupId}&paymentId=${selectedMode}&mobileNumber=`,
|
||||||
'POST',
|
'POST',
|
||||||
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);
|
||||||
@@ -358,36 +358,63 @@ const PaymentModeScreen: React.FC = () => {
|
|||||||
'POST',
|
'POST',
|
||||||
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:
|
||||||
const pgOrderNo = res?.data?.pgOrderNo;
|
{
|
||||||
if (pgOrderNo) {
|
if (res?.status === 'success' || res?.statusCode === 200) {
|
||||||
// Start polling for transaction status
|
|
||||||
pollTransactionStatus(pgOrderNo);
|
const pgOrderNo = res?.data?.pgOrderNo;
|
||||||
} else {
|
if (pgOrderNo) {
|
||||||
ToastAndroid.show('Payment initiated but no order number received', ToastAndroid.SHORT);
|
// Start polling for transaction status
|
||||||
setPaying(false);
|
pollTransactionStatus(pgOrderNo);
|
||||||
}
|
} else {
|
||||||
} else {
|
ToastAndroid.show('Payment initiated but no order number received', ToastAndroid.SHORT);
|
||||||
ToastAndroid.show(res?.data?.message || res?.message || 'Payment failed', ToastAndroid.SHORT);
|
setPaying(false);
|
||||||
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) {
|
} 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();
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user