From 80fdb77590bf441844b914a85ab7460205c6e3dd Mon Sep 17 00:00:00 2001 From: Jaanu423 Date: Tue, 7 Oct 2025 15:33:24 +0530 Subject: [PATCH] notification store and update in local storage --- index.js | 7 +- package-lock.json | 46 +++++- package.json | 4 +- src/components/common/AnimatedCard.tsx | 15 +- src/components/common/Header.tsx | 10 +- src/navigation/AppNavigator.tsx | 3 + src/redux/notificationSlice.ts | 58 ++++++++ src/redux/store.ts | 57 ++++++-- src/screens/home/Home.tsx | 46 +++--- src/screens/notification/Notification.tsx | 170 ++++++++++++++++++++++ src/screens/profile/Profile.tsx | 4 +- src/utils/ApiUrls.tsx | 2 +- 12 files changed, 375 insertions(+), 47 deletions(-) create mode 100644 src/redux/notificationSlice.ts create mode 100644 src/screens/notification/Notification.tsx diff --git a/index.js b/index.js index 0007f0e..34dcb3e 100644 --- a/index.js +++ b/index.js @@ -6,12 +6,15 @@ import { AppRegistry } from 'react-native'; import App from './App'; import { name as appName } from './app.json'; import { Provider } from 'react-redux'; -import { store } from './src/redux/store'; +import { persistor, store } from './src/redux/store'; +import { PersistGate } from 'redux-persist/integration/react'; // ✅ Wrapper with Provider const ReduxWrapper = () => ( - + + + ); diff --git a/package-lock.json b/package-lock.json index 79f3dd4..90e8877 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.1", "dependencies": { "@notifee/react-native": "^9.1.8", + "@react-native-async-storage/async-storage": "^1.24.0", "@react-native-clipboard/clipboard": "^1.16.3", "@react-native-community/blur": "^4.4.1", "@react-native-community/slider": "^5.0.1", @@ -45,7 +46,8 @@ "react-native-vector-icons": "^10.3.0", "react-native-walkthrough-tooltip": "^1.6.0", "react-native-worklets": "^0.6.0", - "react-redux": "^9.2.0" + "react-redux": "^9.2.0", + "redux-persist": "^6.0.0" }, "devDependencies": { "@babel/core": "^7.25.2", @@ -3288,6 +3290,18 @@ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", "license": "BSD-3-Clause" }, + "node_modules/@react-native-async-storage/async-storage": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.24.0.tgz", + "integrity": "sha512-W4/vbwUOYOjco0x3toB8QCr7EjIP6nE9G7o8PMguvvjYT5Awg09lyV4enACRx4s++PPulBiBSjL0KTFx2u0Z/g==", + "license": "MIT", + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.60 <1.0" + } + }, "node_modules/@react-native-clipboard/clipboard": { "version": "1.16.3", "license": "MIT", @@ -7892,6 +7906,15 @@ "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-regex": { "version": "1.2.1", "dev": true, @@ -9094,6 +9117,18 @@ "version": "5.2.1", "license": "MIT" }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "license": "MIT", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "license": "MIT" @@ -13213,6 +13248,15 @@ "version": "5.0.1", "license": "MIT" }, + "node_modules/redux-persist": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz", + "integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==", + "license": "MIT", + "peerDependencies": { + "redux": ">4.0.0" + } + }, "node_modules/redux-thunk": { "version": "3.1.0", "license": "MIT", diff --git a/package.json b/package.json index 802ca1b..f618d1b 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@notifee/react-native": "^9.1.8", + "@react-native-async-storage/async-storage": "^1.24.0", "@react-native-clipboard/clipboard": "^1.16.3", "@react-native-community/blur": "^4.4.1", "@react-native-community/slider": "^5.0.1", @@ -47,7 +48,8 @@ "react-native-vector-icons": "^10.3.0", "react-native-walkthrough-tooltip": "^1.6.0", "react-native-worklets": "^0.6.0", - "react-redux": "^9.2.0" + "react-redux": "^9.2.0", + "redux-persist": "^6.0.0" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/src/components/common/AnimatedCard.tsx b/src/components/common/AnimatedCard.tsx index 7fb8a03..3878505 100644 --- a/src/components/common/AnimatedCard.tsx +++ b/src/components/common/AnimatedCard.tsx @@ -9,13 +9,13 @@ import { StyleProp, } from "react-native"; import LinearGradient from "react-native-linear-gradient"; -import { Ionicons } from "@react-native-vector-icons/ionicons"; +import { Ionicons, IoniconsIconName } from "@react-native-vector-icons/ionicons"; import colors from "../colors"; // Assuming your pastel colors are defined here type CardProps = { title: string; subtitle?: string; - iconName?: string; + iconName?: IoniconsIconName; onPress?: () => void; backOne?: string; backTwo?: string; @@ -89,7 +89,7 @@ const AnimatedCard: React.FC = ({ const styles = StyleSheet.create({ card: { - minWidth: 170, + // minWidth: 170, flex: 1, height: 170, marginHorizontal: 8, @@ -101,13 +101,16 @@ const styles = StyleSheet.create({ shadowOpacity: 0.15, shadowRadius: 12, elevation: 5, - backgroundColor: colors.white, }, gradient: { position: "relative", flex: 1, - justifyContent: "center", - alignItems: "center", + // justifyContent: "center", + // alignItems: "center", + justifyContent: "space-between", + borderRadius: 24, + overflow: "hidden", + gap: 10, }, graphicCircleLarge: { position: "absolute", diff --git a/src/components/common/Header.tsx b/src/components/common/Header.tsx index 5c3a0e9..076b9f9 100644 --- a/src/components/common/Header.tsx +++ b/src/components/common/Header.tsx @@ -2,6 +2,7 @@ import React from "react"; import { View, Text, TouchableOpacity, StyleSheet } from "react-native"; import { useNavigation } from "@react-navigation/native"; import {Ionicons} from "@react-native-vector-icons/ionicons"; +import colors from "../colors"; type HeaderProps = { title: string; @@ -17,8 +18,8 @@ const Header: React.FC = ({ showBack = true, onBackPress, rightAction, - titleColor = "", - arrowColor='pink' + titleColor = colors.primary, + arrowColor = colors.primary }) => { const navigation = useNavigation(); @@ -32,6 +33,11 @@ const Header: React.FC = ({ return ( + {showBack && ( + + + + )} {title} {rightAction} diff --git a/src/navigation/AppNavigator.tsx b/src/navigation/AppNavigator.tsx index 1b460d5..0352de6 100644 --- a/src/navigation/AppNavigator.tsx +++ b/src/navigation/AppNavigator.tsx @@ -20,6 +20,7 @@ import InviteScreen from "../screens/InviteScreen"; import ManagementTabNavigation from "../screens/management/ManagementTabNavigation"; import TransactionsList from "../screens/transaction/TransactionList"; import RandomSelectUserScreen from "../screens/management/RandomSelectUserScreen"; +import Notification from "../screens/notification/Notification"; export type RootStackParamList = { Splash: undefined; @@ -42,6 +43,7 @@ export type RootStackParamList = { ManagementTabs:{ groupId: string | number;} TransactionList:undefined RandomSelectUserScreen: { groupId: string | number, cycleNumber: number }; + Notification:undefined }; const Stack = createNativeStackNavigator(); @@ -95,6 +97,7 @@ const linking = { + ); diff --git a/src/redux/notificationSlice.ts b/src/redux/notificationSlice.ts new file mode 100644 index 0000000..8560a42 --- /dev/null +++ b/src/redux/notificationSlice.ts @@ -0,0 +1,58 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +// Define the shape of a notification object +export interface Notification { + id: string; + title: string; + message: string; + time: string; + read: boolean; +} + +// Define the slice state type +interface NotificationState { + notifications: Notification[]; +} + +// Initial state +const initialState: NotificationState = { + notifications: [], +}; + +const notificationSlice = createSlice({ + name: 'notifications', + initialState, + reducers: { + addNotification: (state, action: PayloadAction) => { + // Adds the new notification to the top + state.notifications.unshift(action.payload); + }, + removeNotification: (state, action: PayloadAction) => { + // Remove notification by id + state.notifications = state.notifications.filter( + (item) => item.id !== action.payload + ); + }, + clearAllNotifications: (state) => { + state.notifications = []; + }, + markAsRead: (state, action: PayloadAction) => { + // Mark one notification as read + const notification = state.notifications.find( + (item) => item.id === action.payload + ); + if (notification) { + notification.read = true; + } + }, + }, +}); + +export const { + addNotification, + removeNotification, + clearAllNotifications, + markAsRead, +} = notificationSlice.actions; + +export default notificationSlice.reducer; diff --git a/src/redux/store.ts b/src/redux/store.ts index 4182583..28405b8 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -1,13 +1,50 @@ -// store.ts -import { configureStore } from "@reduxjs/toolkit"; -import userReducer from "./userSlice"; // adjust the path +// // store.ts +// import { configureStore } from "@reduxjs/toolkit"; +// import userReducer from "./userSlice"; // adjust the path -export const store = configureStore({ - reducer: { - user: userReducer, // register user slice here - }, +// export const store = configureStore({ +// reducer: { +// user: userReducer, // register user slice here +// }, +// }); + +// // ✅ Types for TypeScript +// export type RootState = ReturnType; +// export type AppDispatch = typeof store.dispatch; + + + +import { configureStore, combineReducers } from '@reduxjs/toolkit'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { persistReducer, persistStore } from 'redux-persist'; + +// import slices +import userReducer from './userSlice'; +import notificationReducer from './notificationSlice'; + +// combine reducers +const rootReducer = combineReducers({ + user: userReducer, + notifications: notificationReducer, }); -// ✅ Types for TypeScript -export type RootState = ReturnType; -export type AppDispatch = typeof store.dispatch; +// persist configuration +const persistConfig = { + key: 'root', + storage: AsyncStorage, + whitelist: ['user', 'notifications'], // persist both slices +}; + +// wrap root reducer with persistReducer +const persistedReducer = persistReducer(persistConfig, rootReducer); + +// configure store +export const store = configureStore({ + reducer: persistedReducer, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware({ + serializableCheck: false, + }), +}); + +export const persistor = persistStore(store); diff --git a/src/screens/home/Home.tsx b/src/screens/home/Home.tsx index b33133b..3514090 100644 --- a/src/screens/home/Home.tsx +++ b/src/screens/home/Home.tsx @@ -20,7 +20,7 @@ import { setUserData } from "../../redux/userSlice"; import { TabNavigationProp } from "../../navigation/BottomTabs"; import UserProfileStatus from "./UserProfileStatus"; import AnimatedCard from "../../components/common/AnimatedCard"; -import { Ionicons } from "@react-native-vector-icons/ionicons"; +import { Ionicons, IoniconsIconName } from "@react-native-vector-icons/ionicons"; import { apiCall } from "../../utils/ApiHelper"; if (Platform.OS === "android" && UIManager.setLayoutAnimationEnabledExperimental) { @@ -49,16 +49,16 @@ interface ActionItem { // Updated action items with a prettier pastel palette const organizerActions: ActionItem[] = [ - { id: "1", title: "My Groups", subtitle: "Manage your groups", action: "NAVIGATE_MANAGEMENT", tab: "My Groups", iconName: "people-circle-outline", backOne: '#decbfcff', backTwo: '#B799FF' }, - { id: "2", title: "Joined Groups", subtitle: "View member groups", action: "NAVIGATE_MANAGEMENT", tab: "Joined Groups", iconName: "people-outline", backOne: '#D7E9F7', backTwo: '#82AAE3' }, - { id: "3", title: "Requests", subtitle: "Check new requests", action: "NAVIGATE_MANAGEMENT", tab: "Requests", iconName: "notifications-outline", backOne: '#DFF6DD', backTwo: '#88D18A' }, - { id: "4", title: "Create Group", subtitle: "Create a new group", action: "NAVIGATE_CREATE_GROUP", tab: "Requests", iconName: "people-outline", backOne: '#DFF6DD', backTwo: '#88D18A' }, + { id: "1", title: "My Groups", subtitle: "Manage your groups", action: "NAVIGATE_MANAGEMENT", tab: "My Groups", iconName: "people-circle-outline", backOne: '#decbfcff', backTwo: '#B799FF' }, + { id: "2", title: "Joined Groups", subtitle: "View member groups", action: "NAVIGATE_MANAGEMENT", tab: "Joined Groups", iconName: "people-outline", backOne: '#D7E9F7', backTwo: '#82AAE3' }, + { id: "3", title: "Requests", subtitle: "Check new requests", action: "NAVIGATE_MANAGEMENT", tab: "Requests", iconName: "notifications-outline", backOne: '#DFF6DD', backTwo: '#88D18A' }, + { id: "4", title: "Create Group", subtitle: "Create a new group", action: "NAVIGATE_CREATE_GROUP", tab: "Requests", iconName: "people-outline", backOne: '#DFF6DD', backTwo: '#88D18A' }, ]; const userActions: ActionItem[] = [ - { id: "1", title: "Joined Groups", subtitle: "See your groups", action: "NAVIGATE_MANAGEMENT", tab: "Joined Groups", iconName: "person-outline", backOne: '#D7E9F7', backTwo: '#82AAE3' }, - { id: "2", title: "Your Requests", subtitle: "Track your requests", action: "NAVIGATE_MANAGEMENT", tab: "Your Requests", iconName: "mail-open-outline", backOne: '#FFF4D2', backTwo: '#FFB344' }, - { id: "3", title: "Transactions", subtitle: "View your history", action: "TRANSACTION", iconName: "receipt-outline", backOne: '#decbfcff', backTwo: '#B799FF' }, + { id: "1", title: "Joined Groups", subtitle: "See your groups", action: "NAVIGATE_MANAGEMENT", tab: "Joined Groups", iconName: "person-outline", backOne: '#D7E9F7', backTwo: '#82AAE3' }, + { id: "2", title: "Your Requests", subtitle: "Track your requests", action: "NAVIGATE_MANAGEMENT", tab: "Your Requests", iconName: "mail-open-outline", backOne: '#FFF4D2', backTwo: '#FFB344' }, + { id: "3", title: "Transactions", subtitle: "View your history", action: "TRANSACTION", iconName: "receipt-outline", backOne: '#decbfcff', backTwo: '#B799FF' }, ]; const Home: React.FC = () => { @@ -100,15 +100,15 @@ const Home: React.FC = () => { const renderGridItem: ListRenderItem = ({ item }) => ( - handleActionPress(item)} - /> + handleActionPress(item)} + /> ); @@ -124,15 +124,16 @@ const Home: React.FC = () => { Hello, {user.firstName || apiUser?.firstName || 'User'} - navigation.navigate("Profile" as never)}> - + navigation.navigate("Notification" as never)}> + {/* */} + - + Quick Actions - + { + const dispatch = useDispatch(); + const notifications = useSelector( + (state: any) => state.notifications.notifications + ); + + const handleAdd = () => { + const newNotification = { + id: Date.now().toString(), + title: 'New Message', + message: 'You have a new message in your inbox.', + time: new Date().toLocaleString(), + read: false, + }; + dispatch(addNotification(newNotification)); + }; + + const handleDelete = (id: string) => { + dispatch(removeNotification(id)); + }; + + const handleClearAll = () => { + dispatch(clearAllNotifications()); + }; + + return ( + + {/* Header with Add and Clear All */} +
+ + Delete All + + } + /> + + + {/* Add Notification Button */} + {/* + + Add Notification + + */} + + {/* Notification List */} + item.id} + showsVerticalScrollIndicator={false} + contentContainerStyle={styles.listContainer} + renderItem={({ item }) => ( + + + + {item.title} + {item.message} + {item.time} + + handleDelete(item.id)}> + + + + + )} + ListEmptyComponent={ + No notifications found. + } + /> + + ); +}; + +export default Notification; + +const styles = StyleSheet.create({ + headerContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingHorizontal: 16, + paddingTop: 12, + }, + headerTitle: { + fontSize: 20, + fontWeight: '700', + color: '#222', + }, + clearButtonGradient: { + borderRadius: 8, + paddingVertical: 6, + paddingHorizontal: 14, + }, + clearButtonText: { + color: '#fff', + fontWeight: '600', + }, + addButtonGradient: { + margin: 16, + borderRadius: 8, + paddingVertical: 10, + alignItems: 'center', + }, + addButtonText: { + color: '#fff', + fontWeight: '600', + fontSize: 16, + }, + listContainer: { + paddingHorizontal: 12, + paddingBottom: 30, + }, + notificationCard: { + borderRadius: 10, + padding: 12, + marginBottom: 10, + shadowColor: '#000', + shadowOpacity: 0.1, + shadowRadius: 5, + elevation: 2, + }, + notificationContent: { + flexDirection: 'row', + alignItems: 'center', + }, + title: { + fontWeight: '700', + fontSize: 16, + color: '#333', + }, + message: { + fontSize: 14, + color: '#555', + marginTop: 2, + }, + time: { + fontSize: 12, + color: '#888', + marginTop: 4, + }, + emptyText: { + textAlign: 'center', + marginTop: 60, + fontSize: 16, + color: '#999', + }, +}); diff --git a/src/screens/profile/Profile.tsx b/src/screens/profile/Profile.tsx index 3e44d59..0ad60aa 100644 --- a/src/screens/profile/Profile.tsx +++ b/src/screens/profile/Profile.tsx @@ -377,7 +377,7 @@ const Profile = () => { User Profile confirmLogout(navigation)} style={styles.refreshButton}> - + {/* if (fetching || !user) { @@ -701,7 +701,7 @@ const styles = StyleSheet.create({ borderBottomRightRadius: 20, }, headerTitle: { - fontSize: 22, fontWeight: "600", color: colors.Profile + fontSize: 22, fontWeight: "600", color: colors.primary }, refreshButton: { padding: 4, diff --git a/src/utils/ApiUrls.tsx b/src/utils/ApiUrls.tsx index c407bae..32433ac 100644 --- a/src/utils/ApiUrls.tsx +++ b/src/utils/ApiUrls.tsx @@ -1,6 +1,6 @@ -export const BASE_URL = "https://api.godemo.in/FundCollect/api/" // export const BASE_URL = "https://api.godemo.in/FundCollect/api/" +export const BASE_URL = "http://192.168.1.5:9060/api/" export const LOGIN_URL = `${BASE_URL}user/generate/otp?mobiles=` export const VARIFY_OTP_URL = `${BASE_URL}user/otp/verify` export const ORGRAGISTRATION = `${BASE_URL}organizer/registration/apply`