mirror of
https://github.com/urosran/cally.git
synced 2025-07-17 02:25:10 +00:00
Added notifciations
This commit is contained in:
@ -10,6 +10,8 @@ import NavToDosIcon from "@/assets/svgs/NavToDosIcon";
|
|||||||
import NavBrainDumpIcon from "@/assets/svgs/NavBrainDumpIcon";
|
import NavBrainDumpIcon from "@/assets/svgs/NavBrainDumpIcon";
|
||||||
import NavCalendarIcon from "@/assets/svgs/NavCalendarIcon";
|
import NavCalendarIcon from "@/assets/svgs/NavCalendarIcon";
|
||||||
import NavSettingsIcon from "@/assets/svgs/NavSettingsIcon";
|
import NavSettingsIcon from "@/assets/svgs/NavSettingsIcon";
|
||||||
|
import FeedbackNavIcon from "@/assets/svgs/FeedbackNavIcon";
|
||||||
|
import {MaterialIcons} from "@expo/vector-icons";
|
||||||
import {useSetAtom} from "jotai";
|
import {useSetAtom} from "jotai";
|
||||||
import {
|
import {
|
||||||
isFamilyViewAtom,
|
isFamilyViewAtom,
|
||||||
@ -17,7 +19,7 @@ import {
|
|||||||
toDosPageIndex,
|
toDosPageIndex,
|
||||||
userSettingsView,
|
userSettingsView,
|
||||||
} from "@/components/pages/calendar/atoms";
|
} from "@/components/pages/calendar/atoms";
|
||||||
import FeedbackNavIcon from "@/assets/svgs/FeedbackNavIcon";
|
import Ionicons from "@expo/vector-icons/Ionicons";
|
||||||
|
|
||||||
export default function TabLayout() {
|
export default function TabLayout() {
|
||||||
const {mutateAsync: signOut} = useSignOut();
|
const {mutateAsync: signOut} = useSignOut();
|
||||||
@ -41,7 +43,7 @@ export default function TabLayout() {
|
|||||||
drawerContent={(props) => {
|
drawerContent={(props) => {
|
||||||
return (
|
return (
|
||||||
<DrawerContentScrollView {...props} style={{}}>
|
<DrawerContentScrollView {...props} style={{}}>
|
||||||
<View centerV marginH-30 marginT-20 marginB-20row>
|
<View centerV marginH-30 marginT-20 marginB-20 row>
|
||||||
<ImageBackground
|
<ImageBackground
|
||||||
source={require("../../assets/images/splash.png")}
|
source={require("../../assets/images/splash.png")}
|
||||||
style={{
|
style={{
|
||||||
@ -139,6 +141,19 @@ export default function TabLayout() {
|
|||||||
}}
|
}}
|
||||||
icon={<NavBrainDumpIcon/>}
|
icon={<NavBrainDumpIcon/>}
|
||||||
/>
|
/>
|
||||||
|
<DrawerButton
|
||||||
|
color="#e0ca03"
|
||||||
|
title={"Notifications"}
|
||||||
|
bgColor={"#ffdda1"}
|
||||||
|
pressFunc={() => {
|
||||||
|
props.navigation.navigate("notifications");
|
||||||
|
setPageIndex(0);
|
||||||
|
setToDosIndex(0);
|
||||||
|
setUserView(true);
|
||||||
|
setIsFamilyView(false);
|
||||||
|
}}
|
||||||
|
icon={<Ionicons name="notifications-outline" size={24} color={"#ffa200"} />}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<Button
|
<Button
|
||||||
@ -242,6 +257,13 @@ export default function TabLayout() {
|
|||||||
title: "To-Dos",
|
title: "To-Dos",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Drawer.Screen
|
||||||
|
name="notifications"
|
||||||
|
options={{
|
||||||
|
drawerLabel: "Notifications",
|
||||||
|
title: "Notifications",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Drawer.Screen
|
<Drawer.Screen
|
||||||
name="feedback"
|
name="feedback"
|
||||||
options={{drawerLabel: "Feedback", title: "Feedback"}}
|
options={{drawerLabel: "Feedback", title: "Feedback"}}
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
|
import {BrainDumpProvider} from "@/contexts/DumpContext";
|
||||||
|
import {View} from "react-native-ui-lib";
|
||||||
import BrainDumpPage from "@/components/pages/brain_dump/BrainDumpPage";
|
import BrainDumpPage from "@/components/pages/brain_dump/BrainDumpPage";
|
||||||
import { BrainDumpProvider } from "@/contexts/DumpContext";
|
|
||||||
import { ScrollView } from "react-native-gesture-handler";
|
|
||||||
import { View } from "react-native-ui-lib";
|
|
||||||
|
|
||||||
export default function Screen() {
|
export default function Screen() {
|
||||||
return (
|
return (
|
||||||
<BrainDumpProvider>
|
<BrainDumpProvider>
|
||||||
<View>
|
<View>
|
||||||
<BrainDumpPage />
|
<BrainDumpPage/>
|
||||||
</View>
|
</View>
|
||||||
</BrainDumpProvider>
|
</BrainDumpProvider>
|
||||||
);
|
);
|
||||||
|
5
app/(auth)/notifications/_layout.tsx
Normal file
5
app/(auth)/notifications/_layout.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import {Stack} from "expo-router";
|
||||||
|
|
||||||
|
export default function StackLayout () {
|
||||||
|
return <Stack screenOptions={{headerShown: false}}/>
|
||||||
|
}
|
7
app/(auth)/notifications/index.tsx
Normal file
7
app/(auth)/notifications/index.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import NotificationsPage from "@/components/pages/notifications/NotificationsPage";
|
||||||
|
|
||||||
|
export default function Screen() {
|
||||||
|
return (
|
||||||
|
<NotificationsPage/>
|
||||||
|
);
|
||||||
|
}
|
@ -15,7 +15,6 @@ import {CalendarEvent} from "@/components/pages/calendar/interfaces";
|
|||||||
import {Text} from "react-native-ui-lib";
|
import {Text} from "react-native-ui-lib";
|
||||||
import {addDays, compareAsc, isWithinInterval, subDays} from "date-fns";
|
import {addDays, compareAsc, isWithinInterval, subDays} from "date-fns";
|
||||||
import {useCalSync} from "@/hooks/useCalSync";
|
import {useCalSync} from "@/hooks/useCalSync";
|
||||||
import { useIsMutating } from "react-query";
|
|
||||||
import {useSyncEvents} from "@/hooks/useSyncOnScroll";
|
import {useSyncEvents} from "@/hooks/useSyncOnScroll";
|
||||||
|
|
||||||
interface EventCalendarProps {
|
interface EventCalendarProps {
|
||||||
|
60
components/pages/notifications/NotificationsPage.tsx
Normal file
60
components/pages/notifications/NotificationsPage.tsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import {FlatList, ScrollView, StyleSheet} from "react-native";
|
||||||
|
import React from "react";
|
||||||
|
import {Card, Text, View} from "react-native-ui-lib";
|
||||||
|
import HeaderTemplate from "@/components/shared/HeaderTemplate";
|
||||||
|
import {useGetNotifications} from "@/hooks/firebase/useGetNotifications";
|
||||||
|
import {formatDistanceToNow} from "date-fns";
|
||||||
|
|
||||||
|
const NotificationsPage = () => {
|
||||||
|
const {data: notifications} = useGetNotifications()
|
||||||
|
|
||||||
|
console.log(notifications?.[0]?.timestamp)
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View flexG height={"100%"}>
|
||||||
|
<View flexG>
|
||||||
|
<ScrollView
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
showsHorizontalScrollIndicator={false}
|
||||||
|
>
|
||||||
|
<View marginH-25>
|
||||||
|
<HeaderTemplate
|
||||||
|
message={"Welcome to your notifications!"}
|
||||||
|
isWelcome={false}
|
||||||
|
children={
|
||||||
|
<Text
|
||||||
|
style={{fontFamily: "Manrope_400Regular", fontSize: 14}}
|
||||||
|
>
|
||||||
|
See your notifications here.
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<View>
|
||||||
|
<FlatList data={notifications ?? []} renderItem={({item}) => <Card padding-20 gap-10>
|
||||||
|
<Text text70>{item.content}</Text>
|
||||||
|
<View row spread>
|
||||||
|
<Text text90>{formatDistanceToNow(new Date(item.timestamp), { addSuffix: true })}</Text>
|
||||||
|
<Text text90>{item.timestamp.toLocaleDateString()}</Text>
|
||||||
|
</View>
|
||||||
|
</Card>}/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
searchField: {
|
||||||
|
borderWidth: 0.7,
|
||||||
|
borderColor: "#9b9b9b",
|
||||||
|
borderRadius: 15,
|
||||||
|
height: 42,
|
||||||
|
paddingLeft: 10,
|
||||||
|
marginVertical: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default NotificationsPage;
|
@ -1,6 +1,6 @@
|
|||||||
const {onRequest} = require("firebase-functions/v2/https");
|
const {onRequest} = require("firebase-functions/v2/https");
|
||||||
const {getAuth} = require("firebase-admin/auth");
|
const {getAuth} = require("firebase-admin/auth");
|
||||||
const {getFirestore} = require("firebase-admin/firestore");
|
const {getFirestore, Timestamp} = require("firebase-admin/firestore");
|
||||||
const logger = require("firebase-functions/logger");
|
const logger = require("firebase-functions/logger");
|
||||||
const functions = require('firebase-functions');
|
const functions = require('firebase-functions');
|
||||||
const admin = require('firebase-admin');
|
const admin = require('firebase-admin');
|
||||||
@ -22,7 +22,7 @@ exports.sendNotificationOnEventCreation = functions.firestore
|
|||||||
.document('Events/{eventId}')
|
.document('Events/{eventId}')
|
||||||
.onCreate(async (snapshot, context) => {
|
.onCreate(async (snapshot, context) => {
|
||||||
const eventData = snapshot.data();
|
const eventData = snapshot.data();
|
||||||
const { familyId, creatorId, email } = eventData;
|
const { familyId, creatorId, email, title } = eventData;
|
||||||
|
|
||||||
if (!familyId || !creatorId) {
|
if (!familyId || !creatorId) {
|
||||||
console.error('Missing familyId or creatorId in event data');
|
console.error('Missing familyId or creatorId in event data');
|
||||||
@ -44,7 +44,7 @@ exports.sendNotificationOnEventCreation = functions.firestore
|
|||||||
|
|
||||||
notificationTimeout = setTimeout(async () => {
|
notificationTimeout = setTimeout(async () => {
|
||||||
const eventMessage = eventCount === 1
|
const eventMessage = eventCount === 1
|
||||||
? `An event "${eventData.title}" has been added. Check it out!`
|
? `An event "${title}" has been added. Check it out!`
|
||||||
: `${eventCount} new events have been added.`;
|
: `${eventCount} new events have been added.`;
|
||||||
|
|
||||||
let messages = pushTokens.map(pushToken => {
|
let messages = pushTokens.map(pushToken => {
|
||||||
@ -85,6 +85,22 @@ exports.sendNotificationOnEventCreation = functions.firestore
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save the notification in Firestore for record-keeping
|
||||||
|
const notificationData = {
|
||||||
|
creatorId,
|
||||||
|
familyId,
|
||||||
|
content: eventMessage,
|
||||||
|
eventId: context.params.eventId,
|
||||||
|
timestamp: Timestamp.now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.collection("Notifications").add(notificationData);
|
||||||
|
console.log("Notification stored in Firestore:", notificationData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error saving notification to Firestore:", error);
|
||||||
|
}
|
||||||
|
|
||||||
// Reset state variables after notifications are sent
|
// Reset state variables after notifications are sent
|
||||||
eventCount = 0;
|
eventCount = 0;
|
||||||
pushTokens = [];
|
pushTokens = [];
|
||||||
|
29
hooks/firebase/useGetNotifications.ts
Normal file
29
hooks/firebase/useGetNotifications.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import {useQuery} from "react-query";
|
||||||
|
import firestore from "@react-native-firebase/firestore";
|
||||||
|
import {useAuthContext} from "@/contexts/AuthContext";
|
||||||
|
|
||||||
|
export const useGetNotifications = () => {
|
||||||
|
const { user, profileData } = useAuthContext();
|
||||||
|
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["notifications", user?.uid],
|
||||||
|
queryFn: async () => {
|
||||||
|
const snapshot = await firestore()
|
||||||
|
.collection("Notifications")
|
||||||
|
.where("familyId", "==", profileData?.familyId)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
return snapshot.docs.map((doc) => {
|
||||||
|
const data = doc.data();
|
||||||
|
|
||||||
|
return {...data, timestamp: new Date(data.timestamp.seconds * 1000 + data.timestamp.nanoseconds / 1e6)} as {
|
||||||
|
creatorId: string,
|
||||||
|
familyId: string,
|
||||||
|
content: string,
|
||||||
|
eventId: string,
|
||||||
|
timestamp: Date,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
Reference in New Issue
Block a user