diff --git a/App.tsx b/App.tsx index 30d539a..c9244e1 100644 --- a/App.tsx +++ b/App.tsx @@ -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 ( - - - - - - - - - + + + + + + + + + + + ); } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index e1a825a..b8852ba 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -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"> diff --git a/package.json b/package.json index 78755c5..b1d43e8 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/screens/notification/Notification.tsx b/src/screens/notification/Notification.tsx index 54f9c09..fcf81fc 100644 --- a/src/screens/notification/Notification.tsx +++ b/src/screens/notification/Notification.tsx @@ -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 ) => { + const styleAnimation = useAnimatedStyle(() => { + const translateX = interpolate( + drag.value, + [-100, 0], + [0, 100], + Extrapolate.CLAMP + ); + return { + transform: [{ translateX }], + }; + }); + return ( { onDeleteAction(messageId); - swipeableMethods?.close?.(); + swipeableMethods.close(); }} - style={styles(theme).deleteSwipe} + activeOpacity={0.8} + style={styles(theme).deleteSwipeContainer} > - - Delete + + + Delete + ); }; -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 ( - - renderRightActions(item.messageId, onDelete, swipeableMethods) - } - > - - (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 - ) - } - > - + renderRightActions(item.messageId, onDelete, swipeableMethods, drag) + } + > + + (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 - ), - }, - ]} > - - - - - - - - - {item.data.title} - - - {formatTime(item.sentTime)} - + + + + - {item.data.body} + + + + {item.data.title} + + + {formatTime(item.sentTime)} + + - - - {getFilterDisplayName(item.data.msgType)} + + {item.data.body} + + + + + + {getFilterDisplayName(item.data.msgType)} + + + + {!item.read && ( + + )} + - - {!item.read && } - - - - - ); -}; + + + + ); + }; const renderItem = ({ item }: any) => (
+ {notifications.length > 0 && ( + + + + )} + {notifications.length > 0 && ( + + + + )} + } /> @@ -263,16 +349,19 @@ const NotificationItem = ({ keyExtractor={(item) => item.messageId} renderItem={renderItem} showsVerticalScrollIndicator={false} - contentContainerStyle={styles(theme).container} + contentContainerStyle={styles(theme).listContainer} ListEmptyComponent={ - - - - No notifications + + + + + Stay Tuned! + + We'll notify you when something important happens. } @@ -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', }, - }); \ No newline at end of file + 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, + }, + }); diff --git a/src/screens/payment/PaymentModeScreen.tsx b/src/screens/payment/PaymentModeScreen.tsx index 5450ea7..2cac4c7 100644 --- a/src/screens/payment/PaymentModeScreen.tsx +++ b/src/screens/payment/PaymentModeScreen.tsx @@ -319,9 +319,9 @@ const PaymentModeScreen: React.FC = () => { `${BASE_URL}payments/pg/entry-fee/${groupId}?groupId=${groupId}&paymentId=${selectedMode}&mobileNumber=`, 'POST', payload - ) as any; + ) 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); @@ -358,36 +358,63 @@ const PaymentModeScreen: React.FC = () => { 'POST', payload ) as any; - - console.log("payment payload ",payload) - console.log("payment res ",res) - 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); + + 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); + } + } + 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(); diff --git a/src/screens/profile/Profile.tsx b/src/screens/profile/Profile.tsx index 9ac97a1..8a182a2 100644 --- a/src/screens/profile/Profile.tsx +++ b/src/screens/profile/Profile.tsx @@ -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); } };