mirror of
https://github.com/urosran/cally.git
synced 2025-07-17 02:25:10 +00:00
New calendar
This commit is contained in:
2
app.json
2
app.json
@ -17,7 +17,7 @@
|
|||||||
"supportsTablet": true,
|
"supportsTablet": true,
|
||||||
"bundleIdentifier": "com.cally.app",
|
"bundleIdentifier": "com.cally.app",
|
||||||
"googleServicesFile": "./ios/GoogleService-Info.plist",
|
"googleServicesFile": "./ios/GoogleService-Info.plist",
|
||||||
"buildNumber": "74",
|
"buildNumber": "100",
|
||||||
"usesAppleSignIn": true,
|
"usesAppleSignIn": true,
|
||||||
"infoPlist": {
|
"infoPlist": {
|
||||||
"ITSAppUsesNonExemptEncryption": false
|
"ITSAppUsesNonExemptEncryption": false
|
||||||
|
@ -36,6 +36,7 @@ import DrawerIcon from "@/assets/svgs/DrawerIcon";
|
|||||||
import { RouteProp } from "@react-navigation/core";
|
import { RouteProp } from "@react-navigation/core";
|
||||||
import RefreshButton from "@/components/shared/RefreshButton";
|
import RefreshButton from "@/components/shared/RefreshButton";
|
||||||
import { useCalSync } from "@/hooks/useCalSync";
|
import { useCalSync } from "@/hooks/useCalSync";
|
||||||
|
import {useIsFetching} from "@tanstack/react-query";
|
||||||
|
|
||||||
type DrawerParamList = {
|
type DrawerParamList = {
|
||||||
index: undefined;
|
index: undefined;
|
||||||
@ -77,6 +78,11 @@ export default function TabLayout() {
|
|||||||
const setToDosIndex = useSetAtom(toDosPageIndex);
|
const setToDosIndex = useSetAtom(toDosPageIndex);
|
||||||
const { resyncAllCalendars, isSyncing } = useCalSync();
|
const { resyncAllCalendars, isSyncing } = useCalSync();
|
||||||
|
|
||||||
|
const isFormatting = useIsFetching({queryKey: ['formattedEvents']}) > 0;
|
||||||
|
const isFetching = useIsFetching({queryKey: ['events']}) > 0;
|
||||||
|
|
||||||
|
const isLoading = isSyncing || isFormatting || isFetching;
|
||||||
|
|
||||||
const onRefresh = React.useCallback(async () => {
|
const onRefresh = React.useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
await resyncAllCalendars();
|
await resyncAllCalendars();
|
||||||
@ -116,7 +122,7 @@ export default function TabLayout() {
|
|||||||
if (Device.deviceType !== DeviceType.TABLET || !showViewSwitch) {
|
if (Device.deviceType !== DeviceType.TABLET || !showViewSwitch) {
|
||||||
return isCalendarPage ? (
|
return isCalendarPage ? (
|
||||||
<View marginR-16>
|
<View marginR-16>
|
||||||
<RefreshButton onRefresh={onRefresh} isSyncing={isSyncing} />
|
<RefreshButton onRefresh={onRefresh} isSyncing={isLoading} />
|
||||||
</View>
|
</View>
|
||||||
) : null;
|
) : null;
|
||||||
}
|
}
|
||||||
@ -124,7 +130,7 @@ export default function TabLayout() {
|
|||||||
return (
|
return (
|
||||||
<View marginR-16 row centerV>
|
<View marginR-16 row centerV>
|
||||||
{isCalendarPage && (
|
{isCalendarPage && (
|
||||||
<View marginR-16><RefreshButton onRefresh={onRefresh} isSyncing={isSyncing} /></View>
|
<View marginR-16><RefreshButton onRefresh={onRefresh} isSyncing={isLoading} /></View>
|
||||||
)}
|
)}
|
||||||
<MemoizedViewSwitch navigation={navigation} />
|
<MemoizedViewSwitch navigation={navigation} />
|
||||||
</View>
|
</View>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, {useEffect} from "react";
|
||||||
import {RefreshControl, ScrollView, View} from "react-native";
|
import {RefreshControl, ScrollView, View} from "react-native";
|
||||||
import CalendarPage from "@/components/pages/calendar/CalendarPage";
|
import CalendarPage from "@/components/pages/calendar/CalendarPage";
|
||||||
import TabletCalendarPage from "@/components/pages/(tablet_pages)/calendar/TabletCalendarPage";
|
import TabletCalendarPage from "@/components/pages/(tablet_pages)/calendar/TabletCalendarPage";
|
||||||
@ -6,9 +6,9 @@ import * as Device from "expo-device";
|
|||||||
import {DeviceType} from "expo-device";
|
import {DeviceType} from "expo-device";
|
||||||
import {useCalSync} from "@/hooks/useCalSync";
|
import {useCalSync} from "@/hooks/useCalSync";
|
||||||
import {colorMap} from "@/constants/colorMap";
|
import {colorMap} from "@/constants/colorMap";
|
||||||
import { useSetAtom } from "jotai";
|
import {useSetAtom} from "jotai";
|
||||||
import { selectedUserAtom } from "@/components/pages/calendar/atoms";
|
import {selectedUserAtom} from "@/components/pages/calendar/atoms";
|
||||||
import { useAuthContext } from "@/contexts/AuthContext";
|
import {useAuthContext} from "@/contexts/AuthContext";
|
||||||
|
|
||||||
export default function Screen() {
|
export default function Screen() {
|
||||||
const isTablet = Device.deviceType === DeviceType.TABLET;
|
const isTablet = Device.deviceType === DeviceType.TABLET;
|
||||||
@ -17,7 +17,12 @@ export default function Screen() {
|
|||||||
const {profileData} = useAuthContext()
|
const {profileData} = useAuthContext()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(!isTablet && profileData) setSelectedUser({firstName: profileData.firstName, lastName: profileData.lastName, eventColor: profileData.eventColor})
|
if (!isTablet && profileData) setSelectedUser({
|
||||||
|
uid: profileData.uid!,
|
||||||
|
firstName: profileData.firstName,
|
||||||
|
lastName: profileData.lastName,
|
||||||
|
eventColor: profileData?.eventColor!
|
||||||
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
@ -80,15 +85,8 @@ export default function Screen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView
|
<View style={{flex: 1}}>
|
||||||
style={{flex: 1, height: "100%"}}
|
<CalendarPage/>
|
||||||
contentContainerStyle={{flex: 1, height: "100%"}}
|
</View>
|
||||||
bounces={true}
|
|
||||||
showsVerticalScrollIndicator={false}
|
|
||||||
>
|
|
||||||
<View style={{flex: 1}}>
|
|
||||||
<CalendarPage/>
|
|
||||||
</View>
|
|
||||||
</ScrollView>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -50,19 +50,17 @@ import {Stack} from "expo-router";
|
|||||||
import * as SplashScreen from "expo-splash-screen";
|
import * as SplashScreen from "expo-splash-screen";
|
||||||
import "react-native-reanimated";
|
import "react-native-reanimated";
|
||||||
import {AuthContextProvider} from "@/contexts/AuthContext";
|
import {AuthContextProvider} from "@/contexts/AuthContext";
|
||||||
import {QueryClient, QueryClientProvider} from "react-query";
|
import {QueryClient, QueryClientProvider} from "@tanstack/react-query";
|
||||||
import {TextProps, ThemeManager, Toast, Typography,} from "react-native-ui-lib";
|
import {TextProps, ThemeManager, Toast, Typography,} from "react-native-ui-lib";
|
||||||
import {Platform} from 'react-native';
|
import {Platform} from 'react-native';
|
||||||
import KeyboardManager from 'react-native-keyboard-manager';
|
import KeyboardManager from 'react-native-keyboard-manager';
|
||||||
import {enableScreens} from 'react-native-screens';
|
import {enableScreens} from 'react-native-screens';
|
||||||
|
import {PersistQueryClientProvider} from "@/contexts/PersistQueryClientProvider";
|
||||||
|
|
||||||
enableScreens(true)
|
enableScreens(true)
|
||||||
|
|
||||||
SplashScreen.preventAutoHideAsync();
|
SplashScreen.preventAutoHideAsync();
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
|
||||||
|
|
||||||
|
|
||||||
if (Platform.OS === 'ios') {
|
if (Platform.OS === 'ios') {
|
||||||
KeyboardManager.setEnable(true);
|
KeyboardManager.setEnable(true);
|
||||||
KeyboardManager.setToolbarPreviousNextButtonEnable(true);
|
KeyboardManager.setToolbarPreviousNextButtonEnable(true);
|
||||||
@ -262,7 +260,7 @@ export default function RootLayout() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<PersistQueryClientProvider>
|
||||||
<AuthContextProvider>
|
<AuthContextProvider>
|
||||||
<ThemeProvider value={DefaultTheme}>
|
<ThemeProvider value={DefaultTheme}>
|
||||||
<Stack>
|
<Stack>
|
||||||
@ -273,6 +271,6 @@ export default function RootLayout() {
|
|||||||
<Toast/>
|
<Toast/>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</AuthContextProvider>
|
</AuthContextProvider>
|
||||||
</QueryClientProvider>
|
</PersistQueryClientProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
module.exports = function (api) {
|
module.exports = function (api) {
|
||||||
api.cache(true);
|
api.cache(true);
|
||||||
|
let plugins = []
|
||||||
|
|
||||||
|
if (babelEnv !== 'development') {
|
||||||
|
plugins.push('transform-remove-console');
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
presets: [
|
presets: [
|
||||||
'babel-preset-expo',
|
'babel-preset-expo',
|
||||||
]
|
],
|
||||||
|
plugins
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
102
components/pages/calendar/DetailedCalendar.tsx
Normal file
102
components/pages/calendar/DetailedCalendar.tsx
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import React, {useCallback, useEffect, useMemo, useRef} from "react";
|
||||||
|
import {useAuthContext} from "@/contexts/AuthContext";
|
||||||
|
import {useAtomValue} from "jotai";
|
||||||
|
import {modeAtom, selectedDateAtom, selectedUserAtom} from "@/components/pages/calendar/atoms";
|
||||||
|
import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers";
|
||||||
|
import {CalendarBody, CalendarContainer, CalendarHeader, CalendarKitHandle} from "@howljs/calendar-kit";
|
||||||
|
import {useGetEvents} from "@/hooks/firebase/useGetEvents";
|
||||||
|
import {useFormattedEvents} from "@/components/pages/calendar/useFormattedEvents";
|
||||||
|
import {useCalendarControls} from "@/components/pages/calendar/useCalendarControls";
|
||||||
|
import {EventCell} from "@/components/pages/calendar/EventCell";
|
||||||
|
import {isToday} from "date-fns";
|
||||||
|
|
||||||
|
interface EventCalendarProps {
|
||||||
|
calendarHeight: number;
|
||||||
|
calendarWidth: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DetailedCalendar: React.FC<EventCalendarProps> = ({calendarHeight, calendarWidth}) => {
|
||||||
|
const {profileData} = useAuthContext();
|
||||||
|
const selectedDate = useAtomValue(selectedDateAtom);
|
||||||
|
const mode = useAtomValue(modeAtom);
|
||||||
|
const {data: familyMembers} = useGetFamilyMembers();
|
||||||
|
const calendarRef = useRef<CalendarKitHandle>(null);
|
||||||
|
const {data: events} = useGetEvents();
|
||||||
|
const selectedUser = useAtomValue(selectedUserAtom);
|
||||||
|
|
||||||
|
const {data: formattedEvents} = useFormattedEvents(events ?? [], selectedDate, selectedUser);
|
||||||
|
const {
|
||||||
|
handlePressEvent,
|
||||||
|
handlePressCell,
|
||||||
|
debouncedOnDateChanged
|
||||||
|
} = useCalendarControls(events ?? []);
|
||||||
|
|
||||||
|
const numberOfDays = useMemo(() => {
|
||||||
|
return mode === 'week' ? 7 : mode === '3days' ? 3 : 1;
|
||||||
|
}, [mode]);
|
||||||
|
|
||||||
|
const firstDay = useMemo(() => {
|
||||||
|
return profileData?.firstDayOfWeek === "Mondays" ? 1 : 0;
|
||||||
|
}, [profileData?.firstDayOfWeek]);
|
||||||
|
|
||||||
|
const headerProps = useMemo(() => ({
|
||||||
|
dayBarHeight: 60,
|
||||||
|
headerBottomHeight: 20
|
||||||
|
}), []);
|
||||||
|
|
||||||
|
const bodyProps = useMemo(() => ({
|
||||||
|
showNowIndicator: true,
|
||||||
|
hourFormat: "h:mm a"
|
||||||
|
}), []);
|
||||||
|
|
||||||
|
const containerProps = useMemo(() => ({
|
||||||
|
hourWidth: 60,
|
||||||
|
allowPinchToZoom: true,
|
||||||
|
useHaptic: true,
|
||||||
|
scrollToNow: true,
|
||||||
|
initialDate: selectedDate.toISOString(),
|
||||||
|
}), [selectedDate]);
|
||||||
|
|
||||||
|
const renderEvent = useCallback((event: any) => {
|
||||||
|
const attendees = useMemo(() =>
|
||||||
|
familyMembers?.filter(member => event?.attendees?.includes(member?.uid!)) || [],
|
||||||
|
[familyMembers, event.attendees]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EventCell
|
||||||
|
event={event}
|
||||||
|
onPress={handlePressEvent}
|
||||||
|
attendees={attendees}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}, [familyMembers, handlePressEvent]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedDate && isToday(selectedDate)) {
|
||||||
|
calendarRef?.current?.goToDate({date: selectedDate});
|
||||||
|
}
|
||||||
|
}, [selectedDate]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CalendarContainer
|
||||||
|
ref={calendarRef}
|
||||||
|
{...containerProps}
|
||||||
|
numberOfDays={numberOfDays}
|
||||||
|
calendarWidth={calendarWidth}
|
||||||
|
onDateChanged={debouncedOnDateChanged}
|
||||||
|
firstDay={firstDay}
|
||||||
|
events={formattedEvents ?? []}
|
||||||
|
onPressEvent={handlePressEvent}
|
||||||
|
onPressBackground={handlePressCell}
|
||||||
|
>
|
||||||
|
<CalendarHeader {...headerProps} />
|
||||||
|
<CalendarBody
|
||||||
|
{...bodyProps}
|
||||||
|
renderEvent={renderEvent}
|
||||||
|
/>
|
||||||
|
</CalendarContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DetailedCalendar;
|
@ -1,734 +1,52 @@
|
|||||||
import React, {useCallback, useEffect, useMemo, useState} from "react";
|
import React from 'react';
|
||||||
import {Calendar} from "react-native-big-calendar";
|
import {StyleSheet, View, ActivityIndicator} from 'react-native';
|
||||||
import {ActivityIndicator, StyleSheet, TouchableOpacity, View, ViewStyle} from "react-native";
|
import {Text} from 'react-native-ui-lib';
|
||||||
import {useGetEvents} from "@/hooks/firebase/useGetEvents";
|
import {useGetEvents} from '@/hooks/firebase/useGetEvents';
|
||||||
import {useAtom, useSetAtom} from "jotai";
|
import {useCalSync} from '@/hooks/useCalSync';
|
||||||
|
import {useSyncEvents} from '@/hooks/useSyncOnScroll';
|
||||||
|
import {useAtom} from 'jotai';
|
||||||
import {
|
import {
|
||||||
editVisibleAtom,
|
|
||||||
eventForEditAtom,
|
|
||||||
isAllDayAtom,
|
|
||||||
isFamilyViewAtom,
|
|
||||||
modeAtom,
|
modeAtom,
|
||||||
selectedDateAtom,
|
} from './atoms';
|
||||||
selectedNewEventDateAtom,
|
import {MonthCalendar} from "@/components/pages/calendar/MonthCalendar";
|
||||||
selectedUserAtom,
|
import {DetailedCalendar} from "@/components/pages/calendar/DetailedCalendar";
|
||||||
} from "@/components/pages/calendar/atoms";
|
|
||||||
import {useAuthContext} from "@/contexts/AuthContext";
|
|
||||||
import {CalendarEvent} from "@/components/pages/calendar/interfaces";
|
|
||||||
import {Text} from "react-native-ui-lib";
|
|
||||||
import {addDays, compareAsc, format, isWithinInterval, subDays} from "date-fns";
|
|
||||||
import {useCalSync} from "@/hooks/useCalSync";
|
|
||||||
import {useSyncEvents} from "@/hooks/useSyncOnScroll";
|
|
||||||
import {colorMap} from "@/constants/colorMap";
|
|
||||||
import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers";
|
|
||||||
import CachedImage from "expo-cached-image";
|
|
||||||
import { DeviceType } from "expo-device";
|
|
||||||
import * as Device from "expo-device"
|
|
||||||
|
|
||||||
interface EventCalendarProps {
|
interface EventCalendarProps {
|
||||||
calendarHeight: number;
|
calendarHeight: number;
|
||||||
// WAS USED FOR SCROLLABLE CALENDARS, PERFORMANCE WAS NOT OPTIMAL
|
|
||||||
calendarWidth: number;
|
calendarWidth: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTotalMinutes = () => {
|
export const EventCalendar: React.FC<EventCalendarProps> = React.memo((props) => {
|
||||||
const date = new Date();
|
const {data: events, isLoading} = useGetEvents();
|
||||||
return Math.abs(date.getUTCHours() * 60 + date.getUTCMinutes() - 200);
|
const [mode] = useAtom(modeAtom);
|
||||||
};
|
const {isSyncing} = useSyncEvents();
|
||||||
|
useCalSync();
|
||||||
|
|
||||||
|
if (isLoading || isSyncing) {
|
||||||
const processEventsForSideBySide = (events: CalendarEvent[]) => {
|
|
||||||
if (!events) return [];
|
|
||||||
|
|
||||||
// Group events by day and time slot
|
|
||||||
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,
|
|
||||||
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 (
|
return (
|
||||||
<TouchableOpacity
|
<View style={styles.loadingContainer}>
|
||||||
{...props}
|
{isSyncing && <Text>Syncing...</Text>}
|
||||||
style={[
|
<ActivityIndicator size="large" color="#0000ff"/>
|
||||||
props.style,
|
|
||||||
{
|
|
||||||
width: '100%',
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center'
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text style={styles.allDayEventText} numberOfLines={1}>
|
|
||||||
{event.title}
|
|
||||||
{event.isMultiDaySegment &&
|
|
||||||
` (${format(new Date(event.start), 'MMM d')} - ${format(new Date(event.end), 'MMM d')})`
|
|
||||||
}
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const originalStyle = Array.isArray(props.style) ? props.style[0] : props.style;
|
|
||||||
|
|
||||||
console.log('Rendering event:', {
|
|
||||||
title: event.title,
|
|
||||||
start: event.start,
|
|
||||||
end: event.end,
|
|
||||||
width: event.width,
|
|
||||||
xPos: event.xPos,
|
|
||||||
isMultiDaySegment: event.isMultiDaySegment
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// Ensure we have Date objects
|
|
||||||
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 (
|
|
||||||
<TouchableOpacity
|
|
||||||
{...props}
|
|
||||||
style={[
|
|
||||||
originalStyle,
|
|
||||||
{
|
|
||||||
position: 'absolute',
|
|
||||||
width: `${event.width * 100}%`,
|
|
||||||
left: `${event.xPos * 100}%`,
|
|
||||||
top: topPosition,
|
|
||||||
height: height,
|
|
||||||
zIndex: event.isMultiDaySegment ? 1 : 2,
|
|
||||||
shadowRadius: 2,
|
|
||||||
overflow: "hidden"
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: event.eventColor,
|
|
||||||
borderRadius: 4,
|
|
||||||
padding: 8,
|
|
||||||
justifyContent: 'space-between'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<View>
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
color: 'white',
|
|
||||||
fontSize: 12,
|
|
||||||
fontFamily: "PlusJakartaSans_500Medium",
|
|
||||||
fontWeight: '600',
|
|
||||||
marginBottom: 4
|
|
||||||
}}
|
|
||||||
numberOfLines={1}
|
|
||||||
>
|
|
||||||
{event.title}
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
color: 'white',
|
|
||||||
fontSize: 10,
|
|
||||||
fontFamily: "PlusJakartaSans_500Medium",
|
|
||||||
opacity: 0.8
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{timeString}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Attendees Section */}
|
|
||||||
{attendees?.length > 0 && (
|
|
||||||
<View style={{flexDirection: 'row', marginTop: 8, height: 27.32}}>
|
|
||||||
{attendees?.filter(x=>x?.firstName).slice(0, 5).map((attendee, index) => (
|
|
||||||
<View
|
|
||||||
key={attendee?.uid}
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
left: index * 19,
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
borderRadius: 50,
|
|
||||||
borderWidth: 2,
|
|
||||||
borderColor: '#f2f2f2',
|
|
||||||
overflow: 'hidden',
|
|
||||||
backgroundColor: attendee.eventColor || colorMap.pink,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{attendee.pfp ? (
|
|
||||||
<CachedImage
|
|
||||||
source={{uri: attendee.pfp}}
|
|
||||||
style={{width: '100%', height: '100%'}}
|
|
||||||
cacheKey={attendee.pfp}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<View style={{
|
|
||||||
flex: 1,
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}>
|
|
||||||
<Text style={{
|
|
||||||
color: 'white',
|
|
||||||
fontSize: 12,
|
|
||||||
fontFamily: "Manrope_600SemiBold",
|
|
||||||
}}>
|
|
||||||
{attendee?.firstName?.at(0)}
|
|
||||||
{attendee?.lastName?.at(0)}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
))}
|
|
||||||
{attendees.length > 3 && (
|
|
||||||
<View style={{
|
|
||||||
position: 'absolute',
|
|
||||||
left: 3 * 19,
|
|
||||||
width: 27.32,
|
|
||||||
height: 27.32,
|
|
||||||
borderRadius: 50,
|
|
||||||
borderWidth: 2,
|
|
||||||
borderColor: '#f2f2f2',
|
|
||||||
backgroundColor: colorMap.pink,
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}>
|
|
||||||
<Text style={{
|
|
||||||
color: 'white',
|
|
||||||
fontFamily: "Manrope_600SemiBold",
|
|
||||||
fontSize: 12,
|
|
||||||
}}>
|
|
||||||
+{attendees.length - 3}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const EventCalendar: React.FC<EventCalendarProps> = React.memo(
|
|
||||||
({calendarHeight}) => {
|
|
||||||
const {data: events, isLoading} = useGetEvents();
|
|
||||||
const {profileData, user} = useAuthContext();
|
|
||||||
const [selectedDate, setSelectedDate] = useAtom(selectedDateAtom);
|
|
||||||
const [mode, setMode] = useAtom(modeAtom);
|
|
||||||
const [isFamilyView] = useAtom(isFamilyViewAtom);
|
|
||||||
|
|
||||||
//tablet view filter
|
|
||||||
const [selectedUser] = useAtom(selectedUserAtom);
|
|
||||||
|
|
||||||
const setEditVisible = useSetAtom(editVisibleAtom);
|
|
||||||
const [isAllDay, setIsAllDay] = useAtom(isAllDayAtom);
|
|
||||||
const setEventForEdit = useSetAtom(eventForEditAtom);
|
|
||||||
const setSelectedNewEndDate = useSetAtom(selectedNewEventDateAtom);
|
|
||||||
|
|
||||||
const {isSyncing} = useSyncEvents()
|
|
||||||
const [offsetMinutes, setOffsetMinutes] = useState(getTotalMinutes());
|
|
||||||
useCalSync()
|
|
||||||
|
|
||||||
const todaysDate = new Date();
|
|
||||||
|
|
||||||
const handlePressEvent = useCallback(
|
|
||||||
(event: CalendarEvent) => {
|
|
||||||
if (mode === "day" || mode === "week" || mode === "3days") {
|
|
||||||
setEditVisible(true);
|
|
||||||
setEventForEdit(event);
|
|
||||||
} else {
|
|
||||||
setMode("day");
|
|
||||||
setSelectedDate(event.start);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[setEditVisible, setEventForEdit, mode]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handlePressCell = useCallback(
|
|
||||||
(date: Date) => {
|
|
||||||
if (mode === "day" || mode === "week" || mode === "3days") {
|
|
||||||
setSelectedNewEndDate(date);
|
|
||||||
} else {
|
|
||||||
setMode("day");
|
|
||||||
setSelectedDate(date);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[mode, setSelectedNewEndDate, setSelectedDate]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handlePressDayHeader = useCallback(
|
|
||||||
(date: Date) => {
|
|
||||||
if (mode === "day") {
|
|
||||||
setIsAllDay(true);
|
|
||||||
setSelectedNewEndDate(date);
|
|
||||||
setEditVisible(true);
|
|
||||||
}
|
|
||||||
if (mode === 'week' || mode === '3days') {
|
|
||||||
setSelectedDate(date)
|
|
||||||
setMode("day")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[mode, setSelectedNewEndDate]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSwipeEnd = useCallback(
|
|
||||||
(date: Date) => {
|
|
||||||
setSelectedDate(date);
|
|
||||||
},
|
|
||||||
[setSelectedDate]
|
|
||||||
);
|
|
||||||
|
|
||||||
const memoizedEventCellStyle = useCallback(
|
|
||||||
(event: CalendarEvent) => {
|
|
||||||
let eventColor = event.eventColor;
|
|
||||||
if (!isFamilyView && (event.attendees?.includes(user?.uid!) || event.creatorId! === user?.uid)) {
|
|
||||||
eventColor = profileData?.eventColor ?? colorMap.teal;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {backgroundColor: eventColor, fontSize: 14}
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const memoizedWeekStartsOn = useMemo(
|
|
||||||
() => (profileData?.firstDayOfWeek === "Mondays" ? 1 : 0),
|
|
||||||
[profileData]
|
|
||||||
);
|
|
||||||
|
|
||||||
const isSameDate = useCallback((date1: Date, date2: Date) => {
|
|
||||||
return (
|
|
||||||
date1.getDate() === date2.getDate() &&
|
|
||||||
date1.getMonth() === date2.getMonth() &&
|
|
||||||
date1.getFullYear() === date2.getFullYear()
|
|
||||||
);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const dayHeaderColor = useMemo(() => {
|
|
||||||
return isSameDate(todaysDate, selectedDate) ? "white" : "#4d4d4d";
|
|
||||||
}, [selectedDate, mode]);
|
|
||||||
|
|
||||||
const dateStyle = useMemo(() => {
|
|
||||||
if (mode === "week" || mode === "3days") return undefined;
|
|
||||||
return isSameDate(todaysDate, selectedDate) && mode === "day"
|
|
||||||
? styles.dayHeader
|
|
||||||
: styles.otherDayHeader;
|
|
||||||
}, [selectedDate, mode]);
|
|
||||||
|
|
||||||
const memoizedHeaderContentStyle = useMemo(() => {
|
|
||||||
if (mode === "day") {
|
|
||||||
return styles.dayModeHeader;
|
|
||||||
} else if (mode === "week" || mode === "3days") {
|
|
||||||
return styles.weekModeHeader;
|
|
||||||
} else if (mode === "month") {
|
|
||||||
return styles.monthModeHeader;
|
|
||||||
} else {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}, [mode]);
|
|
||||||
const {enrichedEvents, filteredEvents} = useMemo(() => {
|
|
||||||
const startTime = Date.now();
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
if (selectedUser && Device.deviceType === DeviceType.TABLET) {
|
|
||||||
eventsToFilter = events?.filter(event =>
|
|
||||||
event.attendees?.includes(selectedUser.uid) ||
|
|
||||||
event.creatorId === selectedUser.uid
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const filteredEvents =
|
|
||||||
eventsToFilter?.filter(
|
|
||||||
(event) =>
|
|
||||||
event.start &&
|
|
||||||
event.end &&
|
|
||||||
isWithinInterval(event.start, {
|
|
||||||
start: subDays(selectedDate, startOffset),
|
|
||||||
end: addDays(selectedDate, endOffset),
|
|
||||||
}) &&
|
|
||||||
isWithinInterval(event.end, {
|
|
||||||
start: subDays(selectedDate, startOffset),
|
|
||||||
end: addDays(selectedDate, endOffset),
|
|
||||||
})
|
|
||||||
) ?? [];
|
|
||||||
|
|
||||||
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<string, CalendarEvent[]>);
|
|
||||||
|
|
||||||
const endTime = Date.now();
|
|
||||||
// console.log("memoizedEvents computation time:", endTime - startTime, "ms");
|
|
||||||
|
|
||||||
return {enrichedEvents, filteredEvents};
|
|
||||||
}, [events, selectedDate, mode, selectedUser]);
|
|
||||||
|
|
||||||
const renderCustomDateForMonth = (date: Date) => {
|
|
||||||
const circleStyle = useMemo<ViewStyle>(
|
|
||||||
() => ({
|
|
||||||
width: 30,
|
|
||||||
height: 30,
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
borderRadius: 15,
|
|
||||||
}),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const defaultStyle = useMemo<ViewStyle>(
|
|
||||||
() => ({
|
|
||||||
...circleStyle,
|
|
||||||
}),
|
|
||||||
[circleStyle]
|
|
||||||
);
|
|
||||||
|
|
||||||
const currentDateStyle = useMemo<ViewStyle>(
|
|
||||||
() => ({
|
|
||||||
...circleStyle,
|
|
||||||
backgroundColor: "#4184f2",
|
|
||||||
}),
|
|
||||||
[circleStyle]
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderDate = useCallback(
|
|
||||||
(date: Date) => {
|
|
||||||
const isCurrentDate = isSameDate(todaysDate, date);
|
|
||||||
const appliedStyle = isCurrentDate ? currentDateStyle : defaultStyle;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={{alignItems: "center"}}>
|
|
||||||
<View style={appliedStyle}>
|
|
||||||
<Text style={{color: isCurrentDate ? "white" : "black"}}>
|
|
||||||
{date.getDate()}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[todaysDate, currentDateStyle, defaultStyle]
|
|
||||||
);
|
|
||||||
|
|
||||||
return renderDate(date);
|
|
||||||
};
|
|
||||||
|
|
||||||
const processedEvents = useMemo(() => {
|
|
||||||
return processEventsForSideBySide(filteredEvents);
|
|
||||||
}, [filteredEvents]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setOffsetMinutes(getTotalMinutes());
|
|
||||||
}, [events, mode]);
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return (
|
|
||||||
<View style={styles.loadingContainer}>
|
|
||||||
{isSyncing && <Text>Syncing...</Text>}
|
|
||||||
<ActivityIndicator size="large" color="#0000ff"/>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{isSyncing && (
|
|
||||||
<View style={styles.loadingContainer}>
|
|
||||||
{isSyncing && <Text>Syncing...</Text>}
|
|
||||||
<ActivityIndicator size="large" color="#0000ff"/>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
<Calendar
|
|
||||||
bodyContainerStyle={styles.calHeader}
|
|
||||||
swipeEnabled
|
|
||||||
mode={mode}
|
|
||||||
sortedMonthView
|
|
||||||
events={filteredEvents}
|
|
||||||
// renderEvent={renderEvent}
|
|
||||||
eventCellStyle={memoizedEventCellStyle}
|
|
||||||
allDayEventCellStyle={memoizedEventCellStyle}
|
|
||||||
// enableEnrichedEvents={true}
|
|
||||||
// enrichedEventsByDate={enrichedEvents}
|
|
||||||
onPressEvent={handlePressEvent}
|
|
||||||
weekStartsOn={memoizedWeekStartsOn}
|
|
||||||
height={calendarHeight}
|
|
||||||
activeDate={todaysDate}
|
|
||||||
date={selectedDate}
|
|
||||||
onPressCell={handlePressCell}
|
|
||||||
headerContentStyle={memoizedHeaderContentStyle}
|
|
||||||
onSwipeEnd={handleSwipeEnd}
|
|
||||||
scrollOffsetMinutes={offsetMinutes}
|
|
||||||
theme={{
|
|
||||||
palette: {
|
|
||||||
nowIndicator: profileData?.eventColor || "#fd1575",
|
|
||||||
gray: {
|
|
||||||
"100": "#e8eaed",
|
|
||||||
"200": "#e8eaed",
|
|
||||||
"500": "#b7b7b7",
|
|
||||||
"800": "#919191",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
typography: {
|
|
||||||
fontFamily: "PlusJakartaSans_500Medium",
|
|
||||||
sm: {fontFamily: "Manrope_600SemiBold", fontSize: 8},
|
|
||||||
xl: {
|
|
||||||
fontFamily: "PlusJakartaSans_500Medium",
|
|
||||||
fontSize: 14,
|
|
||||||
},
|
|
||||||
moreLabel: {},
|
|
||||||
xs: {fontSize: 10},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
dayHeaderStyle={dateStyle}
|
|
||||||
dayHeaderHighlightColor={"white"}
|
|
||||||
showAdjacentMonths
|
|
||||||
headerContainerStyle={mode !== "month" ? {
|
|
||||||
overflow: "hidden",
|
|
||||||
} : {}}
|
|
||||||
hourStyle={styles.hourStyle}
|
|
||||||
onPressDateHeader={handlePressDayHeader}
|
|
||||||
ampm
|
|
||||||
// renderCustomDateForMonth={renderCustomDateForMonth}
|
|
||||||
/>
|
|
||||||
<View style={{backgroundColor: 'white', height: 50, width: '100%'}}/>
|
|
||||||
</>
|
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
return mode === "month"
|
||||||
|
? <MonthCalendar {...props} />
|
||||||
|
: <DetailedCalendar {...props} />;
|
||||||
|
});
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
segmentslblStyle: {
|
|
||||||
fontSize: 12,
|
|
||||||
fontFamily: "Manrope_600SemiBold",
|
|
||||||
},
|
|
||||||
calHeader: {
|
|
||||||
borderWidth: 0,
|
|
||||||
paddingBottom: 60,
|
|
||||||
},
|
|
||||||
dayModeHeader: {
|
|
||||||
alignSelf: "flex-start",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignContent: "center",
|
|
||||||
width: 38,
|
|
||||||
right: 42,
|
|
||||||
height: 13,
|
|
||||||
},
|
|
||||||
weekModeHeader: {},
|
|
||||||
monthModeHeader: {},
|
|
||||||
loadingContainer: {
|
loadingContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
position: "absolute",
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
zIndex: 100,
|
|
||||||
backgroundColor: "rgba(255, 255, 255, 0.9)",
|
|
||||||
},
|
|
||||||
dayHeader: {
|
|
||||||
backgroundColor: "#4184f2",
|
|
||||||
aspectRatio: 1,
|
|
||||||
borderRadius: 100,
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
},
|
|
||||||
otherDayHeader: {
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
color: "#919191",
|
|
||||||
aspectRatio: 1,
|
|
||||||
borderRadius: 100,
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
},
|
|
||||||
hourStyle: {
|
|
||||||
color: "#5f6368",
|
|
||||||
fontSize: 12,
|
|
||||||
fontFamily: "Manrope_500Medium",
|
|
||||||
},
|
|
||||||
eventCell: {
|
|
||||||
flex: 1,
|
|
||||||
borderRadius: 4,
|
|
||||||
padding: 4,
|
|
||||||
height: '100%',
|
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
alignItems: 'center',
|
||||||
eventTitle: {
|
position: 'absolute',
|
||||||
color: 'white',
|
width: '100%',
|
||||||
fontSize: 12,
|
height: '100%',
|
||||||
fontFamily: "PlusJakartaSans_500Medium",
|
zIndex: 100,
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default EventCalendar;
|
117
components/pages/calendar/EventCell.tsx
Normal file
117
components/pages/calendar/EventCell.tsx
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {StyleSheet, TouchableOpacity, View} from 'react-native';
|
||||||
|
import {Text} from 'react-native-ui-lib';
|
||||||
|
import CachedImage from 'expo-cached-image';
|
||||||
|
import {format} from 'date-fns';
|
||||||
|
import {colorMap} from '@/constants/colorMap';
|
||||||
|
|
||||||
|
interface EventCellProps {
|
||||||
|
event: any;
|
||||||
|
onPress: (event: any) => void;
|
||||||
|
attendees: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EventCell: React.FC<EventCellProps> = React.memo((
|
||||||
|
{
|
||||||
|
event,
|
||||||
|
onPress,
|
||||||
|
attendees
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => onPress(event)}
|
||||||
|
style={[styles.eventCell, {backgroundColor: event.eventColor}]}
|
||||||
|
>
|
||||||
|
<Text style={styles.eventTitle} numberOfLines={1}>
|
||||||
|
{event.title}
|
||||||
|
</Text>
|
||||||
|
<Text style={[styles.eventTitle, {fontSize: 10, opacity: 0.8}]}>
|
||||||
|
{format(new Date(event.start.dateTime), 'h:mm a')} - {format(new Date(event.end.dateTime), 'h:mm a')}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{attendees.length > 0 && (
|
||||||
|
<View style={styles.attendeesContainer}>
|
||||||
|
{attendees.slice(0, 3).map((attendee, index) => (
|
||||||
|
<View
|
||||||
|
key={attendee?.uid}
|
||||||
|
style={[styles.attendeeIcon, {left: index * 19}]}
|
||||||
|
>
|
||||||
|
{attendee.pfp ? (
|
||||||
|
<CachedImage
|
||||||
|
source={{uri: attendee.pfp}}
|
||||||
|
style={styles.attendeeImage}
|
||||||
|
cacheKey={attendee.pfp}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Text style={styles.attendeeInitials}>
|
||||||
|
{attendee?.firstName?.at(0)}
|
||||||
|
{attendee?.lastName?.at(0)}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
{attendees.length > 3 && (
|
||||||
|
<View style={[styles.attendeeCount, {left: 3 * 19}]}>
|
||||||
|
<Text style={styles.attendeeCountText}>
|
||||||
|
+{attendees.length - 3}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
eventCell: {
|
||||||
|
flex: 1,
|
||||||
|
borderRadius: 4,
|
||||||
|
padding: 8,
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
eventTitle: {
|
||||||
|
color: 'white',
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: "PlusJakartaSans_500Medium",
|
||||||
|
},
|
||||||
|
attendeesContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
marginTop: 8,
|
||||||
|
height: 27.32,
|
||||||
|
},
|
||||||
|
attendeeIcon: {
|
||||||
|
position: 'absolute',
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
borderRadius: 50,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: '#f2f2f2',
|
||||||
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
|
attendeeImage: {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
attendeeInitials: {
|
||||||
|
color: 'white',
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: "Manrope_600SemiBold",
|
||||||
|
},
|
||||||
|
attendeeCount: {
|
||||||
|
position: 'absolute',
|
||||||
|
width: 27.32,
|
||||||
|
height: 27.32,
|
||||||
|
borderRadius: 50,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: '#f2f2f2',
|
||||||
|
backgroundColor: colorMap.pink,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
attendeeCountText: {
|
||||||
|
color: 'white',
|
||||||
|
fontFamily: "Manrope_600SemiBold",
|
||||||
|
fontSize: 12,
|
||||||
|
},
|
||||||
|
});
|
723
components/pages/calendar/MonthCalendar.tsx
Normal file
723
components/pages/calendar/MonthCalendar.tsx
Normal file
@ -0,0 +1,723 @@
|
|||||||
|
import React, {useCallback, useEffect, useMemo, useState} from "react";
|
||||||
|
import {Calendar} from "react-native-big-calendar";
|
||||||
|
import {ActivityIndicator, StyleSheet, TouchableOpacity, View, ViewStyle} from "react-native";
|
||||||
|
import {useGetEvents} from "@/hooks/firebase/useGetEvents";
|
||||||
|
import {useAtom, useSetAtom} from "jotai";
|
||||||
|
import {
|
||||||
|
editVisibleAtom,
|
||||||
|
eventForEditAtom,
|
||||||
|
isAllDayAtom,
|
||||||
|
isFamilyViewAtom,
|
||||||
|
modeAtom,
|
||||||
|
selectedDateAtom,
|
||||||
|
selectedNewEventDateAtom,
|
||||||
|
selectedUserAtom,
|
||||||
|
} from "@/components/pages/calendar/atoms";
|
||||||
|
import {useAuthContext} from "@/contexts/AuthContext";
|
||||||
|
import {CalendarEvent} from "@/components/pages/calendar/interfaces";
|
||||||
|
import {Text} from "react-native-ui-lib";
|
||||||
|
import {addDays, compareAsc, format, isWithinInterval, subDays} from "date-fns";
|
||||||
|
import {useCalSync} from "@/hooks/useCalSync";
|
||||||
|
import {useSyncEvents} from "@/hooks/useSyncOnScroll";
|
||||||
|
import {colorMap} from "@/constants/colorMap";
|
||||||
|
import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers";
|
||||||
|
import CachedImage from "expo-cached-image";
|
||||||
|
import { DeviceType } from "expo-device";
|
||||||
|
import * as Device from "expo-device"
|
||||||
|
|
||||||
|
interface EventCalendarProps {
|
||||||
|
calendarHeight: number;
|
||||||
|
// WAS USED FOR SCROLLABLE CALENDARS, PERFORMANCE WAS NOT OPTIMAL
|
||||||
|
calendarWidth: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTotalMinutes = () => {
|
||||||
|
const date = new Date();
|
||||||
|
return Math.abs(date.getUTCHours() * 60 + date.getUTCMinutes() - 200);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<TouchableOpacity
|
||||||
|
{...props}
|
||||||
|
style={[
|
||||||
|
props.style,
|
||||||
|
{
|
||||||
|
width: '100%',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center'
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text style={styles.allDayEventText} numberOfLines={1}>
|
||||||
|
{event.title}
|
||||||
|
{event.isMultiDaySegment &&
|
||||||
|
` (${format(new Date(event.start), 'MMM d')} - ${format(new Date(event.end), 'MMM d')})`
|
||||||
|
}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<TouchableOpacity
|
||||||
|
{...props}
|
||||||
|
style={[
|
||||||
|
originalStyle,
|
||||||
|
{
|
||||||
|
position: 'absolute',
|
||||||
|
width: `${event.width * 100}%`,
|
||||||
|
left: `${event.xPos * 100}%`,
|
||||||
|
top: topPosition,
|
||||||
|
height: height,
|
||||||
|
zIndex: event.isMultiDaySegment ? 1 : 2,
|
||||||
|
shadowRadius: 2,
|
||||||
|
overflow: "hidden"
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: event.eventColor,
|
||||||
|
borderRadius: 4,
|
||||||
|
padding: 8,
|
||||||
|
justifyContent: 'space-between'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
color: 'white',
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: "PlusJakartaSans_500Medium",
|
||||||
|
fontWeight: '600',
|
||||||
|
marginBottom: 4
|
||||||
|
}}
|
||||||
|
numberOfLines={1}
|
||||||
|
>
|
||||||
|
{event.title}
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
color: 'white',
|
||||||
|
fontSize: 10,
|
||||||
|
fontFamily: "PlusJakartaSans_500Medium",
|
||||||
|
opacity: 0.8
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{timeString}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Attendees Section */}
|
||||||
|
{attendees?.length > 0 && (
|
||||||
|
<View style={{flexDirection: 'row', marginTop: 8, height: 27.32}}>
|
||||||
|
{attendees?.filter(x=>x?.firstName).slice(0, 5).map((attendee, index) => (
|
||||||
|
<View
|
||||||
|
key={attendee?.uid}
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
left: index * 19,
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
borderRadius: 50,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: '#f2f2f2',
|
||||||
|
overflow: 'hidden',
|
||||||
|
backgroundColor: attendee.eventColor || colorMap.pink,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{attendee.pfp ? (
|
||||||
|
<CachedImage
|
||||||
|
source={{uri: attendee.pfp}}
|
||||||
|
style={{width: '100%', height: '100%'}}
|
||||||
|
cacheKey={attendee.pfp}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<View style={{
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}>
|
||||||
|
<Text style={{
|
||||||
|
color: 'white',
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: "Manrope_600SemiBold",
|
||||||
|
}}>
|
||||||
|
{attendee?.firstName?.at(0)}
|
||||||
|
{attendee?.lastName?.at(0)}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
{attendees.length > 3 && (
|
||||||
|
<View style={{
|
||||||
|
position: 'absolute',
|
||||||
|
left: 3 * 19,
|
||||||
|
width: 27.32,
|
||||||
|
height: 27.32,
|
||||||
|
borderRadius: 50,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: '#f2f2f2',
|
||||||
|
backgroundColor: colorMap.pink,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}>
|
||||||
|
<Text style={{
|
||||||
|
color: 'white',
|
||||||
|
fontFamily: "Manrope_600SemiBold",
|
||||||
|
fontSize: 12,
|
||||||
|
}}>
|
||||||
|
+{attendees.length - 3}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const MonthCalendar: React.FC<EventCalendarProps> = React.memo(
|
||||||
|
({calendarHeight}) => {
|
||||||
|
const {data: events, isLoading} = useGetEvents();
|
||||||
|
const {profileData, user} = useAuthContext();
|
||||||
|
const [selectedDate, setSelectedDate] = useAtom(selectedDateAtom);
|
||||||
|
const [mode, setMode] = useAtom(modeAtom);
|
||||||
|
const [isFamilyView] = useAtom(isFamilyViewAtom);
|
||||||
|
|
||||||
|
//tablet view filter
|
||||||
|
const [selectedUser] = useAtom(selectedUserAtom);
|
||||||
|
|
||||||
|
const setEditVisible = useSetAtom(editVisibleAtom);
|
||||||
|
const [isAllDay, setIsAllDay] = useAtom(isAllDayAtom);
|
||||||
|
const setEventForEdit = useSetAtom(eventForEditAtom);
|
||||||
|
const setSelectedNewEndDate = useSetAtom(selectedNewEventDateAtom);
|
||||||
|
|
||||||
|
const {isSyncing} = useSyncEvents()
|
||||||
|
const [offsetMinutes, setOffsetMinutes] = useState(getTotalMinutes());
|
||||||
|
useCalSync()
|
||||||
|
|
||||||
|
const todaysDate = new Date();
|
||||||
|
|
||||||
|
const handlePressEvent = useCallback(
|
||||||
|
(event: CalendarEvent) => {
|
||||||
|
if (mode === "day" || mode === "week" || mode === "3days") {
|
||||||
|
setEditVisible(true);
|
||||||
|
setEventForEdit(event);
|
||||||
|
} else {
|
||||||
|
setMode("day");
|
||||||
|
setSelectedDate(event.start);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setEditVisible, setEventForEdit, mode]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handlePressCell = useCallback(
|
||||||
|
(date: Date) => {
|
||||||
|
if (mode === "day" || mode === "week" || mode === "3days") {
|
||||||
|
setSelectedNewEndDate(date);
|
||||||
|
} else {
|
||||||
|
setMode("day");
|
||||||
|
setSelectedDate(date);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[mode, setSelectedNewEndDate, setSelectedDate]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handlePressDayHeader = useCallback(
|
||||||
|
(date: Date) => {
|
||||||
|
if (mode === "day") {
|
||||||
|
setIsAllDay(true);
|
||||||
|
setSelectedNewEndDate(date);
|
||||||
|
setEditVisible(true);
|
||||||
|
}
|
||||||
|
if (mode === 'week' || mode === '3days') {
|
||||||
|
setSelectedDate(date)
|
||||||
|
setMode("day")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[mode, setSelectedNewEndDate]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSwipeEnd = useCallback(
|
||||||
|
(date: Date) => {
|
||||||
|
setSelectedDate(date);
|
||||||
|
},
|
||||||
|
[setSelectedDate]
|
||||||
|
);
|
||||||
|
|
||||||
|
const memoizedEventCellStyle = useCallback(
|
||||||
|
(event: CalendarEvent) => {
|
||||||
|
let eventColor = event.eventColor;
|
||||||
|
if (!isFamilyView && (event.attendees?.includes(user?.uid!) || event.creatorId! === user?.uid)) {
|
||||||
|
eventColor = profileData?.eventColor ?? colorMap.teal;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {backgroundColor: eventColor, fontSize: 14}
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const memoizedWeekStartsOn = useMemo(
|
||||||
|
() => (profileData?.firstDayOfWeek === "Mondays" ? 1 : 0),
|
||||||
|
[profileData]
|
||||||
|
);
|
||||||
|
|
||||||
|
const isSameDate = useCallback((date1: Date, date2: Date) => {
|
||||||
|
return (
|
||||||
|
date1.getDate() === date2.getDate() &&
|
||||||
|
date1.getMonth() === date2.getMonth() &&
|
||||||
|
date1.getFullYear() === date2.getFullYear()
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const dayHeaderColor = useMemo(() => {
|
||||||
|
return isSameDate(todaysDate, selectedDate) ? "white" : "#4d4d4d";
|
||||||
|
}, [selectedDate, mode]);
|
||||||
|
|
||||||
|
const dateStyle = useMemo(() => {
|
||||||
|
if (mode === "week" || mode === "3days") return undefined;
|
||||||
|
return isSameDate(todaysDate, selectedDate) && mode === "day"
|
||||||
|
? styles.dayHeader
|
||||||
|
: styles.otherDayHeader;
|
||||||
|
}, [selectedDate, mode]);
|
||||||
|
|
||||||
|
const memoizedHeaderContentStyle = useMemo(() => {
|
||||||
|
if (mode === "day") {
|
||||||
|
return styles.dayModeHeader;
|
||||||
|
} else if (mode === "week" || mode === "3days") {
|
||||||
|
return styles.weekModeHeader;
|
||||||
|
} else if (mode === "month") {
|
||||||
|
return styles.monthModeHeader;
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}, [mode]);
|
||||||
|
const {enrichedEvents, filteredEvents} = useMemo(() => {
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (selectedUser && Device.deviceType === DeviceType.TABLET) {
|
||||||
|
eventsToFilter = events?.filter(event =>
|
||||||
|
event.attendees?.includes(selectedUser.uid) ||
|
||||||
|
event.creatorId === selectedUser.uid
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredEvents =
|
||||||
|
eventsToFilter?.filter(
|
||||||
|
(event) =>
|
||||||
|
event.start &&
|
||||||
|
event.end &&
|
||||||
|
isWithinInterval(event.start, {
|
||||||
|
start: subDays(selectedDate, startOffset),
|
||||||
|
end: addDays(selectedDate, endOffset),
|
||||||
|
}) &&
|
||||||
|
isWithinInterval(event.end, {
|
||||||
|
start: subDays(selectedDate, startOffset),
|
||||||
|
end: addDays(selectedDate, endOffset),
|
||||||
|
})
|
||||||
|
) ?? [];
|
||||||
|
|
||||||
|
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<string, CalendarEvent[]>);
|
||||||
|
|
||||||
|
const endTime = Date.now();
|
||||||
|
// console.log("memoizedEvents computation time:", endTime - startTime, "ms");
|
||||||
|
|
||||||
|
return {enrichedEvents, filteredEvents};
|
||||||
|
}, [events, selectedDate, mode, selectedUser]);
|
||||||
|
|
||||||
|
const renderCustomDateForMonth = (date: Date) => {
|
||||||
|
const circleStyle = useMemo<ViewStyle>(
|
||||||
|
() => ({
|
||||||
|
width: 30,
|
||||||
|
height: 30,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
borderRadius: 15,
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const defaultStyle = useMemo<ViewStyle>(
|
||||||
|
() => ({
|
||||||
|
...circleStyle,
|
||||||
|
}),
|
||||||
|
[circleStyle]
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentDateStyle = useMemo<ViewStyle>(
|
||||||
|
() => ({
|
||||||
|
...circleStyle,
|
||||||
|
backgroundColor: "#4184f2",
|
||||||
|
}),
|
||||||
|
[circleStyle]
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderDate = useCallback(
|
||||||
|
(date: Date) => {
|
||||||
|
const isCurrentDate = isSameDate(todaysDate, date);
|
||||||
|
const appliedStyle = isCurrentDate ? currentDateStyle : defaultStyle;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{alignItems: "center"}}>
|
||||||
|
<View style={appliedStyle}>
|
||||||
|
<Text style={{color: isCurrentDate ? "white" : "black"}}>
|
||||||
|
{date.getDate()}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[todaysDate, currentDateStyle, defaultStyle]
|
||||||
|
);
|
||||||
|
|
||||||
|
return renderDate(date);
|
||||||
|
};
|
||||||
|
|
||||||
|
const processedEvents = useMemo(() => {
|
||||||
|
return processEventsForSideBySide(filteredEvents);
|
||||||
|
}, [filteredEvents]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOffsetMinutes(getTotalMinutes());
|
||||||
|
}, [events, mode]);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<View style={styles.loadingContainer}>
|
||||||
|
{isSyncing && <Text>Syncing...</Text>}
|
||||||
|
<ActivityIndicator size="large" color="#0000ff"/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isSyncing && (
|
||||||
|
<View style={styles.loadingContainer}>
|
||||||
|
{isSyncing && <Text>Syncing...</Text>}
|
||||||
|
<ActivityIndicator size="large" color="#0000ff"/>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
<Calendar
|
||||||
|
bodyContainerStyle={styles.calHeader}
|
||||||
|
swipeEnabled
|
||||||
|
mode={mode}
|
||||||
|
sortedMonthView
|
||||||
|
events={filteredEvents}
|
||||||
|
// renderEvent={renderEvent}
|
||||||
|
eventCellStyle={memoizedEventCellStyle}
|
||||||
|
allDayEventCellStyle={memoizedEventCellStyle}
|
||||||
|
// enableEnrichedEvents={true}
|
||||||
|
// enrichedEventsByDate={enrichedEvents}
|
||||||
|
onPressEvent={handlePressEvent}
|
||||||
|
weekStartsOn={memoizedWeekStartsOn}
|
||||||
|
height={calendarHeight}
|
||||||
|
activeDate={todaysDate}
|
||||||
|
date={selectedDate}
|
||||||
|
onPressCell={handlePressCell}
|
||||||
|
headerContentStyle={memoizedHeaderContentStyle}
|
||||||
|
onSwipeEnd={handleSwipeEnd}
|
||||||
|
scrollOffsetMinutes={offsetMinutes}
|
||||||
|
theme={{
|
||||||
|
palette: {
|
||||||
|
nowIndicator: profileData?.eventColor || "#fd1575",
|
||||||
|
gray: {
|
||||||
|
"100": "#e8eaed",
|
||||||
|
"200": "#e8eaed",
|
||||||
|
"500": "#b7b7b7",
|
||||||
|
"800": "#919191",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
typography: {
|
||||||
|
fontFamily: "PlusJakartaSans_500Medium",
|
||||||
|
sm: {fontFamily: "Manrope_600SemiBold", fontSize: 8},
|
||||||
|
xl: {
|
||||||
|
fontFamily: "PlusJakartaSans_500Medium",
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
moreLabel: {},
|
||||||
|
xs: {fontSize: 10},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
dayHeaderStyle={dateStyle}
|
||||||
|
dayHeaderHighlightColor={"white"}
|
||||||
|
showAdjacentMonths
|
||||||
|
headerContainerStyle={mode !== "month" ? {
|
||||||
|
overflow: "hidden",
|
||||||
|
} : {}}
|
||||||
|
hourStyle={styles.hourStyle}
|
||||||
|
onPressDateHeader={handlePressDayHeader}
|
||||||
|
ampm
|
||||||
|
// renderCustomDateForMonth={renderCustomDateForMonth}
|
||||||
|
/>
|
||||||
|
<View style={{backgroundColor: 'white', height: 50, width: '100%'}}/>
|
||||||
|
</>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
segmentslblStyle: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: "Manrope_600SemiBold",
|
||||||
|
},
|
||||||
|
calHeader: {
|
||||||
|
borderWidth: 0,
|
||||||
|
paddingBottom: 60,
|
||||||
|
},
|
||||||
|
dayModeHeader: {
|
||||||
|
alignSelf: "flex-start",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignContent: "center",
|
||||||
|
width: 38,
|
||||||
|
right: 42,
|
||||||
|
height: 13,
|
||||||
|
},
|
||||||
|
weekModeHeader: {},
|
||||||
|
monthModeHeader: {},
|
||||||
|
loadingContainer: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
position: "absolute",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
zIndex: 100,
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.9)",
|
||||||
|
},
|
||||||
|
dayHeader: {
|
||||||
|
backgroundColor: "#4184f2",
|
||||||
|
aspectRatio: 1,
|
||||||
|
borderRadius: 100,
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
},
|
||||||
|
otherDayHeader: {
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
color: "#919191",
|
||||||
|
aspectRatio: 1,
|
||||||
|
borderRadius: 100,
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
},
|
||||||
|
hourStyle: {
|
||||||
|
color: "#5f6368",
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: "Manrope_500Medium",
|
||||||
|
},
|
||||||
|
eventCell: {
|
||||||
|
flex: 1,
|
||||||
|
borderRadius: 4,
|
||||||
|
padding: 4,
|
||||||
|
height: '100%',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
eventTitle: {
|
||||||
|
color: 'white',
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: "PlusJakartaSans_500Medium",
|
||||||
|
},
|
||||||
|
});
|
@ -1 +0,0 @@
|
|||||||
|
|
45
components/pages/calendar/useCalendarControls.ts
Normal file
45
components/pages/calendar/useCalendarControls.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
import debounce from 'debounce';
|
||||||
|
import { useSetAtom } from 'jotai';
|
||||||
|
import {
|
||||||
|
editVisibleAtom,
|
||||||
|
eventForEditAtom,
|
||||||
|
selectedNewEventDateAtom,
|
||||||
|
selectedDateAtom,
|
||||||
|
} from '@/components/pages/calendar/atoms';
|
||||||
|
import {DateOrDateTime} from "@howljs/calendar-kit";
|
||||||
|
|
||||||
|
export const useCalendarControls = (events: any[]) => {
|
||||||
|
const setEditVisible = useSetAtom(editVisibleAtom);
|
||||||
|
const setEventForEdit = useSetAtom(eventForEditAtom);
|
||||||
|
const setSelectedNewEndDate = useSetAtom(selectedNewEventDateAtom);
|
||||||
|
const setSelectedDate = useSetAtom(selectedDateAtom);
|
||||||
|
|
||||||
|
const handlePressEvent = useCallback((event: any) => {
|
||||||
|
const foundEvent = events?.find(x => x.id === event.id);
|
||||||
|
setEditVisible(true);
|
||||||
|
setEventForEdit(foundEvent!);
|
||||||
|
}, [events, setEditVisible, setEventForEdit]);
|
||||||
|
|
||||||
|
const handlePressCell = useCallback((date: DateOrDateTime) => {
|
||||||
|
const selectedDate = new Date(date.dateTime!);
|
||||||
|
const minutes = selectedDate.getMinutes();
|
||||||
|
|
||||||
|
selectedDate.setMinutes(minutes - (minutes % 30), 0, 0); // Also sets seconds and milliseconds to 0
|
||||||
|
|
||||||
|
setSelectedNewEndDate(selectedDate);
|
||||||
|
}, [setSelectedNewEndDate]);
|
||||||
|
|
||||||
|
const debouncedOnDateChanged = useCallback(
|
||||||
|
debounce((date: string) => {
|
||||||
|
setSelectedDate(new Date(date));
|
||||||
|
}, 50),
|
||||||
|
[setSelectedDate]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
handlePressEvent,
|
||||||
|
handlePressCell,
|
||||||
|
debouncedOnDateChanged
|
||||||
|
};
|
||||||
|
};
|
154
components/pages/calendar/useFormattedEvents.ts
Normal file
154
components/pages/calendar/useFormattedEvents.ts
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import { DeviceType } from 'expo-device';
|
||||||
|
import * as Device from 'expo-device';
|
||||||
|
import { format } from 'date-fns';
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
interface EventTimestamp {
|
||||||
|
seconds: number;
|
||||||
|
nanoseconds: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Event {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
start: Date | EventTimestamp;
|
||||||
|
startDate?: EventTimestamp;
|
||||||
|
end: Date | EventTimestamp;
|
||||||
|
endDate?: EventTimestamp;
|
||||||
|
allDay: boolean;
|
||||||
|
eventColor: string;
|
||||||
|
attendees?: string[];
|
||||||
|
creatorId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FormattedEvent {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
start: { date: string } | { dateTime: string; timeZone: string };
|
||||||
|
end: { date: string } | { dateTime: string; timeZone: string };
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Precompute time constants
|
||||||
|
const DAY_IN_MS = 24 * 60 * 60 * 1000;
|
||||||
|
const PERIOD_IN_MS = 45 * DAY_IN_MS;
|
||||||
|
const TIME_ZONE = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||||
|
|
||||||
|
// Memoize date range calculations
|
||||||
|
const getDateRangeKey = (timestamp: number) => Math.floor(timestamp / PERIOD_IN_MS);
|
||||||
|
|
||||||
|
// Optimized date validation and conversion
|
||||||
|
const getValidDate = (date: any, timestamp?: EventTimestamp): Date | null => {
|
||||||
|
try {
|
||||||
|
if (timestamp?.seconds) {
|
||||||
|
return new Date(timestamp.seconds * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (date instanceof Date) {
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof date === 'string') {
|
||||||
|
return new Date(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Batch process events
|
||||||
|
const processEvents = async (
|
||||||
|
events: Event[],
|
||||||
|
selectedDate: Date,
|
||||||
|
selectedUser: { uid: string } | null
|
||||||
|
): Promise<FormattedEvent[]> => {
|
||||||
|
// 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 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');
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error processing event:', event.id, error);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useFormattedEvents = (
|
||||||
|
events: Event[],
|
||||||
|
selectedDate: Date,
|
||||||
|
selectedUser: { uid: string } | null
|
||||||
|
) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['formattedEvents', events, selectedDate, selectedUser?.uid],
|
||||||
|
queryFn: () => processEvents(events, selectedDate, selectedUser),
|
||||||
|
enabled: events.length > 0,
|
||||||
|
staleTime: Infinity,
|
||||||
|
placeholderData: (previousData) => previousData,
|
||||||
|
gcTime: Infinity
|
||||||
|
});
|
||||||
|
};
|
@ -9,7 +9,7 @@ import * as Notifications from 'expo-notifications';
|
|||||||
import * as Device from 'expo-device';
|
import * as Device from 'expo-device';
|
||||||
import Constants from 'expo-constants';
|
import Constants from 'expo-constants';
|
||||||
import {Platform} from 'react-native';
|
import {Platform} from 'react-native';
|
||||||
import {useQueryClient} from "react-query";
|
import {useQueryClient} from "@tanstack/react-query";
|
||||||
|
|
||||||
|
|
||||||
export enum ProfileType {
|
export enum ProfileType {
|
||||||
|
77
contexts/PersistQueryClientProvider.tsx
Normal file
77
contexts/PersistQueryClientProvider.tsx
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
|
import { persistQueryClient } from '@tanstack/react-query-persist-client';
|
||||||
|
import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister';
|
||||||
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import type { AsyncPersistRetryer } from '@tanstack/query-async-storage-persister';
|
||||||
|
|
||||||
|
const createQueryClient = () => new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
gcTime: 1000 * 60 * 60 * 24, // 24 hours
|
||||||
|
staleTime: 1000 * 60 * 5, // 5 minutes
|
||||||
|
retry: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export function PersistQueryClientProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
const [queryClient] = useState(() => createQueryClient());
|
||||||
|
const [isRestored, setIsRestored] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const initPersistence = async () => {
|
||||||
|
const retry: AsyncPersistRetryer = async ({ persistedClient, error, errorCount }) => {
|
||||||
|
if (errorCount < 3) return persistedClient;
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const asyncStoragePersister = createAsyncStoragePersister({
|
||||||
|
storage: AsyncStorage,
|
||||||
|
key: 'REACT_QUERY_CACHE',
|
||||||
|
throttleTime: 1000,
|
||||||
|
serialize: data => JSON.stringify(data),
|
||||||
|
deserialize: str => JSON.parse(str),
|
||||||
|
retry
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
persistQueryClient({
|
||||||
|
queryClient,
|
||||||
|
persister: asyncStoragePersister,
|
||||||
|
maxAge: 1000 * 60 * 60 * 24 * 7, // 7 days
|
||||||
|
buster: 'v1',
|
||||||
|
dehydrateOptions: {
|
||||||
|
shouldDehydrateQuery: query => {
|
||||||
|
const persistedQueries = ['events'];
|
||||||
|
return persistedQueries.some(key =>
|
||||||
|
Array.isArray(query.queryKey) &&
|
||||||
|
query.queryKey.includes(key)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
setIsRestored(true);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to restore query cache:', error);
|
||||||
|
setIsRestored(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
initPersistence();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
queryClient.clear();
|
||||||
|
};
|
||||||
|
}, [queryClient]);
|
||||||
|
|
||||||
|
if (!isRestored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
{children}
|
||||||
|
</QueryClientProvider>
|
||||||
|
);
|
||||||
|
}
|
@ -1144,12 +1144,14 @@ exports.cleanupTokenRefreshFlags = functions.pubsub
|
|||||||
|
|
||||||
async function fetchAndSaveGoogleEvents({token, refreshToken, email, familyId, creatorId}) {
|
async function fetchAndSaveGoogleEvents({token, refreshToken, email, familyId, creatorId}) {
|
||||||
const baseDate = new Date();
|
const baseDate = new Date();
|
||||||
const timeMin = new Date(baseDate.setMonth(baseDate.getMonth() - 1)).toISOString();
|
const oneYearAgo = new Date(baseDate);
|
||||||
const timeMax = new Date(baseDate.setMonth(baseDate.getMonth() + 2)).toISOString();
|
oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
|
||||||
|
const oneYearAhead = new Date(baseDate);
|
||||||
|
oneYearAhead.setFullYear(oneYearAhead.getFullYear() + 1);
|
||||||
|
|
||||||
let totalEvents = 0;
|
let totalEvents = 0;
|
||||||
let pageToken = null;
|
let pageToken = null;
|
||||||
const batchSize = 50;
|
const batchSize = 250;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(`[FETCH] Starting event fetch for user: ${email}`);
|
console.log(`[FETCH] Starting event fetch for user: ${email}`);
|
||||||
@ -1158,12 +1160,12 @@ async function fetchAndSaveGoogleEvents({token, refreshToken, email, familyId, c
|
|||||||
let events = [];
|
let events = [];
|
||||||
const url = new URL(`https://www.googleapis.com/calendar/v3/calendars/primary/events`);
|
const url = new URL(`https://www.googleapis.com/calendar/v3/calendars/primary/events`);
|
||||||
url.searchParams.set("singleEvents", "true");
|
url.searchParams.set("singleEvents", "true");
|
||||||
url.searchParams.set("timeMin", timeMin);
|
url.searchParams.set("timeMin", oneYearAgo.toISOString());
|
||||||
url.searchParams.set("timeMax", timeMax);
|
url.searchParams.set("timeMax", oneYearAhead.toISOString());
|
||||||
url.searchParams.set("maxResults", batchSize.toString());
|
url.searchParams.set("maxResults", batchSize.toString());
|
||||||
if (pageToken) url.searchParams.set("pageToken", pageToken);
|
if (pageToken) url.searchParams.set("pageToken", pageToken);
|
||||||
|
|
||||||
console.log(`[FETCH] Making request with token: ${token.substring(0, 10)}...`);
|
console.log(`[FETCH] Making request with token: ${token?.substring(0, 10)}...`);
|
||||||
|
|
||||||
let response = await fetch(url.toString(), {
|
let response = await fetch(url.toString(), {
|
||||||
headers: {
|
headers: {
|
||||||
@ -1178,12 +1180,10 @@ async function fetchAndSaveGoogleEvents({token, refreshToken, email, familyId, c
|
|||||||
console.log(`[TOKEN] Token refreshed successfully during fetch`);
|
console.log(`[TOKEN] Token refreshed successfully during fetch`);
|
||||||
token = refreshedGoogleToken;
|
token = refreshedGoogleToken;
|
||||||
|
|
||||||
// Update token in Firestore
|
|
||||||
await db.collection("Profiles").doc(creatorId).update({
|
await db.collection("Profiles").doc(creatorId).update({
|
||||||
[`googleAccounts.${email}.accessToken`]: refreshedGoogleToken
|
[`googleAccounts.${email}.accessToken`]: refreshedGoogleToken
|
||||||
});
|
});
|
||||||
|
|
||||||
// Retry the request with new token
|
|
||||||
response = await fetch(url.toString(), {
|
response = await fetch(url.toString(), {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${refreshedGoogleToken}`,
|
Authorization: `Bearer ${refreshedGoogleToken}`,
|
||||||
|
@ -280,6 +280,13 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
|
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
|
||||||
integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==
|
integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==
|
||||||
|
|
||||||
|
"@react-native-async-storage/async-storage@1.23.1":
|
||||||
|
version "1.23.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-1.23.1.tgz#cad3cd4fab7dacfe9838dce6ecb352f79150c883"
|
||||||
|
integrity sha512-Qd2kQ3yi6Y3+AcUlrHxSLlnBvpdCEMVGFlVBneVOjaFaPU61g1huc38g339ysXspwY1QZA2aNhrk/KlHGO+ewA==
|
||||||
|
dependencies:
|
||||||
|
merge-options "^3.0.4"
|
||||||
|
|
||||||
"@tootallnate/once@2":
|
"@tootallnate/once@2":
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
|
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
|
||||||
@ -1470,6 +1477,11 @@ is-path-inside@^3.0.3:
|
|||||||
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
|
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
|
||||||
integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
|
integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
|
||||||
|
|
||||||
|
is-plain-obj@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287"
|
||||||
|
integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==
|
||||||
|
|
||||||
is-stream@^2.0.0:
|
is-stream@^2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
|
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
|
||||||
@ -1688,6 +1700,13 @@ merge-descriptors@1.0.3:
|
|||||||
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5"
|
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5"
|
||||||
integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==
|
integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==
|
||||||
|
|
||||||
|
merge-options@^3.0.4:
|
||||||
|
version "3.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/merge-options/-/merge-options-3.0.4.tgz#84709c2aa2a4b24c1981f66c179fe5565cc6dbb7"
|
||||||
|
integrity sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==
|
||||||
|
dependencies:
|
||||||
|
is-plain-obj "^2.1.0"
|
||||||
|
|
||||||
merge2@^1.3.0:
|
merge2@^1.3.0:
|
||||||
version "1.4.1"
|
version "1.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {useMutation, useQueryClient} from "react-query";
|
import {useMutation, useQueryClient} from "@tanstack/react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
import storage from "@react-native-firebase/storage";
|
import storage from "@react-native-firebase/storage";
|
||||||
import {useAuthContext} from "@/contexts/AuthContext";
|
import {useAuthContext} from "@/contexts/AuthContext";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {useMutation} from "react-query";
|
import {useMutation} from "@tanstack/react-query";
|
||||||
import {UserProfile} from "@firebase/auth";
|
import {UserProfile} from "@firebase/auth";
|
||||||
import {useAuthContext} from "@/contexts/AuthContext";
|
import {useAuthContext} from "@/contexts/AuthContext";
|
||||||
import {useUpdateUserData} from "@/hooks/firebase/useUpdateUserData";
|
import {useUpdateUserData} from "@/hooks/firebase/useUpdateUserData";
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {useAuthContext} from "@/contexts/AuthContext";
|
import {useAuthContext} from "@/contexts/AuthContext";
|
||||||
import {useMutation, useQueryClient} from "react-query";
|
import {useMutation, useQueryClient} from "@tanstack/react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
import {EventData} from "@/hooks/firebase/types/eventData";
|
import {EventData} from "@/hooks/firebase/types/eventData";
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {useAuthContext} from "@/contexts/AuthContext";
|
import {useAuthContext} from "@/contexts/AuthContext";
|
||||||
import {useMutation, useQueryClient} from "react-query";
|
import {useMutation, useQueryClient} from "@tanstack/react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
import { IFeedback } from "@/contexts/FeedbackContext";
|
import { IFeedback } from "@/contexts/FeedbackContext";
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useMutation, useQueryClient } from "react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
import { useAuthContext } from "@/contexts/AuthContext";
|
import { useAuthContext } from "@/contexts/AuthContext";
|
||||||
import {IGrocery} from "@/hooks/firebase/types/groceryData";
|
import {IGrocery} from "@/hooks/firebase/types/groceryData";
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useAuthContext } from "@/contexts/AuthContext";
|
import { useAuthContext } from "@/contexts/AuthContext";
|
||||||
import { useMutation, useQueryClient } from "react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
import { IFeedback } from "@/contexts/FeedbackContext";
|
import { IFeedback } from "@/contexts/FeedbackContext";
|
||||||
import { IBrainDump } from "@/contexts/DumpContext";
|
import { IBrainDump } from "@/contexts/DumpContext";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {useMutation, useQueryClient} from "react-query";
|
import {useMutation, useQueryClient} from "@tanstack/react-query";
|
||||||
import {UserProfile} from "@/hooks/firebase/types/profileTypes";
|
import {UserProfile} from "@/hooks/firebase/types/profileTypes";
|
||||||
import functions from '@react-native-firebase/functions';
|
import functions from '@react-native-firebase/functions';
|
||||||
import {ProfileType, useAuthContext} from "@/contexts/AuthContext";
|
import {ProfileType, useAuthContext} from "@/contexts/AuthContext";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useMutation, useQueryClient } from "react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
import { useAuthContext } from "@/contexts/AuthContext";
|
import { useAuthContext } from "@/contexts/AuthContext";
|
||||||
import { DAYS_OF_WEEK_ENUM, IToDo, REPEAT_TYPE } from "@/hooks/firebase/types/todoData";
|
import { DAYS_OF_WEEK_ENUM, IToDo, REPEAT_TYPE } from "@/hooks/firebase/types/todoData";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {useMutation, useQueryClient} from "react-query";
|
import {useMutation, useQueryClient} from "@tanstack/react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
|
|
||||||
export const useDeleteEvent = () => {
|
export const useDeleteEvent = () => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {useAuthContext} from "@/contexts/AuthContext";
|
import {useAuthContext} from "@/contexts/AuthContext";
|
||||||
import {useMutation, useQueryClient} from "react-query";
|
import {useMutation, useQueryClient} from "@tanstack/react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
|
|
||||||
export const useDeleteFeedback = () => {
|
export const useDeleteFeedback = () => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useMutation, useQueryClient } from "react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
|
|
||||||
export const useDeleteGrocery = () => {
|
export const useDeleteGrocery = () => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useAuthContext } from "@/contexts/AuthContext";
|
import { useAuthContext } from "@/contexts/AuthContext";
|
||||||
import { useMutation, useQueryClient } from "react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
|
|
||||||
export const useDeleteNote = () => {
|
export const useDeleteNote = () => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {useMutation, useQueryClient} from "react-query";
|
import {useMutation, useQueryClient} from "@tanstack/react-query";
|
||||||
import {useAuthContext} from "@/contexts/AuthContext";
|
import {useAuthContext} from "@/contexts/AuthContext";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
import {Notification} from "@/hooks/firebase/useGetNotifications";
|
import {Notification} from "@/hooks/firebase/useGetNotifications";
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {useAuthContext} from "@/contexts/AuthContext";
|
import {useAuthContext} from "@/contexts/AuthContext";
|
||||||
import {useMutation} from "react-query";
|
import {useMutation} from "@tanstack/react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
import auth, {FirebaseAuthTypes} from "@react-native-firebase/auth";
|
import auth, {FirebaseAuthTypes} from "@react-native-firebase/auth";
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {useAuthContext} from "@/contexts/AuthContext";
|
import {useAuthContext} from "@/contexts/AuthContext";
|
||||||
import {useMutation} from "react-query";
|
import {useMutation} from "@tanstack/react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
import {UserProfile} from "@/hooks/firebase/types/profileTypes";
|
import {UserProfile} from "@/hooks/firebase/types/profileTypes";
|
||||||
import {FirebaseAuthTypes} from "@react-native-firebase/auth";
|
import {FirebaseAuthTypes} from "@react-native-firebase/auth";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useMutation } from "react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
|
|
||||||
export const useSignUp = () => {
|
export const useSignUp = () => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {useQuery} from "react-query";
|
import {useQuery} from "@tanstack/react-query";
|
||||||
import {ChildProfile} from "@/hooks/firebase/types/profileTypes";
|
import {ChildProfile} from "@/hooks/firebase/types/profileTypes";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
import {useAuthContext} from "@/contexts/AuthContext";
|
import {useAuthContext} from "@/contexts/AuthContext";
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import {useQuery, useQueryClient} from "react-query";
|
import {useQuery, useQueryClient} from "@tanstack/react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
import {useAuthContext} from "@/contexts/AuthContext";
|
import {useAuthContext} from "@/contexts/AuthContext";
|
||||||
import {useAtomValue} from "jotai";
|
import {useAtomValue} from "jotai";
|
||||||
import {isFamilyViewAtom} from "@/components/pages/calendar/atoms";
|
import {isFamilyViewAtom} from "@/components/pages/calendar/atoms";
|
||||||
import {colorMap} from "@/constants/colorMap";
|
import {colorMap} from "@/constants/colorMap";
|
||||||
import {uuidv4} from "@firebase/util";
|
import {useEffect, useRef} from "react";
|
||||||
import {useEffect} from "react";
|
|
||||||
|
|
||||||
const createEventHash = (event: any): string => {
|
const createEventHash = (event: any): string => {
|
||||||
const str = `${event.startDate?.seconds || ''}-${event.endDate?.seconds || ''}-${
|
const str = `${event.startDate?.seconds || ''}-${event.endDate?.seconds || ''}-${
|
||||||
@ -21,206 +20,120 @@ const createEventHash = (event: any): string => {
|
|||||||
return hash.toString(36);
|
return hash.toString(36);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const useGetEvents = () => {
|
export const useGetEvents = () => {
|
||||||
const {user, profileData} = useAuthContext();
|
const {user, profileData} = useAuthContext();
|
||||||
const isFamilyView = useAtomValue(isFamilyViewAtom);
|
const isFamilyView = useAtomValue(isFamilyViewAtom);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
const lastSyncTimestamp = useRef<number>(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!profileData?.familyId) {
|
if (!profileData?.familyId) return;
|
||||||
console.log('[SYNC] No family ID available, skipping listener setup');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[SYNC] Setting up sync listener', {
|
|
||||||
familyId: profileData.familyId,
|
|
||||||
userId: user?.uid,
|
|
||||||
isFamilyView
|
|
||||||
});
|
|
||||||
|
|
||||||
const unsubscribe = firestore()
|
const unsubscribe = firestore()
|
||||||
.collection('Households')
|
.collection('Households')
|
||||||
.where("familyId", "==", profileData.familyId)
|
.where("familyId", "==", profileData.familyId)
|
||||||
.onSnapshot((snapshot) => {
|
.onSnapshot((snapshot) => {
|
||||||
console.log('[SYNC] Snapshot received', {
|
|
||||||
empty: snapshot.empty,
|
|
||||||
size: snapshot.size,
|
|
||||||
changes: snapshot.docChanges().length
|
|
||||||
});
|
|
||||||
|
|
||||||
snapshot.docChanges().forEach((change) => {
|
snapshot.docChanges().forEach((change) => {
|
||||||
console.log('[SYNC] Processing change', {
|
|
||||||
type: change.type,
|
|
||||||
docId: change.doc.id,
|
|
||||||
newData: change.doc.data()
|
|
||||||
});
|
|
||||||
|
|
||||||
if (change.type === 'modified') {
|
if (change.type === 'modified') {
|
||||||
const data = change.doc.data();
|
const data = change.doc.data();
|
||||||
console.log('[SYNC] Modified document data', {
|
|
||||||
hasLastSyncTimestamp: !!data?.lastSyncTimestamp,
|
|
||||||
hasLastUpdateTimestamp: !!data?.lastUpdateTimestamp,
|
|
||||||
allFields: Object.keys(data || {})
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data?.lastSyncTimestamp) {
|
if (data?.lastSyncTimestamp) {
|
||||||
console.log('[SYNC] Sync timestamp change detected', {
|
const newTimestamp = data.lastSyncTimestamp.seconds;
|
||||||
timestamp: data.lastSyncTimestamp.toDate(),
|
if (newTimestamp > lastSyncTimestamp.current) {
|
||||||
householdId: change.doc.id,
|
lastSyncTimestamp.current = newTimestamp;
|
||||||
queryKey: ["events", user?.uid, isFamilyView]
|
queryClient.invalidateQueries(["events", user?.uid, isFamilyView]);
|
||||||
});
|
}
|
||||||
|
|
||||||
console.log('[SYNC] Invalidating queries...');
|
|
||||||
queryClient.invalidateQueries(["events", user?.uid, isFamilyView]);
|
|
||||||
console.log('[SYNC] Queries invalidated');
|
|
||||||
} else {
|
|
||||||
console.log('[SYNC] Modified document without lastSyncTimestamp', {
|
|
||||||
householdId: change.doc.id
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, (error) => {
|
}, console.error);
|
||||||
console.error('[SYNC] Listener error:', {
|
|
||||||
message: error.message,
|
|
||||||
code: error.code,
|
|
||||||
stack: error.stack
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('[SYNC] Listener setup complete');
|
return unsubscribe;
|
||||||
|
|
||||||
return () => {
|
|
||||||
console.log('[SYNC] Cleaning up sync listener', {
|
|
||||||
familyId: profileData.familyId,
|
|
||||||
userId: user?.uid
|
|
||||||
});
|
|
||||||
unsubscribe();
|
|
||||||
};
|
|
||||||
}, [profileData?.familyId, user?.uid, isFamilyView, queryClient]);
|
}, [profileData?.familyId, user?.uid, isFamilyView, queryClient]);
|
||||||
|
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["events", user?.uid, isFamilyView],
|
queryKey: ["events", user?.uid, isFamilyView],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
console.log(`Fetching events - Family View: ${isFamilyView}, User: ${user?.uid}`);
|
|
||||||
|
|
||||||
const db = firestore();
|
const db = firestore();
|
||||||
const userId = user?.uid;
|
const userId = user?.uid;
|
||||||
const familyId = profileData?.familyId;
|
const familyId = profileData?.familyId;
|
||||||
let allEvents = [];
|
|
||||||
|
const eventsQuery = db.collection("Events");
|
||||||
|
let constraints = [];
|
||||||
|
|
||||||
if (isFamilyView) {
|
if (isFamilyView) {
|
||||||
const [publicFamilyEvents, privateCreatorEvents, privateAttendeeEvents, userAttendeeEvents] = await Promise.all([
|
constraints = [
|
||||||
// Public family events
|
eventsQuery
|
||||||
db.collection("Events")
|
|
||||||
.where("familyId", "==", familyId)
|
.where("familyId", "==", familyId)
|
||||||
.where("private", "==", false)
|
.where("private", "==", false),
|
||||||
.get(),
|
eventsQuery
|
||||||
|
.where("creatorId", "==", userId),
|
||||||
// Private events user created
|
eventsQuery
|
||||||
db.collection("Events")
|
|
||||||
.where("familyId", "==", familyId)
|
|
||||||
.where("private", "==", true)
|
|
||||||
.where("creatorId", "==", userId)
|
|
||||||
.get(),
|
|
||||||
|
|
||||||
// Private events user is attending
|
|
||||||
db.collection("Events")
|
|
||||||
.where("private", "==", true)
|
|
||||||
.where("attendees", "array-contains", userId)
|
.where("attendees", "array-contains", userId)
|
||||||
.get(),
|
|
||||||
|
|
||||||
// All events where user is attendee
|
|
||||||
db.collection("Events")
|
|
||||||
.where("attendees", "array-contains", userId)
|
|
||||||
.get(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
allEvents = [
|
|
||||||
...publicFamilyEvents.docs.map(doc => ({...doc.data(), id: doc.id})),
|
|
||||||
...privateCreatorEvents.docs.map(doc => ({...doc.data(), id: doc.id})),
|
|
||||||
...privateAttendeeEvents.docs.map(doc => ({...doc.data(), id: doc.id})),
|
|
||||||
...userAttendeeEvents.docs.map(doc => ({...doc.data(), id: doc.id})),
|
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
const [creatorEvents, attendeeEvents] = await Promise.all([
|
constraints = [
|
||||||
db.collection("Events")
|
eventsQuery.where("creatorId", "==", userId),
|
||||||
.where("creatorId", "==", userId)
|
eventsQuery.where("attendees", "array-contains", userId)
|
||||||
.get(),
|
|
||||||
db.collection("Events")
|
|
||||||
.where("attendees", "array-contains", userId)
|
|
||||||
.get()
|
|
||||||
]);
|
|
||||||
|
|
||||||
console.log(`Found ${creatorEvents.size} creator events, ${attendeeEvents.size} attendee events`);
|
|
||||||
|
|
||||||
allEvents = [
|
|
||||||
...creatorEvents.docs.map(doc => ({...doc.data(), id: doc.id})),
|
|
||||||
...attendeeEvents.docs.map(doc => ({...doc.data(), id: doc.id}))
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const uniqueEventsMap = new Map();
|
const snapshots = await Promise.all(
|
||||||
const processedHashes = new Set();
|
constraints.map(query => query.get())
|
||||||
|
|
||||||
allEvents.forEach(event => {
|
|
||||||
const eventHash = createEventHash(event);
|
|
||||||
|
|
||||||
console.log(`Processing ${uniqueEventsMap.size} unique events`);
|
|
||||||
|
|
||||||
const processedEvent = {
|
|
||||||
...event,
|
|
||||||
id: event.id || uuidv4(),
|
|
||||||
creatorId: event.creatorId || userId
|
|
||||||
};
|
|
||||||
|
|
||||||
// Only add the event if we haven't seen this hash before
|
|
||||||
if (!processedHashes.has(eventHash)) {
|
|
||||||
processedHashes.add(eventHash);
|
|
||||||
uniqueEventsMap.set(processedEvent.id, processedEvent);
|
|
||||||
} else {
|
|
||||||
console.log(`Duplicate event detected and skipped using hash: ${eventHash}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`Processing ${uniqueEventsMap.size} unique events after deduplication`);
|
|
||||||
|
|
||||||
const processedEvents = await Promise.all(
|
|
||||||
Array.from(uniqueEventsMap.values()).map(async (event) => {
|
|
||||||
const profileSnapshot = await db
|
|
||||||
.collection("Profiles")
|
|
||||||
.doc(event.creatorId)
|
|
||||||
.get();
|
|
||||||
|
|
||||||
const profileData = profileSnapshot.data();
|
|
||||||
const eventColor = profileData?.eventColor || colorMap.pink;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...event,
|
|
||||||
start: event.allDay
|
|
||||||
? new Date(new Date(event.startDate.seconds * 1000).setHours(0, 0, 0, 0))
|
|
||||||
: new Date(event.startDate.seconds * 1000),
|
|
||||||
end: event.allDay
|
|
||||||
? new Date(new Date(event.endDate.seconds * 1000).setHours(0, 0, 0, 0))
|
|
||||||
: new Date(event.endDate.seconds * 1000),
|
|
||||||
hideHours: event.allDay,
|
|
||||||
eventColor,
|
|
||||||
notes: event.notes,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const uniqueEvents = new Map();
|
||||||
|
const processedHashes = new Set();
|
||||||
|
const creatorIds = new Set();
|
||||||
|
|
||||||
console.log(`Events processing completed, returning ${processedEvents.length} events`);
|
snapshots.forEach(snapshot => {
|
||||||
return processedEvents;
|
snapshot.docs.forEach(doc => {
|
||||||
|
const event = doc.data();
|
||||||
|
const hash = createEventHash(event);
|
||||||
|
|
||||||
|
if (!processedHashes.has(hash)) {
|
||||||
|
processedHashes.add(hash);
|
||||||
|
creatorIds.add(event.creatorId);
|
||||||
|
uniqueEvents.set(doc.id, event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const creatorIdsArray = Array.from(creatorIds);
|
||||||
|
const creatorProfiles = new Map();
|
||||||
|
|
||||||
|
for (let i = 0; i < creatorIdsArray.length; i += 10) {
|
||||||
|
const chunk = creatorIdsArray.slice(i, i + 10);
|
||||||
|
const profilesSnapshot = await db
|
||||||
|
.collection("Profiles")
|
||||||
|
.where(firestore.FieldPath.documentId(), "in", chunk)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
profilesSnapshot.docs.forEach(doc => {
|
||||||
|
creatorProfiles.set(doc.id, doc.data()?.eventColor || colorMap.pink);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(uniqueEvents.entries()).map(([id, event]) => {
|
||||||
|
const startSeconds = event.startDate.seconds;
|
||||||
|
const endSeconds = event.endDate.seconds;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...event,
|
||||||
|
id,
|
||||||
|
start: event.allDay
|
||||||
|
? new Date(new Date(startSeconds * 1000).setHours(0, 0, 0, 0))
|
||||||
|
: new Date(startSeconds * 1000),
|
||||||
|
end: event.allDay
|
||||||
|
? new Date(new Date(endSeconds * 1000).setHours(0, 0, 0, 0))
|
||||||
|
: new Date(endSeconds * 1000),
|
||||||
|
hideHours: event.allDay,
|
||||||
|
eventColor: creatorProfiles.get(event.creatorId) || colorMap.pink,
|
||||||
|
notes: event.notes
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
staleTime: 5 * 60 * 1000,
|
staleTime: 5 * 60 * 1000,
|
||||||
cacheTime: 30 * 60 * 1000,
|
gcTime: Infinity,
|
||||||
keepPreviousData: true,
|
placeholderData: (previousData) => previousData,
|
||||||
onError: (error) => {
|
|
||||||
console.error('Error fetching events:', error);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
@ -1,4 +1,4 @@
|
|||||||
import {useQuery} from "react-query";
|
import {useQuery} from "@tanstack/react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
import {useAuthContext} from "@/contexts/AuthContext";
|
import {useAuthContext} from "@/contexts/AuthContext";
|
||||||
import {UserProfile} from "@/hooks/firebase/types/profileTypes";
|
import {UserProfile} from "@/hooks/firebase/types/profileTypes";
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useAuthContext } from "@/contexts/AuthContext";
|
import { useAuthContext } from "@/contexts/AuthContext";
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
import { IFeedback } from "@/contexts/FeedbackContext";
|
import { IFeedback } from "@/contexts/FeedbackContext";
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {useQuery} from "react-query";
|
import {useQuery} from "@tanstack/react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
import {useAuthContext} from "@/contexts/AuthContext";
|
import {useAuthContext} from "@/contexts/AuthContext";
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useQuery } from "react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
|
|
||||||
export const useGetHouseholdName = (familyId: string) => {
|
export const useGetHouseholdName = (familyId: string) => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useAuthContext } from "@/contexts/AuthContext";
|
import { useAuthContext } from "@/contexts/AuthContext";
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
import { IBrainDump } from "@/contexts/DumpContext";
|
import { IBrainDump } from "@/contexts/DumpContext";
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useQuery } from "react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
import { useAuthContext } from "@/contexts/AuthContext";
|
import { useAuthContext } from "@/contexts/AuthContext";
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useQuery } from "react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import firestore, {or, query, where} from "@react-native-firebase/firestore";
|
import firestore, {or, query, where} from "@react-native-firebase/firestore";
|
||||||
import { ProfileType, useAuthContext } from "@/contexts/AuthContext";
|
import { ProfileType, useAuthContext } from "@/contexts/AuthContext";
|
||||||
import { IToDo } from "@/hooks/firebase/types/todoData";
|
import { IToDo } from "@/hooks/firebase/types/todoData";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useQuery } from "react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { UserProfile } from "@/hooks/firebase/types/profileTypes";
|
import { UserProfile } from "@/hooks/firebase/types/profileTypes";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {useMutation} from "react-query";
|
import {useMutation} from "@tanstack/react-query";
|
||||||
import functions, {FirebaseFunctionsTypes} from '@react-native-firebase/functions';
|
import functions, {FirebaseFunctionsTypes} from '@react-native-firebase/functions';
|
||||||
import auth from "@react-native-firebase/auth";
|
import auth from "@react-native-firebase/auth";
|
||||||
import {useAuthContext} from "@/contexts/AuthContext";
|
import {useAuthContext} from "@/contexts/AuthContext";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useMutation, useQueryClient } from "react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import functions from '@react-native-firebase/functions';
|
import functions from '@react-native-firebase/functions';
|
||||||
import { ProfileType, useAuthContext } from "@/contexts/AuthContext";
|
import { ProfileType, useAuthContext } from "@/contexts/AuthContext";
|
||||||
import { HttpsCallableResult } from "@firebase/functions";
|
import { HttpsCallableResult } from "@firebase/functions";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {useMutation} from "react-query";
|
import {useMutation} from "@tanstack/react-query";
|
||||||
import auth from "@react-native-firebase/auth";
|
import auth from "@react-native-firebase/auth";
|
||||||
|
|
||||||
export const useResetPassword = () => {
|
export const useResetPassword = () => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {useAuthContext} from "@/contexts/AuthContext";
|
import {useAuthContext} from "@/contexts/AuthContext";
|
||||||
import {useMutation} from "react-query";
|
import {useMutation} from "@tanstack/react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
import {UserProfile} from "@/hooks/firebase/types/profileTypes";
|
import {UserProfile} from "@/hooks/firebase/types/profileTypes";
|
||||||
import {FirebaseAuthTypes} from "@react-native-firebase/auth";
|
import {FirebaseAuthTypes} from "@react-native-firebase/auth";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useMutation } from "react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import auth from "@react-native-firebase/auth";
|
import auth from "@react-native-firebase/auth";
|
||||||
|
|
||||||
export const useSignIn = () => {
|
export const useSignIn = () => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {useMutation} from "react-query";
|
import {useMutation} from "@tanstack/react-query";
|
||||||
import auth from "@react-native-firebase/auth";
|
import auth from "@react-native-firebase/auth";
|
||||||
import {useRouter} from "expo-router";
|
import {useRouter} from "expo-router";
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {useMutation} from "react-query";
|
import {useMutation} from "@tanstack/react-query";
|
||||||
import auth from "@react-native-firebase/auth";
|
import auth from "@react-native-firebase/auth";
|
||||||
import {ProfileType, useAuthContext} from "@/contexts/AuthContext";
|
import {ProfileType, useAuthContext} from "@/contexts/AuthContext";
|
||||||
import {useSetUserData} from "./useSetUserData";
|
import {useSetUserData} from "./useSetUserData";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {useMutation, useQueryClient} from "react-query";
|
import {useMutation, useQueryClient} from "@tanstack/react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
import {EventData} from "@/hooks/firebase/types/eventData";
|
import {EventData} from "@/hooks/firebase/types/eventData";
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useAuthContext } from "@/contexts/AuthContext";
|
import { useAuthContext } from "@/contexts/AuthContext";
|
||||||
import { useMutation, useQueryClient } from "react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
import { IFeedback } from "@/contexts/FeedbackContext";
|
import { IFeedback } from "@/contexts/FeedbackContext";
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {useMutation, useQueryClient} from "react-query";
|
import {useMutation, useQueryClient} from "@tanstack/react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
import {IGrocery} from "@/hooks/firebase/types/groceryData";
|
import {IGrocery} from "@/hooks/firebase/types/groceryData";
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
import { useMutation, useQueryClient } from "react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
export const useUpdateHouseholdName = () => {
|
export const useUpdateHouseholdName = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useAuthContext } from "@/contexts/AuthContext";
|
import { useAuthContext } from "@/contexts/AuthContext";
|
||||||
import { useMutation, useQueryClient } from "react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
import { IBrainDump } from "@/contexts/DumpContext";
|
import { IBrainDump } from "@/contexts/DumpContext";
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {useMutation, useQueryClient} from "react-query";
|
import {useMutation, useQueryClient} from "@tanstack/react-query";
|
||||||
import {UserProfile} from "@/hooks/firebase/types/profileTypes";
|
import {UserProfile} from "@/hooks/firebase/types/profileTypes";
|
||||||
import {ProfileType, useAuthContext} from "@/contexts/AuthContext";
|
import {ProfileType, useAuthContext} from "@/contexts/AuthContext";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useMutation, useQueryClient } from "react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
import {IToDo, REPEAT_TYPE} from "@/hooks/firebase/types/todoData";
|
import {IToDo, REPEAT_TYPE} from "@/hooks/firebase/types/todoData";
|
||||||
import {addDays, addMonths, addWeeks, addYears, compareAsc, format, subDays} from "date-fns";
|
import {addDays, addMonths, addWeeks, addYears, compareAsc, format, subDays} from "date-fns";
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
import {FirebaseAuthTypes} from "@react-native-firebase/auth";
|
import {FirebaseAuthTypes} from "@react-native-firebase/auth";
|
||||||
import {useMutation, useQueryClient} from "react-query";
|
import {useMutation, useQueryClient} from "@tanstack/react-query";
|
||||||
import {useAuthContext} from "@/contexts/AuthContext";
|
import {useAuthContext} from "@/contexts/AuthContext";
|
||||||
import {UserProfile} from "@/hooks/firebase/types/profileTypes";
|
import {UserProfile} from "@/hooks/firebase/types/profileTypes";
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ import * as Google from "expo-auth-session/providers/google";
|
|||||||
import * as AuthSession from "expo-auth-session";
|
import * as AuthSession from "expo-auth-session";
|
||||||
import * as AppleAuthentication from "expo-apple-authentication";
|
import * as AppleAuthentication from "expo-apple-authentication";
|
||||||
import * as Notifications from 'expo-notifications';
|
import * as Notifications from 'expo-notifications';
|
||||||
import {useQueryClient} from "react-query";
|
import {useQueryClient} from "@tanstack/react-query";
|
||||||
import {AppleAccount, GoogleAccount, MicrosoftAccount} from "@/hooks/firebase/types/profileTypes";
|
import {AppleAccount, GoogleAccount, MicrosoftAccount} from "@/hooks/firebase/types/profileTypes";
|
||||||
|
|
||||||
const googleConfig = {
|
const googleConfig = {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {useMutation, useQueryClient} from "react-query";
|
import {useMutation, useQueryClient} from "@tanstack/react-query";
|
||||||
import {useAuthContext} from "@/contexts/AuthContext";
|
import {useAuthContext} from "@/contexts/AuthContext";
|
||||||
import {useCreateEventsFromProvider} from "@/hooks/firebase/useCreateEvent";
|
import {useCreateEventsFromProvider} from "@/hooks/firebase/useCreateEvent";
|
||||||
import {fetchiPhoneCalendarEvents} from "@/calendar-integration/apple-calendar-utils";
|
import {fetchiPhoneCalendarEvents} from "@/calendar-integration/apple-calendar-utils";
|
||||||
@ -23,7 +23,6 @@ export const useFetchAndSaveAppleEvents = () => {
|
|||||||
timeMax
|
timeMax
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(response);
|
|
||||||
const items = response ?? [];
|
const items = response ?? [];
|
||||||
await createEventsFromProvider(items);
|
await createEventsFromProvider(items);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {useMutation, useQueryClient} from "react-query";
|
import {useMutation, useQueryClient} from "@tanstack/react-query";
|
||||||
import {useAuthContext} from "@/contexts/AuthContext";
|
import {useAuthContext} from "@/contexts/AuthContext";
|
||||||
import functions from "@react-native-firebase/functions";
|
import functions from "@react-native-firebase/functions";
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useMutation, useQueryClient } from "react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useAuthContext } from "@/contexts/AuthContext";
|
import { useAuthContext } from "@/contexts/AuthContext";
|
||||||
import { useSetUserData } from "@/hooks/firebase/useSetUserData";
|
import { useSetUserData } from "@/hooks/firebase/useSetUserData";
|
||||||
import functions from '@react-native-firebase/functions';
|
import functions from '@react-native-firebase/functions';
|
||||||
|
@ -333,6 +333,7 @@
|
|||||||
"${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle",
|
"${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle",
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/PromisesSwift/Promises_Privacy.bundle",
|
"${PODS_CONFIGURATION_BUILD_DIR}/PromisesSwift/Promises_Privacy.bundle",
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/RCT-Folly/RCT-Folly_privacy.bundle",
|
"${PODS_CONFIGURATION_BUILD_DIR}/RCT-Folly/RCT-Folly_privacy.bundle",
|
||||||
|
"${PODS_CONFIGURATION_BUILD_DIR}/RNCAsyncStorage/RNCAsyncStorage_resources.bundle",
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/ReachabilitySwift.bundle",
|
"${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/ReachabilitySwift.bundle",
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/React-Core_privacy.bundle",
|
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/React-Core_privacy.bundle",
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact/React-cxxreact_privacy.bundle",
|
"${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact/React-cxxreact_privacy.bundle",
|
||||||
@ -375,6 +376,7 @@
|
|||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle",
|
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle",
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Promises_Privacy.bundle",
|
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Promises_Privacy.bundle",
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCT-Folly_privacy.bundle",
|
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCT-Folly_privacy.bundle",
|
||||||
|
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCAsyncStorage_resources.bundle",
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ReachabilitySwift.bundle",
|
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ReachabilitySwift.bundle",
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-Core_privacy.bundle",
|
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-Core_privacy.bundle",
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-cxxreact_privacy.bundle",
|
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-cxxreact_privacy.bundle",
|
||||||
|
@ -47,7 +47,7 @@
|
|||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>74</string>
|
<string>100</string>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
@ -29,6 +29,8 @@
|
|||||||
</constraints>
|
</constraints>
|
||||||
<color key="backgroundColor" name="SplashScreenBackground"/>
|
<color key="backgroundColor" name="SplashScreenBackground"/>
|
||||||
<color key="backgroundColor" name="SplashScreenBackground"/>
|
<color key="backgroundColor" name="SplashScreenBackground"/>
|
||||||
|
<color key="backgroundColor" name="SplashScreenBackground"/>
|
||||||
|
<color key="backgroundColor" name="SplashScreenBackground"/>
|
||||||
</view>
|
</view>
|
||||||
</viewController>
|
</viewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="EXPO-PLACEHOLDER-1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="EXPO-PLACEHOLDER-1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
@ -44,5 +46,11 @@
|
|||||||
<namedColor name="SplashScreenBackground">
|
<namedColor name="SplashScreenBackground">
|
||||||
<color alpha="1.000" blue="1.00000000000000" green="1.00000000000000" red="1.00000000000000" customColorSpace="sRGB" colorSpace="custom"/>
|
<color alpha="1.000" blue="1.00000000000000" green="1.00000000000000" red="1.00000000000000" customColorSpace="sRGB" colorSpace="custom"/>
|
||||||
</namedColor>
|
</namedColor>
|
||||||
|
<namedColor name="SplashScreenBackground">
|
||||||
|
<color alpha="1.000" blue="1.00000000000000" green="1.00000000000000" red="1.00000000000000" customColorSpace="sRGB" colorSpace="custom"/>
|
||||||
|
</namedColor>
|
||||||
|
<namedColor name="SplashScreenBackground">
|
||||||
|
<color alpha="1.000" blue="1.00000000000000" green="1.00000000000000" red="1.00000000000000" customColorSpace="sRGB" colorSpace="custom"/>
|
||||||
|
</namedColor>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
@ -1403,6 +1403,8 @@ PODS:
|
|||||||
- ExpoModulesCore
|
- ExpoModulesCore
|
||||||
- ExpoFont (13.0.1):
|
- ExpoFont (13.0.1):
|
||||||
- ExpoModulesCore
|
- ExpoModulesCore
|
||||||
|
- ExpoHaptics (14.0.0):
|
||||||
|
- ExpoModulesCore
|
||||||
- ExpoHead (4.0.11):
|
- ExpoHead (4.0.11):
|
||||||
- ExpoModulesCore
|
- ExpoModulesCore
|
||||||
- ExpoImagePicker (16.0.3):
|
- ExpoImagePicker (16.0.3):
|
||||||
@ -3263,6 +3265,8 @@ PODS:
|
|||||||
- ReactNativeUiLib (4.3.2):
|
- ReactNativeUiLib (4.3.2):
|
||||||
- React
|
- React
|
||||||
- RecaptchaInterop (100.0.0)
|
- RecaptchaInterop (100.0.0)
|
||||||
|
- RNCAsyncStorage (2.1.0):
|
||||||
|
- React-Core
|
||||||
- RNDateTimePicker (8.2.0):
|
- RNDateTimePicker (8.2.0):
|
||||||
- React-Core
|
- React-Core
|
||||||
- RNFBApp (21.6.1):
|
- RNFBApp (21.6.1):
|
||||||
@ -3454,6 +3458,7 @@ DEPENDENCIES:
|
|||||||
- ExpoDevice (from `../node_modules/expo-device/ios`)
|
- ExpoDevice (from `../node_modules/expo-device/ios`)
|
||||||
- ExpoFileSystem (from `../node_modules/expo-file-system/ios`)
|
- ExpoFileSystem (from `../node_modules/expo-file-system/ios`)
|
||||||
- ExpoFont (from `../node_modules/expo-font/ios`)
|
- ExpoFont (from `../node_modules/expo-font/ios`)
|
||||||
|
- ExpoHaptics (from `../node_modules/expo-haptics/ios`)
|
||||||
- ExpoHead (from `../node_modules/expo-router/ios`)
|
- ExpoHead (from `../node_modules/expo-router/ios`)
|
||||||
- ExpoImagePicker (from `../node_modules/expo-image-picker/ios`)
|
- ExpoImagePicker (from `../node_modules/expo-image-picker/ios`)
|
||||||
- ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`)
|
- ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`)
|
||||||
@ -3536,6 +3541,7 @@ DEPENDENCIES:
|
|||||||
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
|
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
|
||||||
- ReactNativeKeyboardManager (from `../node_modules/react-native-keyboard-manager`)
|
- ReactNativeKeyboardManager (from `../node_modules/react-native-keyboard-manager`)
|
||||||
- ReactNativeUiLib (from `../node_modules/react-native-ui-lib`)
|
- ReactNativeUiLib (from `../node_modules/react-native-ui-lib`)
|
||||||
|
- "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)"
|
||||||
- "RNDateTimePicker (from `../node_modules/@react-native-community/datetimepicker`)"
|
- "RNDateTimePicker (from `../node_modules/@react-native-community/datetimepicker`)"
|
||||||
- "RNFBApp (from `../node_modules/@react-native-firebase/app`)"
|
- "RNFBApp (from `../node_modules/@react-native-firebase/app`)"
|
||||||
- "RNFBAuth (from `../node_modules/@react-native-firebase/auth`)"
|
- "RNFBAuth (from `../node_modules/@react-native-firebase/auth`)"
|
||||||
@ -3632,6 +3638,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: "../node_modules/expo-file-system/ios"
|
:path: "../node_modules/expo-file-system/ios"
|
||||||
ExpoFont:
|
ExpoFont:
|
||||||
:path: "../node_modules/expo-font/ios"
|
:path: "../node_modules/expo-font/ios"
|
||||||
|
ExpoHaptics:
|
||||||
|
:path: "../node_modules/expo-haptics/ios"
|
||||||
ExpoHead:
|
ExpoHead:
|
||||||
:path: "../node_modules/expo-router/ios"
|
:path: "../node_modules/expo-router/ios"
|
||||||
ExpoImagePicker:
|
ExpoImagePicker:
|
||||||
@ -3794,6 +3802,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: "../node_modules/react-native-keyboard-manager"
|
:path: "../node_modules/react-native-keyboard-manager"
|
||||||
ReactNativeUiLib:
|
ReactNativeUiLib:
|
||||||
:path: "../node_modules/react-native-ui-lib"
|
:path: "../node_modules/react-native-ui-lib"
|
||||||
|
RNCAsyncStorage:
|
||||||
|
:path: "../node_modules/@react-native-async-storage/async-storage"
|
||||||
RNDateTimePicker:
|
RNDateTimePicker:
|
||||||
:path: "../node_modules/@react-native-community/datetimepicker"
|
:path: "../node_modules/@react-native-community/datetimepicker"
|
||||||
RNFBApp:
|
RNFBApp:
|
||||||
@ -3851,6 +3861,7 @@ SPEC CHECKSUMS:
|
|||||||
ExpoDevice: e24dd19a43065182eb246bc0e7f84186862f1e83
|
ExpoDevice: e24dd19a43065182eb246bc0e7f84186862f1e83
|
||||||
ExpoFileSystem: dc2679a2b5d4c465ca881129074da95faee943d5
|
ExpoFileSystem: dc2679a2b5d4c465ca881129074da95faee943d5
|
||||||
ExpoFont: 7522d869d84ee2ee8093ee997fef5b86f85d856b
|
ExpoFont: 7522d869d84ee2ee8093ee997fef5b86f85d856b
|
||||||
|
ExpoHaptics: e636188d1d5f7ccb79f3c1bfab47aaf5a1768c73
|
||||||
ExpoHead: c2695f9e8d685d6fa76e7a42006b0b3420cf2d50
|
ExpoHead: c2695f9e8d685d6fa76e7a42006b0b3420cf2d50
|
||||||
ExpoImagePicker: 440a560c9bfc63edae854d08b9f12f75bd11c112
|
ExpoImagePicker: 440a560c9bfc63edae854d08b9f12f75bd11c112
|
||||||
ExpoKeepAwake: 783e68647b969b210a786047c3daa7b753dcac1f
|
ExpoKeepAwake: 783e68647b969b210a786047c3daa7b753dcac1f
|
||||||
@ -3959,6 +3970,7 @@ SPEC CHECKSUMS:
|
|||||||
ReactNativeKeyboardManager: 704d89bde3cb1e0f432bc273a44eec96eab9d90f
|
ReactNativeKeyboardManager: 704d89bde3cb1e0f432bc273a44eec96eab9d90f
|
||||||
ReactNativeUiLib: acf5e70a64140d6f38a969978f090c16862c2d3d
|
ReactNativeUiLib: acf5e70a64140d6f38a969978f090c16862c2d3d
|
||||||
RecaptchaInterop: 7d1a4a01a6b2cb1610a47ef3f85f0c411434cb21
|
RecaptchaInterop: 7d1a4a01a6b2cb1610a47ef3f85f0c411434cb21
|
||||||
|
RNCAsyncStorage: cc6479c4acd84cc7004946946c8afe30b018184d
|
||||||
RNDateTimePicker: 40ffda97d071a98a10fdca4fa97e3977102ccd14
|
RNDateTimePicker: 40ffda97d071a98a10fdca4fa97e3977102ccd14
|
||||||
RNFBApp: a3c9c3b23aa2d77001ce62b5a997325927593c12
|
RNFBApp: a3c9c3b23aa2d77001ce62b5a997325927593c12
|
||||||
RNFBAuth: 15a007eecd6ba7b9e52b45e2b41cdf6fe9f13968
|
RNFBAuth: 15a007eecd6ba7b9e52b45e2b41cdf6fe9f13968
|
||||||
|
@ -35,6 +35,8 @@
|
|||||||
"@expo-google-fonts/plus-jakarta-sans": "^0.2.3",
|
"@expo-google-fonts/plus-jakarta-sans": "^0.2.3",
|
||||||
"@expo-google-fonts/poppins": "^0.2.3",
|
"@expo-google-fonts/poppins": "^0.2.3",
|
||||||
"@expo/vector-icons": "^14.0.2",
|
"@expo/vector-icons": "^14.0.2",
|
||||||
|
"@howljs/calendar-kit": "^2.2.1",
|
||||||
|
"@react-native-async-storage/async-storage": "^2.1.0",
|
||||||
"@react-native-community/blur": "^4.4.0",
|
"@react-native-community/blur": "^4.4.0",
|
||||||
"@react-native-community/datetimepicker": "8.2.0",
|
"@react-native-community/datetimepicker": "8.2.0",
|
||||||
"@react-native-firebase/app": "^21.6.1",
|
"@react-native-firebase/app": "^21.6.1",
|
||||||
@ -47,6 +49,9 @@
|
|||||||
"@react-native/assets-registry": "^0.76.3",
|
"@react-native/assets-registry": "^0.76.3",
|
||||||
"@react-navigation/drawer": "^7.0.0",
|
"@react-navigation/drawer": "^7.0.0",
|
||||||
"@react-navigation/native": "^7.0.0",
|
"@react-navigation/native": "^7.0.0",
|
||||||
|
"@tanstack/query-async-storage-persister": "^5.62.7",
|
||||||
|
"@tanstack/react-query": "^5.62.7",
|
||||||
|
"@tanstack/react-query-persist-client": "^5.62.7",
|
||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
"debounce": "^2.1.1",
|
"debounce": "^2.1.1",
|
||||||
"expo": "^52.0.17",
|
"expo": "^52.0.17",
|
||||||
@ -61,6 +66,7 @@
|
|||||||
"expo-dev-client": "~5.0.5",
|
"expo-dev-client": "~5.0.5",
|
||||||
"expo-device": "~7.0.1",
|
"expo-device": "~7.0.1",
|
||||||
"expo-font": "~13.0.1",
|
"expo-font": "~13.0.1",
|
||||||
|
"expo-haptics": "~14.0.0",
|
||||||
"expo-image-picker": "~16.0.3",
|
"expo-image-picker": "~16.0.3",
|
||||||
"expo-linking": "~7.0.3",
|
"expo-linking": "~7.0.3",
|
||||||
"expo-localization": "~16.0.0",
|
"expo-localization": "~16.0.0",
|
||||||
@ -98,7 +104,6 @@
|
|||||||
"react-native-toast-message": "^2.2.1",
|
"react-native-toast-message": "^2.2.1",
|
||||||
"react-native-ui-lib": "^7.27.0",
|
"react-native-ui-lib": "^7.27.0",
|
||||||
"react-native-web": "~0.19.10",
|
"react-native-web": "~0.19.10",
|
||||||
"react-query": "^3.39.3",
|
|
||||||
"timezonecomplete": "^5.13.1",
|
"timezonecomplete": "^5.13.1",
|
||||||
"tzdata": "^1.0.42"
|
"tzdata": "^1.0.42"
|
||||||
},
|
},
|
||||||
@ -110,6 +115,7 @@
|
|||||||
"@types/react-native-onboarding-swiper": "^1.1.9",
|
"@types/react-native-onboarding-swiper": "^1.1.9",
|
||||||
"@types/react-test-renderer": "^18.0.7",
|
"@types/react-test-renderer": "^18.0.7",
|
||||||
"babel-plugin-module-resolver": "^5.0.2",
|
"babel-plugin-module-resolver": "^5.0.2",
|
||||||
|
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||||
"jest": "^29.2.1",
|
"jest": "^29.2.1",
|
||||||
"jest-expo": "~52.0.2",
|
"jest-expo": "~52.0.2",
|
||||||
"postinstall-postinstall": "^2.1.0",
|
"postinstall-postinstall": "^2.1.0",
|
||||||
|
184
yarn.lock
184
yarn.lock
@ -845,7 +845,7 @@
|
|||||||
pirates "^4.0.6"
|
pirates "^4.0.6"
|
||||||
source-map-support "^0.5.16"
|
source-map-support "^0.5.16"
|
||||||
|
|
||||||
"@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.0", "@babel/runtime@^7.21.0", "@babel/runtime@^7.23.8", "@babel/runtime@^7.25.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4":
|
"@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.0", "@babel/runtime@^7.21.0", "@babel/runtime@^7.25.0", "@babel/runtime@^7.8.4":
|
||||||
version "7.26.0"
|
version "7.26.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1"
|
||||||
integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==
|
integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==
|
||||||
@ -2133,6 +2133,17 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@hapi/hoek" "^9.0.0"
|
"@hapi/hoek" "^9.0.0"
|
||||||
|
|
||||||
|
"@howljs/calendar-kit@^2.2.1":
|
||||||
|
version "2.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@howljs/calendar-kit/-/calendar-kit-2.2.1.tgz#125a3984e04a8b2f4f889c2be859fc88f779fbc5"
|
||||||
|
integrity sha512-3ZgPS46/14pfZ/gBbMsdrSpCy/jTfVpgeHcvOHW7itflUnFBOSCRhGc4cWRd27vD9gIAu63PdWngQ34p/5EwFw==
|
||||||
|
dependencies:
|
||||||
|
lodash.debounce "^4.0.8"
|
||||||
|
lodash.isequal "^4.5.0"
|
||||||
|
lodash.merge "^4.6.2"
|
||||||
|
luxon "^3.4.4"
|
||||||
|
rrule "^2.8.1"
|
||||||
|
|
||||||
"@ide/backoff@^1.0.0":
|
"@ide/backoff@^1.0.0":
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@ide/backoff/-/backoff-1.0.0.tgz#466842c25bd4a4833e0642fab41ccff064010176"
|
resolved "https://registry.yarnpkg.com/@ide/backoff/-/backoff-1.0.0.tgz#466842c25bd4a4833e0642fab41ccff064010176"
|
||||||
@ -2541,6 +2552,13 @@
|
|||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
"@radix-ui/react-compose-refs" "1.0.0"
|
"@radix-ui/react-compose-refs" "1.0.0"
|
||||||
|
|
||||||
|
"@react-native-async-storage/async-storage@^2.1.0":
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-2.1.0.tgz#84ca82af320c16d3d8e617508ea523fe786b6781"
|
||||||
|
integrity sha512-eAGQGPTAuFNEoIQSB5j2Jh1zm5NPyBRTfjRMfCN0W1OakC5WIB5vsDyIQhUweKN9XOE2/V07lqTMGsL0dGXNkA==
|
||||||
|
dependencies:
|
||||||
|
merge-options "^3.0.4"
|
||||||
|
|
||||||
"@react-native-community/blur@^4.4.0":
|
"@react-native-community/blur@^4.4.0":
|
||||||
version "4.4.1"
|
version "4.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/@react-native-community/blur/-/blur-4.4.1.tgz#72cbc0be5a84022c33091683ec6888925ebcca6e"
|
resolved "https://registry.yarnpkg.com/@react-native-community/blur/-/blur-4.4.1.tgz#72cbc0be5a84022c33091683ec6888925ebcca6e"
|
||||||
@ -3354,6 +3372,39 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@sinonjs/commons" "^3.0.0"
|
"@sinonjs/commons" "^3.0.0"
|
||||||
|
|
||||||
|
"@tanstack/query-async-storage-persister@^5.62.7":
|
||||||
|
version "5.62.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@tanstack/query-async-storage-persister/-/query-async-storage-persister-5.62.7.tgz#9d076294d8c231c61dccf333151443893246dd93"
|
||||||
|
integrity sha512-8qSJ1oTnGhCikPADWd35xrswyYtmkqYnakWgqeXjxL+F+qPGgsfexNUlBu9TNqo9eAP/1ia4Lt5Ks2fTsMzBgg==
|
||||||
|
dependencies:
|
||||||
|
"@tanstack/query-persist-client-core" "5.62.7"
|
||||||
|
|
||||||
|
"@tanstack/query-core@5.62.7":
|
||||||
|
version "5.62.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.62.7.tgz#c7f6d0131c08cd2f60e73ec6e7b70e2e9e335def"
|
||||||
|
integrity sha512-fgpfmwatsrUal6V+8EC2cxZIQVl9xvL7qYa03gsdsCy985UTUlS4N+/3hCzwR0PclYDqisca2AqR1BVgJGpUDA==
|
||||||
|
|
||||||
|
"@tanstack/query-persist-client-core@5.62.7":
|
||||||
|
version "5.62.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@tanstack/query-persist-client-core/-/query-persist-client-core-5.62.7.tgz#3c03c46fe15db1098cb07b7c9867071c3d336c84"
|
||||||
|
integrity sha512-9HcaD9rEp2nGWnrw2osK5UCSKJbJKEdn+MEhVVfnUPSFN7MZFpFZxpRCHJi3fRpWOYsVeH9EFODX+aoJaniJMA==
|
||||||
|
dependencies:
|
||||||
|
"@tanstack/query-core" "5.62.7"
|
||||||
|
|
||||||
|
"@tanstack/react-query-persist-client@^5.62.7":
|
||||||
|
version "5.62.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@tanstack/react-query-persist-client/-/react-query-persist-client-5.62.7.tgz#74f66da2c0c077df17709644cbaa517ffe402540"
|
||||||
|
integrity sha512-RmEJ3YvsK7lv1Of3CCXBgHDtoZVwHMtTKCTegZz+xijVJsgJaNNfel4YTpbQ0ydnWT2IcohdqnHUtBE6p1KCIA==
|
||||||
|
dependencies:
|
||||||
|
"@tanstack/query-persist-client-core" "5.62.7"
|
||||||
|
|
||||||
|
"@tanstack/react-query@^5.62.7":
|
||||||
|
version "5.62.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.62.7.tgz#8f253439a38ad6ce820bc6d42d89ca2556574d1a"
|
||||||
|
integrity sha512-+xCtP4UAFDTlRTYyEjLx0sRtWyr5GIk7TZjZwBu4YaNahi3Rt2oMyRqfpfVrtwsqY2sayP4iXVCwmC+ZqqFmuw==
|
||||||
|
dependencies:
|
||||||
|
"@tanstack/query-core" "5.62.7"
|
||||||
|
|
||||||
"@tootallnate/once@2":
|
"@tootallnate/once@2":
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
|
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
|
||||||
@ -4170,6 +4221,11 @@ babel-plugin-transform-inline-environment-variables@^0.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-inline-environment-variables/-/babel-plugin-transform-inline-environment-variables-0.0.2.tgz#1372475c6c87f3c4ce6046587730ee041bc784b9"
|
resolved "https://registry.yarnpkg.com/babel-plugin-transform-inline-environment-variables/-/babel-plugin-transform-inline-environment-variables-0.0.2.tgz#1372475c6c87f3c4ce6046587730ee041bc784b9"
|
||||||
integrity sha512-8gobU7uuTIjz62aXTEZOH5yhuIPojNVAgLK0xnepdGS19aqOEphy7FVWBsojPa14yrQGM/w63uDox4thYcHCnA==
|
integrity sha512-8gobU7uuTIjz62aXTEZOH5yhuIPojNVAgLK0xnepdGS19aqOEphy7FVWBsojPa14yrQGM/w63uDox4thYcHCnA==
|
||||||
|
|
||||||
|
babel-plugin-transform-remove-console@^6.9.4:
|
||||||
|
version "6.9.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/babel-plugin-transform-remove-console/-/babel-plugin-transform-remove-console-6.9.4.tgz#b980360c067384e24b357a588d807d3c83527780"
|
||||||
|
integrity sha512-88blrUrMX3SPiGkT1GnvVY8E/7A+k6oj3MNvUtTIxJflFzXTw1bHkuJ/y039ouhFMp2prRn5cQGzokViYi1dsg==
|
||||||
|
|
||||||
babel-preset-current-node-syntax@^1.0.0:
|
babel-preset-current-node-syntax@^1.0.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30"
|
resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30"
|
||||||
@ -4252,7 +4308,7 @@ better-opn@~3.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
open "^8.0.4"
|
open "^8.0.4"
|
||||||
|
|
||||||
big-integer@1.6.x, big-integer@^1.6.16:
|
big-integer@1.6.x:
|
||||||
version "1.6.52"
|
version "1.6.52"
|
||||||
resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85"
|
resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85"
|
||||||
integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==
|
integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==
|
||||||
@ -4359,20 +4415,6 @@ braces@^3.0.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
fill-range "^7.1.1"
|
fill-range "^7.1.1"
|
||||||
|
|
||||||
broadcast-channel@^3.4.1:
|
|
||||||
version "3.7.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-3.7.0.tgz#2dfa5c7b4289547ac3f6705f9c00af8723889937"
|
|
||||||
integrity sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==
|
|
||||||
dependencies:
|
|
||||||
"@babel/runtime" "^7.7.2"
|
|
||||||
detect-node "^2.1.0"
|
|
||||||
js-sha3 "0.8.0"
|
|
||||||
microseconds "0.2.0"
|
|
||||||
nano-time "1.0.0"
|
|
||||||
oblivious-set "1.0.0"
|
|
||||||
rimraf "3.0.2"
|
|
||||||
unload "2.2.0"
|
|
||||||
|
|
||||||
browserslist@^4.24.0, browserslist@^4.24.2:
|
browserslist@^4.24.0, browserslist@^4.24.2:
|
||||||
version "4.24.2"
|
version "4.24.2"
|
||||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.2.tgz#f5845bc91069dbd55ee89faf9822e1d885d16580"
|
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.2.tgz#f5845bc91069dbd55ee89faf9822e1d885d16580"
|
||||||
@ -5222,11 +5264,6 @@ detect-newline@^3.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
|
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
|
||||||
integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==
|
integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==
|
||||||
|
|
||||||
detect-node@^2.0.4, detect-node@^2.1.0:
|
|
||||||
version "2.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1"
|
|
||||||
integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==
|
|
||||||
|
|
||||||
diff-sequences@^29.6.3:
|
diff-sequences@^29.6.3:
|
||||||
version "29.6.3"
|
version "29.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921"
|
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921"
|
||||||
@ -5787,6 +5824,11 @@ expo-font@~13.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
fontfaceobserver "^2.1.0"
|
fontfaceobserver "^2.1.0"
|
||||||
|
|
||||||
|
expo-haptics@~14.0.0:
|
||||||
|
version "14.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/expo-haptics/-/expo-haptics-14.0.0.tgz#b3ccea2ed5c7f4c2505e2e8cbfa799091b185303"
|
||||||
|
integrity sha512-5tYJN+2axYF22BtG1elBQAV1aZPUOCtr9sItClfm4jDoekGiPCxZG/nylcA3DVh2bUHMSll4Y98qjFFFhwZ1Cw==
|
||||||
|
|
||||||
expo-image-loader@~5.0.0:
|
expo-image-loader@~5.0.0:
|
||||||
version "5.0.0"
|
version "5.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/expo-image-loader/-/expo-image-loader-5.0.0.tgz#4f58a21ab26e40d6fccc211b664fd9fe21a5dcb8"
|
resolved "https://registry.yarnpkg.com/expo-image-loader/-/expo-image-loader-5.0.0.tgz#4f58a21ab26e40d6fccc211b664fd9fe21a5dcb8"
|
||||||
@ -7197,6 +7239,11 @@ is-path-inside@^3.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
|
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
|
||||||
integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
|
integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
|
||||||
|
|
||||||
|
is-plain-obj@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287"
|
||||||
|
integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==
|
||||||
|
|
||||||
is-plain-object@^2.0.4:
|
is-plain-object@^2.0.4:
|
||||||
version "2.0.4"
|
version "2.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
|
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
|
||||||
@ -7816,11 +7863,6 @@ jotai@^2.9.1:
|
|||||||
resolved "https://registry.yarnpkg.com/jotai/-/jotai-2.10.1.tgz#8d5598d06fa295110de0914f10bd1d10ea229723"
|
resolved "https://registry.yarnpkg.com/jotai/-/jotai-2.10.1.tgz#8d5598d06fa295110de0914f10bd1d10ea229723"
|
||||||
integrity sha512-4FycO+BOTl2auLyF2Chvi6KTDqdsdDDtpaL/WHQMs8f3KS1E3loiUShQzAzFA/sMU5cJ0hz/RT1xum9YbG/zaA==
|
integrity sha512-4FycO+BOTl2auLyF2Chvi6KTDqdsdDDtpaL/WHQMs8f3KS1E3loiUShQzAzFA/sMU5cJ0hz/RT1xum9YbG/zaA==
|
||||||
|
|
||||||
js-sha3@0.8.0:
|
|
||||||
version "0.8.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840"
|
|
||||||
integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==
|
|
||||||
|
|
||||||
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||||
@ -8270,6 +8312,11 @@ lodash.isboolean@^3.0.3:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
|
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
|
||||||
integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==
|
integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==
|
||||||
|
|
||||||
|
lodash.isequal@^4.5.0:
|
||||||
|
version "4.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
||||||
|
integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
|
||||||
|
|
||||||
lodash.isinteger@^4.0.4:
|
lodash.isinteger@^4.0.4:
|
||||||
version "4.0.4"
|
version "4.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
|
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
|
||||||
@ -8290,6 +8337,11 @@ lodash.isstring@^4.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
|
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
|
||||||
integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==
|
integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==
|
||||||
|
|
||||||
|
lodash.merge@^4.6.2:
|
||||||
|
version "4.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
||||||
|
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
||||||
|
|
||||||
lodash.once@^4.0.0:
|
lodash.once@^4.0.0:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
|
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
|
||||||
@ -8368,6 +8420,11 @@ lru-memoizer@^2.2.0:
|
|||||||
lodash.clonedeep "^4.5.0"
|
lodash.clonedeep "^4.5.0"
|
||||||
lru-cache "6.0.0"
|
lru-cache "6.0.0"
|
||||||
|
|
||||||
|
luxon@^3.4.4:
|
||||||
|
version "3.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.5.0.tgz#6b6f65c5cd1d61d1fd19dbf07ee87a50bf4b8e20"
|
||||||
|
integrity sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==
|
||||||
|
|
||||||
make-dir@^2.0.0, make-dir@^2.1.0:
|
make-dir@^2.0.0, make-dir@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
|
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
|
||||||
@ -8395,14 +8452,6 @@ marky@^1.2.2:
|
|||||||
resolved "https://registry.yarnpkg.com/marky/-/marky-1.2.5.tgz#55796b688cbd72390d2d399eaaf1832c9413e3c0"
|
resolved "https://registry.yarnpkg.com/marky/-/marky-1.2.5.tgz#55796b688cbd72390d2d399eaaf1832c9413e3c0"
|
||||||
integrity sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==
|
integrity sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==
|
||||||
|
|
||||||
match-sorter@^6.0.2:
|
|
||||||
version "6.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.4.0.tgz#ae9c166cb3c9efd337690b3160c0e28cb8377c13"
|
|
||||||
integrity sha512-d4664ahzdL1QTTvmK1iI0JsrxWeJ6gn33qkYtnPg3mcn+naBLtXSgSPOe+X2vUgtgGwaAk3eiaj7gwKjjMAq+Q==
|
|
||||||
dependencies:
|
|
||||||
"@babel/runtime" "^7.23.8"
|
|
||||||
remove-accents "0.5.0"
|
|
||||||
|
|
||||||
md5-file@^3.2.3:
|
md5-file@^3.2.3:
|
||||||
version "3.2.3"
|
version "3.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/md5-file/-/md5-file-3.2.3.tgz#f9bceb941eca2214a4c0727f5e700314e770f06f"
|
resolved "https://registry.yarnpkg.com/md5-file/-/md5-file-3.2.3.tgz#f9bceb941eca2214a4c0727f5e700314e770f06f"
|
||||||
@ -8463,6 +8512,13 @@ merge-descriptors@1.0.3:
|
|||||||
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5"
|
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5"
|
||||||
integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==
|
integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==
|
||||||
|
|
||||||
|
merge-options@^3.0.4:
|
||||||
|
version "3.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/merge-options/-/merge-options-3.0.4.tgz#84709c2aa2a4b24c1981f66c179fe5565cc6dbb7"
|
||||||
|
integrity sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==
|
||||||
|
dependencies:
|
||||||
|
is-plain-obj "^2.1.0"
|
||||||
|
|
||||||
merge-stream@^2.0.0:
|
merge-stream@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
|
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
|
||||||
@ -8685,11 +8741,6 @@ micromatch@^4.0.2, micromatch@^4.0.4:
|
|||||||
braces "^3.0.3"
|
braces "^3.0.3"
|
||||||
picomatch "^2.3.1"
|
picomatch "^2.3.1"
|
||||||
|
|
||||||
microseconds@0.2.0:
|
|
||||||
version "0.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39"
|
|
||||||
integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==
|
|
||||||
|
|
||||||
mime-db@1.52.0:
|
mime-db@1.52.0:
|
||||||
version "1.52.0"
|
version "1.52.0"
|
||||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
|
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
|
||||||
@ -8850,13 +8901,6 @@ mz@^2.7.0:
|
|||||||
object-assign "^4.0.1"
|
object-assign "^4.0.1"
|
||||||
thenify-all "^1.0.0"
|
thenify-all "^1.0.0"
|
||||||
|
|
||||||
nano-time@1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef"
|
|
||||||
integrity sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==
|
|
||||||
dependencies:
|
|
||||||
big-integer "^1.6.16"
|
|
||||||
|
|
||||||
nanoid@3.3.7, nanoid@^3.3.7:
|
nanoid@3.3.7, nanoid@^3.3.7:
|
||||||
version "3.3.7"
|
version "3.3.7"
|
||||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
|
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
|
||||||
@ -9051,11 +9095,6 @@ object.assign@^4.1.4, object.assign@^4.1.5:
|
|||||||
has-symbols "^1.0.3"
|
has-symbols "^1.0.3"
|
||||||
object-keys "^1.1.1"
|
object-keys "^1.1.1"
|
||||||
|
|
||||||
oblivious-set@1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.0.0.tgz#c8316f2c2fb6ff7b11b6158db3234c49f733c566"
|
|
||||||
integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==
|
|
||||||
|
|
||||||
on-finished@2.4.1:
|
on-finished@2.4.1:
|
||||||
version "2.4.1"
|
version "2.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
|
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
|
||||||
@ -10002,15 +10041,6 @@ react-native@0.76.3:
|
|||||||
ws "^6.2.3"
|
ws "^6.2.3"
|
||||||
yargs "^17.6.2"
|
yargs "^17.6.2"
|
||||||
|
|
||||||
react-query@^3.39.3:
|
|
||||||
version "3.39.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.39.3.tgz#4cea7127c6c26bdea2de5fb63e51044330b03f35"
|
|
||||||
integrity sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==
|
|
||||||
dependencies:
|
|
||||||
"@babel/runtime" "^7.5.5"
|
|
||||||
broadcast-channel "^3.4.1"
|
|
||||||
match-sorter "^6.0.2"
|
|
||||||
|
|
||||||
react-refresh@^0.14.0, react-refresh@^0.14.2:
|
react-refresh@^0.14.0, react-refresh@^0.14.2:
|
||||||
version "0.14.2"
|
version "0.14.2"
|
||||||
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9"
|
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9"
|
||||||
@ -10166,11 +10196,6 @@ regjsparser@^0.11.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
jsesc "~3.0.2"
|
jsesc "~3.0.2"
|
||||||
|
|
||||||
remove-accents@0.5.0:
|
|
||||||
version "0.5.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.5.0.tgz#77991f37ba212afba162e375b627631315bed687"
|
|
||||||
integrity sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==
|
|
||||||
|
|
||||||
remove-trailing-slash@^0.1.0:
|
remove-trailing-slash@^0.1.0:
|
||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/remove-trailing-slash/-/remove-trailing-slash-0.1.1.tgz#be2285a59f39c74d1bce4f825950061915e3780d"
|
resolved "https://registry.yarnpkg.com/remove-trailing-slash/-/remove-trailing-slash-0.1.1.tgz#be2285a59f39c74d1bce4f825950061915e3780d"
|
||||||
@ -10293,13 +10318,6 @@ reusify@^1.0.4:
|
|||||||
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
|
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
|
||||||
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
|
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
|
||||||
|
|
||||||
rimraf@3.0.2, rimraf@^3.0.2:
|
|
||||||
version "3.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
|
|
||||||
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
|
|
||||||
dependencies:
|
|
||||||
glob "^7.1.3"
|
|
||||||
|
|
||||||
rimraf@^2.6.3:
|
rimraf@^2.6.3:
|
||||||
version "2.7.1"
|
version "2.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
|
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
|
||||||
@ -10307,6 +10325,13 @@ rimraf@^2.6.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
glob "^7.1.3"
|
glob "^7.1.3"
|
||||||
|
|
||||||
|
rimraf@^3.0.2:
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
|
||||||
|
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
|
||||||
|
dependencies:
|
||||||
|
glob "^7.1.3"
|
||||||
|
|
||||||
rimraf@~2.6.2:
|
rimraf@~2.6.2:
|
||||||
version "2.6.3"
|
version "2.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
|
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
|
||||||
@ -10314,6 +10339,13 @@ rimraf@~2.6.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
glob "^7.1.3"
|
glob "^7.1.3"
|
||||||
|
|
||||||
|
rrule@^2.8.1:
|
||||||
|
version "2.8.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/rrule/-/rrule-2.8.1.tgz#e8341a9ce3e68ce5b8da4d502e893cd9f286805e"
|
||||||
|
integrity sha512-hM3dHSBMeaJ0Ktp7W38BJZ7O1zOgaFEsn41PDk+yHoEtfLV+PoJt9E9xAlZiWgf/iqEqionN0ebHFZIDAp+iGw==
|
||||||
|
dependencies:
|
||||||
|
tslib "^2.4.0"
|
||||||
|
|
||||||
rtl-detect@^1.0.2:
|
rtl-detect@^1.0.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/rtl-detect/-/rtl-detect-1.1.2.tgz#ca7f0330af5c6bb626c15675c642ba85ad6273c6"
|
resolved "https://registry.yarnpkg.com/rtl-detect/-/rtl-detect-1.1.2.tgz#ca7f0330af5c6bb626c15675c642ba85ad6273c6"
|
||||||
@ -11501,14 +11533,6 @@ universalify@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
|
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
|
||||||
integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
|
integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
|
||||||
|
|
||||||
unload@2.2.0:
|
|
||||||
version "2.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/unload/-/unload-2.2.0.tgz#ccc88fdcad345faa06a92039ec0f80b488880ef7"
|
|
||||||
integrity sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==
|
|
||||||
dependencies:
|
|
||||||
"@babel/runtime" "^7.6.2"
|
|
||||||
detect-node "^2.0.4"
|
|
||||||
|
|
||||||
unpipe@1.0.0, unpipe@~1.0.0:
|
unpipe@1.0.0, unpipe@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
||||||
|
Reference in New Issue
Block a user