diff --git a/app/(auth)/_layout.tsx b/app/(auth)/_layout.tsx index e479569..11036e0 100644 --- a/app/(auth)/_layout.tsx +++ b/app/(auth)/_layout.tsx @@ -81,7 +81,9 @@ export default function TabLayout() { const isFetching = useIsFetching({queryKey: ['events']}) > 0; - const isLoading = isSyncing || isFetching; + const isLoading = React.useMemo(() => { + return isSyncing || isFetching; + }, [isSyncing, isFetching]); const onRefresh = React.useCallback(async () => { try { diff --git a/app/_layout.tsx b/app/_layout.tsx index d8f9032..0352a9e 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -56,6 +56,7 @@ import {Platform} from 'react-native'; import KeyboardManager from 'react-native-keyboard-manager'; import {enableScreens} from 'react-native-screens'; import {PersistQueryClientProvider} from "@/contexts/PersistQueryClientProvider"; +import auth from "@react-native-firebase/auth"; enableScreens(true) diff --git a/components/pages/calendar/DetailedCalendar.tsx b/components/pages/calendar/DetailedCalendar.tsx index b1afd86..f8509ee 100644 --- a/components/pages/calendar/DetailedCalendar.tsx +++ b/components/pages/calendar/DetailedCalendar.tsx @@ -10,23 +10,42 @@ import {useCalendarControls} from "@/components/pages/calendar/useCalendarContro import {EventCell} from "@/components/pages/calendar/EventCell"; import {isToday} from "date-fns"; import {View} from "react-native-ui-lib"; +import {useAtomCallback} from 'jotai/utils' interface EventCalendarProps { calendarHeight: number; calendarWidth: number; + mode: "week" | "month" | "day" | "3days"; + onLoad?: () => void; } const MemoizedEventCell = React.memo(EventCell); -export const DetailedCalendar: React.FC = React.memo(({calendarHeight, calendarWidth}) => { +export const DetailedCalendar: React.FC = React.memo(( + { + calendarHeight, + calendarWidth, + mode, + onLoad + }) => { const {profileData} = useAuthContext(); const selectedDate = useAtomValue(selectedDateAtom); - const mode = useAtomValue(modeAtom); const {data: familyMembers} = useGetFamilyMembers(); const calendarRef = useRef(null); const {data: events} = useGetEvents(); const selectedUser = useAtomValue(selectedUserAtom); + const checkModeAndGoToDate = useAtomCallback(useCallback((get) => { + const currentMode = get(modeAtom); + if ((selectedDate && isToday(selectedDate)) || currentMode === "month") { + calendarRef?.current?.goToDate({date: selectedDate}); + } + }, [selectedDate])); + + useEffect(() => { + checkModeAndGoToDate(); + }, [selectedDate, checkModeAndGoToDate]); + const {data: formattedEvents} = useFormattedEvents(events ?? [], selectedDate, selectedUser); const { handlePressEvent, @@ -44,7 +63,7 @@ export const DetailedCalendar: React.FC = React.memo(({calen const headerProps = useMemo(() => ({ dayBarHeight: 60, - headerBottomHeight: 20 + headerBottomHeight: 20, }), []); const bodyProps = useMemo(() => ({ @@ -66,7 +85,6 @@ export const DetailedCalendar: React.FC = React.memo(({calen const renderEvent = useCallback((event: any) => { const attendees = getAttendees(event); - return ( = React.memo(({calen ); }, [familyMembers, handlePressEvent, getAttendees]); - useEffect(() => { - if (selectedDate && isToday(selectedDate)) { - calendarRef?.current?.goToDate({date: selectedDate}); - } - }, [selectedDate]); - return ( = React.memo(({calen events={formattedEvents ?? []} onPressEvent={handlePressEvent} onPressBackground={handlePressCell} - + onLoad={onLoad} > = React.memo((props) => { - const {isLoading} = useGetEvents(); - const [mode] = useAtom(modeAtom); - const {isSyncing} = useSyncEvents(); + const { isLoading } = useGetEvents(); + const [mode] = useAtom(modeAtom); + const { isSyncing } = useSyncEvents(); + const isTablet = Device.deviceType === Device.DeviceType.TABLET; + const isCalendarReady = useSharedValue(false); useCalSync(); - if (isLoading || isSyncing) { - return ( - - {isSyncing && Syncing...} - - - ); - } + const handleRenderComplete = React.useCallback(() => { + isCalendarReady.value = true; + }, []); - return mode === "month" - ? - : ; -}); - -const styles = StyleSheet.create({ - loadingContainer: { + const containerStyle = useAnimatedStyle(() => ({ + opacity: withTiming(isCalendarReady.value ? 1 : 0, { duration: 500 }), flex: 1, - justifyContent: 'center', - alignItems: 'center', + })); + + const monthStyle = useAnimatedStyle(() => ({ + opacity: withTiming(mode === 'month' ? 1 : 0, { duration: 300 }), position: 'absolute', width: '100%', height: '100%', + })); + + const detailedDayStyle = useAnimatedStyle(() => ({ + opacity: withTiming(mode === 'day' ? 1 : 0, { duration: 300 }), + position: 'absolute', + width: '100%', + height: '100%', + })); + + const detailedMultiStyle = useAnimatedStyle(() => ({ + opacity: withTiming(mode === (isTablet ? 'week' : '3days') ? 1 : 0, { duration: 300 }), + position: 'absolute', + width: '100%', + height: '100%', + })); + + return ( + + {(isLoading || isSyncing) && ( + + {isSyncing && Syncing...} + + + )} + + + + + + + + + {!isLoading && ( + + )} + + + + ); +}); + +const styles = StyleSheet.create({ + root: { + flex: 1, + }, + loadingContainer: { + position: 'absolute', + width: '100%', + height: '100%', + justifyContent: 'center', + alignItems: 'center', zIndex: 100, backgroundColor: 'rgba(255, 255, 255, 0.9)', }, diff --git a/components/pages/calendar/MonthCalendar.tsx b/components/pages/calendar/MonthCalendar.tsx index e9b319a..2645934 100644 --- a/components/pages/calendar/MonthCalendar.tsx +++ b/components/pages/calendar/MonthCalendar.tsx @@ -37,330 +37,6 @@ const getTotalMinutes = () => { }; -const processEventsForSideBySide = (events: CalendarEvent[]) => { - if (!events) return []; - - const timeSlots: { [key: string]: CalendarEvent[] } = {}; - - events.forEach(event => { - const startDate = new Date(event.start); - const endDate = new Date(event.end); - - // If it's an all-day event, mark it and add it directly - if (event.allDay) { - const key = `${startDate.toISOString().split('T')[0]}-allday`; - if (!timeSlots[key]) timeSlots[key] = []; - timeSlots[key].push({ - ...event, - isAllDayEvent: true, - allDay: true, - width: 1, - xPos: 0 - }); - return; - } - - // Handle multi-day events - if (startDate.toDateString() !== endDate.toDateString()) { - // Create array of dates between start and end - const dates = []; - let currentDate = new Date(startDate); - - while (currentDate <= endDate) { - dates.push(new Date(currentDate)); - currentDate.setDate(currentDate.getDate() + 1); - } - - // Create segments for each day - dates.forEach((date, index) => { - const isFirstDay = index === 0; - const isLastDay = index === dates.length - 1; - - let segmentStart, segmentEnd; - - if (isFirstDay) { - // First day: use original start time to end of day - segmentStart = new Date(startDate); - segmentEnd = new Date(date); - segmentEnd.setHours(23, 59, 59); - } else if (isLastDay) { - // Last day: use start of day to original end time - segmentStart = new Date(date); - segmentStart.setHours(0, 0, 0); - segmentEnd = new Date(endDate); - } else { - // Middle days: full day - segmentStart = new Date(date); - segmentStart.setHours(0, 0, 0); - segmentEnd = new Date(date); - segmentEnd.setHours(23, 59, 59); - } - - const key = `${segmentStart.toISOString().split('T')[0]}-${segmentStart.getHours()}`; - if (!timeSlots[key]) timeSlots[key] = []; - - timeSlots[key].push({ - ...event, - start: segmentStart, - end: segmentEnd, - isMultiDaySegment: true, - isFirstDay, - isLastDay, - originalStart: startDate, - originalEnd: endDate, - allDay: true // Mark multi-day events as all-day events - }); - }); - } else { - // Regular event - const key = `${startDate.toISOString().split('T')[0]}-${startDate.getHours()}`; - if (!timeSlots[key]) timeSlots[key] = []; - timeSlots[key].push(event); - } - }); - - // Process all time slots - return Object.values(timeSlots).flatMap(slotEvents => { - // Sort events by start time - slotEvents.sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime()); - - // Find overlapping events (only for non-all-day events) - return slotEvents.map((event, index) => { - // If it's an all-day or multi-day event, return as is - if (event.allDay || event.isMultiDaySegment) { - return { - ...event, - width: 1, - xPos: 0 - }; - } - - // Handle regular events - const overlappingEvents = slotEvents.filter((otherEvent, otherIndex) => { - if (index === otherIndex || otherEvent.allDay || otherEvent.isMultiDaySegment) return false; - const eventStart = new Date(event.start); - const eventEnd = new Date(event.end); - const otherStart = new Date(otherEvent.start); - const otherEnd = new Date(otherEvent.end); - return (eventStart < otherEnd && eventEnd > otherStart); - }); - - const total = overlappingEvents.length + 1; - const position = index % total; - - return { - ...event, - width: 1 / total, - xPos: position / total - }; - }); - }); -}; - -const renderEvent = (event: CalendarEvent & { - width: number; - xPos: number; - isMultiDaySegment?: boolean; - isFirstDay?: boolean; - isLastDay?: boolean; - originalStart?: Date; - originalEnd?: Date; - isAllDayEvent?: boolean; - allDay?: boolean; - eventColor?: string; - attendees?: string[]; - creatorId?: string; - pfp?: string; - firstName?: string; - lastName?: string; - notes?: string; - hideHours?: boolean; -}, props: any) => { - const {data: familyMembers} = useGetFamilyMembers(); - - const attendees = useMemo(() => { - if (!familyMembers || !event.attendees) return event?.creatorId ? [event?.creatorId] : []; - return familyMembers.filter(member => event?.attendees?.includes(member?.uid!)); - }, [familyMembers, event.attendees]); - - if (event.allDay && !!event.isMultiDaySegment) { - return ( - - - {event.title} - {event.isMultiDaySegment && - ` (${format(new Date(event.start), 'MMM d')} - ${format(new Date(event.end), 'MMM d')})` - } - - - ); - } - - const originalStyle = Array.isArray(props.style) ? props.style[0] : props.style; - - const startDate = event.start instanceof Date ? event.start : new Date(event.start); - const endDate = event.end instanceof Date ? event.end : new Date(event.end); - - const hourHeight = props.hourHeight || 60; - const startHour = startDate.getHours(); - const startMinutes = startDate.getMinutes(); - const topPosition = (startHour + startMinutes / 60) * hourHeight; - - const endHour = endDate.getHours(); - const endMinutes = endDate.getMinutes(); - const duration = (endHour + endMinutes / 60) - (startHour + startMinutes / 60); - const height = duration * hourHeight; - - const formatTime = (date: Date) => { - const hours = date.getHours(); - const minutes = date.getMinutes(); - const ampm = hours >= 12 ? 'pm' : 'am'; - const formattedHours = hours % 12 || 12; - const formattedMinutes = minutes.toString().padStart(2, '0'); - return `${formattedHours}:${formattedMinutes}${ampm}`; - }; - - const timeString = event.isMultiDaySegment - ? event.isFirstDay - ? `${formatTime(startDate)} → 12:00PM` - : event.isLastDay - ? `12:00am → ${formatTime(endDate)}` - : 'All day' - : `${formatTime(startDate)} - ${formatTime(endDate)}`; - - return ( - - - - - {event.title} - - - {timeString} - - - - {/* Attendees Section */} - {attendees?.length > 0 && ( - - {attendees?.filter(x=>x?.firstName).slice(0, 5).map((attendee, index) => ( - - {attendee.pfp ? ( - - ) : ( - - - {attendee?.firstName?.at(0)} - {attendee?.lastName?.at(0)} - - - )} - - ))} - {attendees.length > 3 && ( - - - +{attendees.length - 3} - - - )} - - )} - - - ); -}; - - export const MonthCalendar: React.FC = React.memo( ({calendarHeight}) => { const {data: events, isLoading} = useGetEvents(); @@ -398,12 +74,10 @@ export const MonthCalendar: React.FC = React.memo( const handlePressCell = useCallback( (date: Date) => { - if (mode === "day" || mode === "week" || mode === "3days") { - setSelectedNewEndDate(date); - } else { - setMode("day"); - setSelectedDate(date); - } + date && setSelectedDate(date); + setTimeout(() => { + setMode("day"); + }, 100) }, [mode, setSelectedNewEndDate, setSelectedDate] ); @@ -477,13 +151,11 @@ export const MonthCalendar: React.FC = React.memo( return {}; } }, [mode]); - const {enrichedEvents, filteredEvents} = useMemo(() => { - const startTime = Date.now(); - + const {filteredEvents} = useMemo(() => { const startOffset = mode === "month" ? 40 : (mode === "week" || mode === "3days") ? 10 : 1; const endOffset = mode === "month" ? 40 : (mode === "week" || mode === "3days") ? 10 : 1; - let eventsToFilter = events; + let eventsToFilter = events ?? []; if (selectedUser && Device.deviceType === DeviceType.TABLET) { eventsToFilter = events?.filter(event => @@ -495,8 +167,8 @@ export const MonthCalendar: React.FC = React.memo( const filteredEvents = eventsToFilter?.filter( (event) => - event.start && - event.end && + event?.start instanceof Date && + event?.end instanceof Date && isWithinInterval(event.start, { start: subDays(selectedDate, startOffset), end: addDays(selectedDate, endOffset), @@ -507,83 +179,14 @@ export const MonthCalendar: React.FC = React.memo( }) ) ?? []; - const enrichedEvents = filteredEvents.reduce((acc, event) => { - const dateKey = event.start.toISOString().split("T")[0]; - acc[dateKey] = acc[dateKey] || []; - acc[dateKey].push({ - ...event, - overlapPosition: false, - overlapCount: 0, - }); - - acc[dateKey].sort((a: { start: any; }, b: { start: any; }) => compareAsc(a.start, b.start)); - - return acc; - }, {} as Record); - - const endTime = Date.now(); - // console.log("memoizedEvents computation time:", endTime - startTime, "ms"); - - return {enrichedEvents, filteredEvents}; + return {filteredEvents}; }, [events, selectedDate, mode, selectedUser]); - const renderCustomDateForMonth = (date: Date) => { - const circleStyle = useMemo( - () => ({ - width: 30, - height: 30, - justifyContent: "center", - alignItems: "center", - borderRadius: 15, - }), - [] - ); - - const defaultStyle = useMemo( - () => ({ - ...circleStyle, - }), - [circleStyle] - ); - - const currentDateStyle = useMemo( - () => ({ - ...circleStyle, - backgroundColor: "#4184f2", - }), - [circleStyle] - ); - - const renderDate = useCallback( - (date: Date) => { - const isCurrentDate = isSameDate(todaysDate, date); - const appliedStyle = isCurrentDate ? currentDateStyle : defaultStyle; - - return ( - - - - {date.getDate()} - - - - ); - }, - [todaysDate, currentDateStyle, defaultStyle] - ); - - return renderDate(date); - }; - - const processedEvents = useMemo(() => { - return processEventsForSideBySide(filteredEvents); - }, [filteredEvents]); - useEffect(() => { setOffsetMinutes(getTotalMinutes()); }, [events, mode]); - if (isLoading) { + if (isLoading || !events) { return ( {isSyncing && Syncing...} @@ -603,7 +206,7 @@ export const MonthCalendar: React.FC = React.memo( { + const startTime = 'date' in event.start ? event.start.date : event.start.dateTime; + const endTime = 'date' in event.end ? event.end.date : event.end.dateTime; + const str = `${startTime}-${endTime}-${event.title}`; + + let hash = 0; + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash = hash & hash; + } + return hash.toString(36); +}; + + // Precompute time constants const DAY_IN_MS = 24 * 60 * 60 * 1000; const PERIOD_IN_MS = 5 * DAY_IN_MS; @@ -59,75 +74,70 @@ const getValidDate = (date: any, timestamp?: EventTimestamp): Date | null => { }; // Batch process events + const processEvents = async ( events: Event[], selectedDate: Date, selectedUser: { uid: string } | null ): Promise => { - // Early return if no events if (!events.length) return []; - // Pre-calculate constants const currentRangeKey = getDateRangeKey(selectedDate.getTime()); const isTablet = Device.deviceType === DeviceType.TABLET; const userId = selectedUser?.uid; - // Process in chunks to avoid blocking the main thread + const uniqueEvents = new Map(); + const processedHashes = new Set(); + const CHUNK_SIZE = 100; - const results: FormattedEvent[] = []; for (let i = 0; i < events.length; i += CHUNK_SIZE) { const chunk = events.slice(i, i + CHUNK_SIZE); - - // Process chunk and await to give UI thread a chance to breathe await new Promise(resolve => setTimeout(resolve, 0)); for (const event of chunk) { try { - // Quick user filter if (isTablet && userId && !event.attendees?.includes(userId) && event.creatorId !== userId) { continue; } - // Validate dates first const startDate = getValidDate(event.start, event.startDate); if (!startDate) continue; const rangeKey = getDateRangeKey(startDate.getTime()); - // Skip events outside our range if (Math.abs(rangeKey - currentRangeKey) > 1) continue; const endDate = getValidDate(event.end, event.endDate); if (!endDate) continue; - if (event.allDay) { - const dateStr = format(startDate, 'yyyy-MM-dd'); - const endDateStr = format(endDate, 'yyyy-MM-dd'); + const formattedEvent = event.allDay ? { + id: event.id, + title: event.title, + start: { date: format(startDate, 'yyyy-MM-dd') }, + end: { date: format(endDate, 'yyyy-MM-dd') }, + color: event.eventColor + } : { + id: event.id, + title: event.title, + start: { + dateTime: startDate.toISOString(), + timeZone: TIME_ZONE + }, + end: { + dateTime: endDate.toISOString(), + timeZone: TIME_ZONE + }, + color: event.eventColor + }; - results.push({ - id: event.id, - title: event.title, - start: { date: dateStr }, - end: { date: endDateStr }, - color: event.eventColor - }); - } else { - results.push({ - id: event.id, - title: event.title, - start: { - dateTime: startDate.toISOString(), - timeZone: TIME_ZONE - }, - end: { - dateTime: endDate.toISOString(), - timeZone: TIME_ZONE - }, - color: event.eventColor - }); + const hash = createEventHash(formattedEvent); + if (!processedHashes.has(hash)) { + processedHashes.add(hash); + uniqueEvents.set(event.id, formattedEvent); } + } catch (error) { console.error('Error processing event:', event.id, error); continue; @@ -135,7 +145,7 @@ const processEvents = async ( } } - return results; + return Array.from(uniqueEvents.values()); }; export const useFormattedEvents = ( diff --git a/components/pages/grocery/GroceryItem.tsx b/components/pages/grocery/GroceryItem.tsx index cc5ceda..3a0abbc 100644 --- a/components/pages/grocery/GroceryItem.tsx +++ b/components/pages/grocery/GroceryItem.tsx @@ -79,7 +79,6 @@ const GroceryItem = ({ setTitle: setNewTitle, setCategory: setCategory, closeEdit: closeEdit, - handleEditSubmit: updateGroceryItem, }} onInputFocus={onInputFocus} /> diff --git a/components/shared/HeaderTemplate.tsx b/components/shared/HeaderTemplate.tsx index 6005fd4..990bed2 100644 --- a/components/shared/HeaderTemplate.tsx +++ b/components/shared/HeaderTemplate.tsx @@ -107,9 +107,6 @@ const HeaderTemplate = (props: { {isFamilyView && props.isCalendar && children.length > 0 && ( {children.slice(0, 3).map((child, index) => { - { - console.log("yeaaaah"); - } const bgColor: string = child.eventColor || colorMap.pink; return child.pfp ? ( 0 && ( {children.slice(0, 3).map((child, index) => { - { - console.log("yeaaaah"); - } const bgColor: string = child.eventColor || colorMap.pink; return child.pfp ? ( = ({ return ( { - const {user: currentUser, profileData} = useAuthContext() - const queryClients = useQueryClient() + const {user: currentUser, profileData} = useAuthContext(); + const queryClient = useQueryClient(); return useMutation({ mutationKey: ["createEvent"], mutationFn: async (eventData: Partial) => { - try { - if (eventData.id) { - const snapshot = await firestore() - .collection("Events") - .where("id", "==", eventData.id) - .get(); + const newDoc = firestore().collection('Events').doc(); + await firestore() + .collection("Events") + .add({ + ...eventData, + id: newDoc.id, + creatorId: currentUser?.uid, + familyId: profileData?.familyId + }); + }, + onMutate: async (newEvent) => { + await queryClient.cancelQueries({ + queryKey: ["events", currentUser?.uid] + }); - if (!snapshot.empty) { - const docId = snapshot.docs[0].id; - await firestore() - .collection("Events") - .doc(docId) - .set({ - ...eventData, - attendees: (eventData.attendees?.length ?? 0), - creatorId: currentUser?.uid, - familyId: profileData?.familyId - }, {merge: true}); - return; - } - } - const newDoc = firestore().collection('Events').doc(); - await firestore() - .collection("Events") - .add({...eventData, id: newDoc.id, creatorId: currentUser?.uid, familyId: profileData?.familyId}); - } catch (e) { - console.error(e); + const formattedEvent = { + ...newEvent, + start: newEvent.startDate, + end: newEvent.endDate, + id: Date.now().toString(), + creatorId: currentUser?.uid, + familyId: profileData?.familyId, + eventColor: profileData?.eventColor, + hideHours: newEvent.allDay, + }; + + ["false", "true"].forEach(viewState => { + const queryKey = ["events", currentUser?.uid, viewState === "true"]; + const previousData = queryClient.getQueryData(queryKey) as any[] || []; + queryClient.setQueryData(queryKey, [...previousData, formattedEvent]); + }); + }, + onSettled: () => { + if (profileData?.familyId) { + firestore() + .collection("Households") + .where("familyId", "==", profileData.familyId) + .get() + .then(snapshot => { + snapshot.docs.forEach(doc => { + doc.ref.update({ + lastSyncTimestamp: firestore.FieldValue.serverTimestamp() + }); + }); + }); } } - }) -} + }); +}; export const useCreateEventsFromProvider = () => { - const {user: currentUser} = useAuthContext(); + const {user: currentUser, profileData} = useAuthContext(); const queryClient = useQueryClient(); return useMutation({ mutationKey: ["createEventsFromProvider"], mutationFn: async (eventDataArray: Partial[]) => { - try { - // Create an array of promises for each event's Firestore read/write operation - const promises = eventDataArray.map(async (eventData) => { - console.log("Processing EventData: ", eventData); + const promises = eventDataArray.map(async (eventData) => { + const snapshot = await firestore() + .collection("Events") + .where("id", "==", eventData.id) + .get(); - // Check if the event already exists - const snapshot = await firestore() + if (snapshot.empty) { + return firestore() .collection("Events") - .where("id", "==", eventData.id) - .get(); + .add({...eventData, creatorId: currentUser?.uid}); + } + const docId = snapshot.docs[0].id; + return firestore() + .collection("Events") + .doc(docId) + .set({...eventData, creatorId: currentUser?.uid}, {merge: true}); + }); - if (snapshot.empty) { - // Event doesn't exist, so add it - return firestore() - .collection("Events") - .add({...eventData, creatorId: currentUser?.uid}); - } else { - // Event exists, update it - const docId = snapshot.docs[0].id; - return firestore() - .collection("Events") - .doc(docId) - .set({...eventData, creatorId: currentUser?.uid}, {merge: true}); - } - }); - - // Execute all promises in parallel - await Promise.all(promises); - - } catch (e) { - console.error("Error creating/updating events: ", e); - } + await Promise.all(promises); }, - onSuccess: () => { - queryClient.invalidateQueries({queryKey: ["events"]}); + onMutate: async (newEvents) => { + await queryClient.cancelQueries({queryKey: ["events", currentUser?.uid]}); + + const previousPersonalEvents = queryClient.getQueryData(["events", currentUser?.uid, false]); + const previousFamilyEvents = queryClient.getQueryData(["events", currentUser?.uid, true]); + + const formattedEvents = newEvents.map(event => ({ + ...event, + start: new Date(event.startDate.seconds * 1000), + end: new Date(event.endDate.seconds * 1000), + hideHours: event.allDay, + eventColor: profileData?.eventColor, + creatorId: currentUser?.uid, + familyId: profileData?.familyId + })); + + const updateQueryData = (old: any[] = []) => [...old, ...formattedEvents]; + + queryClient.setQueryData(["events", currentUser?.uid, false], updateQueryData); + queryClient.setQueryData(["events", currentUser?.uid, true], updateQueryData); + + return {previousPersonalEvents, previousFamilyEvents}; + }, + onError: (err, newEvents, context) => { + queryClient.setQueryData(["events", currentUser?.uid, false], context?.previousPersonalEvents); + queryClient.setQueryData(["events", currentUser?.uid, true], context?.previousFamilyEvents); + }, + onSettled: () => { + if (profileData?.familyId) { + firestore() + .collection("Households") + .where("familyId", "==", profileData.familyId) + .get() + .then(snapshot => { + snapshot.docs.forEach(doc => { + doc.ref.update({ + lastSyncTimestamp: firestore.FieldValue.serverTimestamp() + }); + }); + }); + } } - }); -}; \ No newline at end of file + }) +} \ No newline at end of file diff --git a/hooks/firebase/useCreateGrocery.ts b/hooks/firebase/useCreateGrocery.ts index 624e36b..21b9827 100644 --- a/hooks/firebase/useCreateGrocery.ts +++ b/hooks/firebase/useCreateGrocery.ts @@ -6,10 +6,11 @@ import { IGrocery } from "@/hooks/firebase/types/groceryData"; export const useCreateGrocery = () => { const { user: currentUser, profileData } = useAuthContext(); const queryClient = useQueryClient(); - const groceriesKey = ["groceries"]; + const groceriesKey = ["groceries", currentUser?.uid]; return useMutation({ mutationFn: (groceryData: Partial) => { + console.log("Call") const newDoc = firestore().collection('Groceries').doc(); return firestore() .collection("Groceries") @@ -20,10 +21,9 @@ export const useCreateGrocery = () => { creatorId: currentUser?.uid }); }, - onSuccess: () => { + onSettled: () => { return queryClient.invalidateQueries({ queryKey: groceriesKey, - exact: true }); } }); diff --git a/hooks/firebase/useGetCaregivers.ts b/hooks/firebase/useGetCaregivers.ts deleted file mode 100644 index 7046fd2..0000000 --- a/hooks/firebase/useGetCaregivers.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useMutation } from "@tanstack/react-query"; -import firestore from "@react-native-firebase/firestore"; - -export const useSignUp = () => { - return useMutation({ - mutationKey: ["getCaregivers"], - mutationFn: async () => { - const snapshot = await firestore() - .collection("Profiles") - .where("userType", "==", "caregiver") - .get(); - }, - }); -}; diff --git a/hooks/firebase/useGetChildrenByParentId.ts b/hooks/firebase/useGetChildrenByParentId.ts deleted file mode 100644 index f797c69..0000000 --- a/hooks/firebase/useGetChildrenByParentId.ts +++ /dev/null @@ -1,34 +0,0 @@ -import {useQuery} from "@tanstack/react-query"; -import {ChildProfile} from "@/hooks/firebase/types/profileTypes"; -import firestore from "@react-native-firebase/firestore"; -import {useAuthContext} from "@/contexts/AuthContext"; - -export const useGetChildrenByParentId = () => { - const {user} = useAuthContext() - - return useQuery({ - queryKey: ["getChildrenByParentId", user?.uid], - queryFn: async (): Promise => { - try { - const snapshot = await firestore() - .collection("Profiles") - .where("userType", "==", "child") - .where("parentId", "==", user?.uid!) - .get(); - - return snapshot.docs.map((doc) => { - const data = doc.data(); - return { - ...data, - birthday: data.birthday.toDate(), - } as ChildProfile; - }); - } catch (error) { - console.error("Error retrieving child users:", error); - return []; - } - }, - enabled: !!user?.uid - } - ) -} \ No newline at end of file diff --git a/hooks/firebase/useGetEvents.ts b/hooks/firebase/useGetEvents.ts index b534a9c..b2dea8d 100644 --- a/hooks/firebase/useGetEvents.ts +++ b/hooks/firebase/useGetEvents.ts @@ -127,6 +127,9 @@ export const useGetEvents = () => { queryClient.invalidateQueries({ queryKey: ["events", user.uid] }); + queryClient.invalidateQueries({ + queryKey: ["notifications"] + }); } } } diff --git a/hooks/firebase/useGetGroceries.ts b/hooks/firebase/useGetGroceries.ts index f8f293d..265e8be 100644 --- a/hooks/firebase/useGetGroceries.ts +++ b/hooks/firebase/useGetGroceries.ts @@ -27,6 +27,9 @@ export const useGetGroceries = () => { creatorId: data.creatorId }; }); - } + }, + staleTime: Infinity, + gcTime: Infinity, + placeholderData: (previousData) => previousData, }) }; \ No newline at end of file diff --git a/hooks/firebase/useGetNotes.ts b/hooks/firebase/useGetNotes.ts index 580e3c0..bf4d626 100644 --- a/hooks/firebase/useGetNotes.ts +++ b/hooks/firebase/useGetNotes.ts @@ -28,5 +28,8 @@ export const useGetNotes = () => { } }, enabled: !!currentUser?.uid, + staleTime: Infinity, + gcTime: Infinity, + placeholderData: (previousData) => previousData, }); }; diff --git a/hooks/firebase/useGetNotifications.ts b/hooks/firebase/useGetNotifications.ts index 6294f48..585b76d 100644 --- a/hooks/firebase/useGetNotifications.ts +++ b/hooks/firebase/useGetNotifications.ts @@ -29,6 +29,8 @@ export interface Notification { export const useGetNotifications = () => { const { user, profileData } = useAuthContext(); + console.log(profileData?.familyId) + return useQuery({ queryKey: ["notifications", user?.uid], queryFn: async () => { @@ -44,11 +46,13 @@ export const useGetNotifications = () => { id: doc.id, ...data, timestamp: new Date(data.timestamp.seconds * 1000 + data.timestamp.nanoseconds / 1e6), - date: data.date ? new Date(data.date.seconds * 1000 + data.date.nanoseconds / 1e6) : undefined + date: data.date ? new Date(data.date.seconds * 1000 + data.date.nanoseconds / 1e6) : undefined, }; }); }, refetchOnWindowFocus: true, - staleTime: 60000, + staleTime: Infinity, + gcTime: Infinity, + placeholderData: (previousData) => previousData, }); }; \ No newline at end of file diff --git a/hooks/firebase/useGetTodos.ts b/hooks/firebase/useGetTodos.ts index 6fde87b..f2f1ad5 100644 --- a/hooks/firebase/useGetTodos.ts +++ b/hooks/firebase/useGetTodos.ts @@ -31,6 +31,9 @@ export const useGetTodos = () => { repeatDays: data.repeatDays ?? [] }; }) as IToDo[]; - } + }, + staleTime: Infinity, + gcTime: Infinity, + placeholderData: (previousData) => previousData, }) }; \ No newline at end of file diff --git a/hooks/firebase/useUpdateEvent.ts b/hooks/firebase/useUpdateEvent.ts deleted file mode 100644 index 5935469..0000000 --- a/hooks/firebase/useUpdateEvent.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {useMutation, useQueryClient} from "@tanstack/react-query"; -import firestore from "@react-native-firebase/firestore"; -import {EventData} from "@/hooks/firebase/types/eventData"; - -export const useUpdateEvent = () => { - const queryClients = useQueryClient() - - return useMutation({ - mutationKey: ["updateEvent"], - mutationFn: async (eventData: Partial) => { - try { - await firestore() - .collection("Events") - .doc(`${eventData.id}`) - .update(eventData); - } catch (e) { - console.error(e) - } - }, - onSuccess: () => { - queryClients.invalidateQueries({queryKey: ["events"]}) - } - }) -} \ No newline at end of file diff --git a/hooks/firebase/useUpdateTodo.ts b/hooks/firebase/useUpdateTodo.ts index 01356bf..15330aa 100644 --- a/hooks/firebase/useUpdateTodo.ts +++ b/hooks/firebase/useUpdateTodo.ts @@ -47,7 +47,7 @@ export const useUpdateTodo = () => { date: data.date ? new Date(data.date.seconds * 1000) : null, ref: doc.ref }; - }) as IToDo[]; + }) as unknown as IToDo[]; let filteredTodos = connectedTodos?.filter((item) => compareAsc(format(item.date, 'yyyy-MM-dd'), format(todoData.date, 'yyyy-MM-dd')) === 1 || compareAsc(format(item.date, 'yyyy-MM-dd'), format(todoData.date, 'yyyy-MM-dd')) === 0).sort((a,b) =>{ diff --git a/ios/Cally/AppDelegate.mm b/ios/Cally/AppDelegate.mm index 67adadf..79e7d62 100644 --- a/ios/Cally/AppDelegate.mm +++ b/ios/Cally/AppDelegate.mm @@ -11,6 +11,13 @@ // @generated begin @react-native-firebase/app-didFinishLaunchingWithOptions - expo prebuild (DO NOT MODIFY) sync-ecd111c37e49fdd1ed6354203cd6b1e2a38cccda [FIRApp configure]; // @generated end @react-native-firebase/app-didFinishLaunchingWithOptions + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + if (![defaults boolForKey:@"notFirstRun"]) { + [defaults setBool:YES forKey:@"notFirstRun"]; + [defaults synchronize]; + [[FIRAuth auth] signOut:NULL]; + } + self.moduleName = @"main"; // You can add your custom initial props in the dictionary below. diff --git a/patches/react-native-big-calendar+4.15.1.patch b/patches/react-native-big-calendar+4.15.1.patch index 59c0912..ffd45df 100644 --- a/patches/react-native-big-calendar+4.15.1.patch +++ b/patches/react-native-big-calendar+4.15.1.patch @@ -180,7 +180,6 @@ index 848ceba..f326b8e 100644 + tmpDay_1 = add(tmpDay_1, { days: 1 }); } + -+ console.log(finalEvents_1); return finalEvents_1; } }, [events, sortedMonthView]);