notification store and update in local storage

This commit is contained in:
Jaanu423
2025-10-07 15:33:24 +05:30
parent d197e31acb
commit 80fdb77590
12 changed files with 375 additions and 47 deletions
+5 -2
View File
@@ -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 = () => (
<Provider store={store}>
<App />
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>
);
+45 -1
View File
@@ -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",
+3 -1
View File
@@ -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",
+9 -6
View File
@@ -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<CardProps> = ({
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",
+8 -2
View File
@@ -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<HeaderProps> = ({
showBack = true,
onBackPress,
rightAction,
titleColor = "",
arrowColor='pink'
titleColor = colors.primary,
arrowColor = colors.primary
}) => {
const navigation = useNavigation();
@@ -32,6 +33,11 @@ const Header: React.FC<HeaderProps> = ({
return (
<View style={styles.header}>
{showBack && (
<TouchableOpacity onPress={handleBack} style={{padding:6}}>
<Ionicons name="arrow-back" size={24} color={arrowColor} />
</TouchableOpacity>
)}
<Text style={[styles.headerTitle, {color:titleColor}]}>{title}</Text>
<View>{rightAction}</View>
</View>
+3
View File
@@ -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<RootStackParamList>();
@@ -95,6 +97,7 @@ const linking = {
<Stack.Screen name="ManagementTabs" component={ManagementTabNavigation} options={{ headerShown: false }} />
<Stack.Screen name="TransactionList" component={TransactionsList} options={{ headerShown: false }} />
<Stack.Screen name="RandomSelectUserScreen" component={RandomSelectUserScreen} options={{ headerShown: false }} />
<Stack.Screen name="Notification" component={Notification} options={{ headerShown: false }} />
</Stack.Navigator>
</NavigationContainer>
);
+58
View File
@@ -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<Notification>) => {
// Adds the new notification to the top
state.notifications.unshift(action.payload);
},
removeNotification: (state, action: PayloadAction<string>) => {
// Remove notification by id
state.notifications = state.notifications.filter(
(item) => item.id !== action.payload
);
},
clearAllNotifications: (state) => {
state.notifications = [];
},
markAsRead: (state, action: PayloadAction<string>) => {
// 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;
+47 -10
View File
@@ -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<typeof store.getState>;
// 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<typeof store.getState>;
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);
+22 -20
View File
@@ -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<ActionItem> = ({ item }) => (
<View style={styles.cardWrapper}>
<AnimatedCard
key={item.id}
title={item.title}
subtitle={item.subtitle}
iconName={item.iconName}
backOne={item.backOne}
backTwo={item.backTwo}
onPress={() => handleActionPress(item)}
/>
<AnimatedCard
key={item.id}
title={item.title}
subtitle={item.subtitle}
iconName={item.iconName}
backOne={item.backOne}
backTwo={item.backTwo}
onPress={() => handleActionPress(item)}
/>
</View>
);
@@ -124,8 +124,9 @@ const Home: React.FC = () => {
<Text style={styles.greeting}>Hello,</Text>
<Text style={styles.userName}>{user.firstName || apiUser?.firstName || 'User'}</Text>
</View>
<TouchableOpacity onPress={() => navigation.navigate("Profile" as never)}>
<Image source={getProfileImageUri()} style={styles.profileImage} />
<TouchableOpacity onPress={() => navigation.navigate("Notification" as never)}>
{/* <Image source={getProfileImageUri()} style={styles.profileImage} /> */}
<Ionicons name="notifications-outline" size={30} color={colors.textDark} />
</TouchableOpacity>
</View>
@@ -184,6 +185,7 @@ const styles = StyleSheet.create({
},
cardWrapper: {
flex: 1,
marginHorizontal: 8,
// marginHorizontal: 8,
// backgroundColor:'red'
},
});
+170
View File
@@ -0,0 +1,170 @@
import React from 'react';
import {
View,
Text,
TouchableOpacity,
FlatList,
StyleSheet,
} from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import {
addNotification,
removeNotification,
clearAllNotifications,
} from '../../redux/notificationSlice';
import LinearGradient from 'react-native-linear-gradient';
import { Ionicons } from '@react-native-vector-icons/ionicons';
import AppView from '../../components/AppView';
import Header from '../../components/common/Header';
const Notification = () => {
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 (
<AppView>
{/* Header with Add and Clear All */}
<Header title='Notifications' rightAction={<LinearGradient
colors={['#FF6B6B', '#FF8E53']}
style={styles.clearButtonGradient}>
<TouchableOpacity onPress={handleClearAll}>
<Text style={styles.clearButtonText}>Delete All</Text>
</TouchableOpacity>
</LinearGradient>}
/>
{/* Add Notification Button */}
{/* <LinearGradient
colors={['#4facfe', '#00f2fe']}
style={styles.addButtonGradient}>
<TouchableOpacity onPress={handleAdd}>
<Text style={styles.addButtonText}>Add Notification</Text>
</TouchableOpacity>
</LinearGradient> */}
{/* Notification List */}
<FlatList
data={notifications}
keyExtractor={(item) => item.id}
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.listContainer}
renderItem={({ item }) => (
<LinearGradient
colors={['#f5f7fa', '#c3cfe2']}
style={styles.notificationCard}>
<View style={styles.notificationContent}>
<View style={{ flex: 1 }}>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.message}>{item.message}</Text>
<Text style={styles.time}>{item.time}</Text>
</View>
<TouchableOpacity onPress={() => handleDelete(item.id)}>
<Ionicons name="trash-outline" size={22} color="#e63946" />
</TouchableOpacity>
</View>
</LinearGradient>
)}
ListEmptyComponent={
<Text style={styles.emptyText}>No notifications found.</Text>
}
/>
</AppView>
);
};
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',
},
});
+2 -2
View File
@@ -377,7 +377,7 @@ const Profile = () => {
<View style={styles.header}>
<Text style={styles.headerTitle}>User Profile</Text>
<TouchableOpacity onPress={() => confirmLogout(navigation)} style={styles.refreshButton}>
<Ionicons name="log-out-outline" size={24} color={colors.Profile} />
<Ionicons name="log-out-outline" size={24} color={colors.primary} />
</TouchableOpacity>
</View>
{/* 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,
+1 -1
View File
@@ -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`