mirror of
https://github.com/urosran/cally.git
synced 2026-03-10 20:31:43 +00:00
Merge branch 'dev'
# Conflicts: # app.json # ios/cally/Info.plist # yarn.lock
This commit is contained in:
@ -1,10 +1,12 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_CALENDAR"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<queries>
|
||||
<intent>
|
||||
@ -22,7 +24,7 @@
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://u.expo.dev/bdb8c57b-25bb-4d36-b3b8-5b09c5092f52"/>
|
||||
<activity android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true" android:screenOrientation="portrait">
|
||||
<activity android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|uiMode|locale|layoutDirection" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true" android:screenOrientation="portrait">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<resources>
|
||||
<string name="app_name">Cally - Family Planner</string>
|
||||
<string name="app_name">Cally.</string>
|
||||
<string name="expo_splash_screen_resize_mode" translatable="false">contain</string>
|
||||
<string name="expo_splash_screen_status_bar_translucent" translatable="false">false</string>
|
||||
<string name="expo_system_ui_user_interface_style" translatable="false">light</string>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
rootProject.name = 'Cally - Family Planner'
|
||||
rootProject.name = 'Cally.'
|
||||
|
||||
dependencyResolutionManagement {
|
||||
versionCatalogs {
|
||||
|
||||
18
app.json
18
app.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "Cally - Family Planner",
|
||||
"name": "Cally.",
|
||||
"slug": "cally",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
@ -16,7 +16,8 @@
|
||||
"supportsTablet": true,
|
||||
"bundleIdentifier": "com.cally.app",
|
||||
"googleServicesFile": "./ios/GoogleService-Info.plist",
|
||||
"buildNumber": "28"
|
||||
"buildNumber": "31",
|
||||
"usesAppleSignIn": true
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
@ -63,7 +64,18 @@
|
||||
"defaultChannel": "default"
|
||||
}
|
||||
],
|
||||
"expo-font"
|
||||
[
|
||||
"expo-calendar",
|
||||
{
|
||||
"calendarPermission": "The app needs to access your calendar."
|
||||
}
|
||||
],
|
||||
[
|
||||
"expo-apple-authentication"
|
||||
],
|
||||
"expo-font",
|
||||
"expo-localization",
|
||||
"./plugins/withPodfile"
|
||||
],
|
||||
"experiments": {
|
||||
"typedRoutes": true
|
||||
|
||||
@ -1,14 +1,11 @@
|
||||
import React from "react";
|
||||
import { CalendarProvider } from "@/contexts/CalendarContext"; // Import the new CalendarPage component
|
||||
import CalendarPage from "@/components/pages/calendar/CalendarPage";
|
||||
import { SettingsContextProvider } from "@/contexts/SettingsContext";
|
||||
import {SettingsContextProvider} from "@/contexts/SettingsContext";
|
||||
|
||||
export default function Screen() {
|
||||
return (
|
||||
<SettingsContextProvider>
|
||||
<CalendarProvider>
|
||||
<CalendarPage />
|
||||
</CalendarProvider>
|
||||
</SettingsContextProvider>
|
||||
);
|
||||
return (
|
||||
<SettingsContextProvider>
|
||||
<CalendarPage/>
|
||||
</SettingsContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
472
app/_layout.tsx
472
app/_layout.tsx
@ -1,170 +1,6 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { DefaultTheme, ThemeProvider } from "@react-navigation/native";
|
||||
import React, {useEffect} from "react";
|
||||
import {DefaultTheme, ThemeProvider} from "@react-navigation/native";
|
||||
import {
|
||||
useFonts,
|
||||
Manrope_200ExtraLight,
|
||||
Manrope_300Light,
|
||||
Manrope_400Regular,
|
||||
Manrope_500Medium,
|
||||
Manrope_600SemiBold,
|
||||
Manrope_700Bold,
|
||||
Manrope_800ExtraBold,
|
||||
} from "@expo-google-fonts/manrope";
|
||||
import {
|
||||
PlusJakartaSans_200ExtraLight,
|
||||
PlusJakartaSans_300Light,
|
||||
PlusJakartaSans_400Regular,
|
||||
PlusJakartaSans_500Medium,
|
||||
PlusJakartaSans_600SemiBold,
|
||||
PlusJakartaSans_700Bold,
|
||||
PlusJakartaSans_800ExtraBold,
|
||||
PlusJakartaSans_200ExtraLight_Italic,
|
||||
PlusJakartaSans_300Light_Italic,
|
||||
PlusJakartaSans_400Regular_Italic,
|
||||
PlusJakartaSans_500Medium_Italic,
|
||||
PlusJakartaSans_600SemiBold_Italic,
|
||||
PlusJakartaSans_700Bold_Italic,
|
||||
PlusJakartaSans_800ExtraBold_Italic,
|
||||
} from "@expo-google-fonts/plus-jakarta-sans";
|
||||
import {
|
||||
Poppins_100Thin,
|
||||
Poppins_100Thin_Italic,
|
||||
Poppins_200ExtraLight,
|
||||
Poppins_200ExtraLight_Italic,
|
||||
Poppins_300Light,
|
||||
Poppins_300Light_Italic,
|
||||
Poppins_400Regular,
|
||||
Poppins_400Regular_Italic,
|
||||
Poppins_500Medium,
|
||||
Poppins_500Medium_Italic,
|
||||
Poppins_600SemiBold,
|
||||
Poppins_600SemiBold_Italic,
|
||||
Poppins_700Bold,
|
||||
Poppins_700Bold_Italic,
|
||||
Poppins_800ExtraBold,
|
||||
Poppins_800ExtraBold_Italic,
|
||||
Poppins_900Black,
|
||||
Poppins_900Black_Italic,
|
||||
} from "@expo-google-fonts/poppins";
|
||||
import { Stack } from "expo-router";
|
||||
import * as SplashScreen from "expo-splash-screen";
|
||||
import "react-native-reanimated";
|
||||
import { AuthContextProvider } from "@/contexts/AuthContext";
|
||||
import { QueryClient, QueryClientProvider } from "react-query";
|
||||
import {
|
||||
ThemeManager,
|
||||
Typography,
|
||||
Toast,
|
||||
TextProps,
|
||||
} from "react-native-ui-lib";
|
||||
import functions from "@react-native-firebase/functions";
|
||||
import auth from "@react-native-firebase/auth";
|
||||
import firestore from "@react-native-firebase/firestore";
|
||||
|
||||
SplashScreen.preventAutoHideAsync();
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
if (__DEV__) {
|
||||
functions().useEmulator("localhost", 5001);
|
||||
firestore().useEmulator("localhost", 5471);
|
||||
auth().useEmulator("http://localhost:9099");
|
||||
}
|
||||
|
||||
type TextStyleBase =
|
||||
| "text10"
|
||||
| "text20"
|
||||
| "text30"
|
||||
| "text40"
|
||||
| "text50"
|
||||
| "text60"
|
||||
| "text70"
|
||||
| "text80"
|
||||
| "text90"
|
||||
| "text100";
|
||||
type TextStyleModifier = "R" | "M" | "BO" | "H" | "BL" | "L";
|
||||
type TextStyle = TextStyleBase | `${TextStyleBase}${TextStyleModifier}`;
|
||||
|
||||
type TextStyleProps = {
|
||||
[K in TextStyle]?: boolean;
|
||||
};
|
||||
|
||||
type ExtendedTextProps = TextProps & TextStyleProps;
|
||||
|
||||
interface FontStyle {
|
||||
fontFamily: string;
|
||||
fontSize: number;
|
||||
}
|
||||
|
||||
const getManropeFontStyle = (style: TextStyle): FontStyle => {
|
||||
let fontFamily: string;
|
||||
let fontSize: number;
|
||||
|
||||
if (style.includes("L") || style.includes("BL"))
|
||||
fontFamily = "Manrope_300Light";
|
||||
else if (style.includes("R")) fontFamily = "Manrope_400Regular";
|
||||
else if (style.includes("M")) fontFamily = "Manrope_500Medium";
|
||||
else if (style.includes("BO") || style.includes("H"))
|
||||
fontFamily = "Manrope_700Bold";
|
||||
else {
|
||||
const baseStyle = style.slice(0, 6) as TextStyleBase;
|
||||
switch (baseStyle) {
|
||||
case "text10":
|
||||
case "text20":
|
||||
fontFamily = "Manrope_700Bold";
|
||||
break;
|
||||
case "text30":
|
||||
case "text40":
|
||||
fontFamily = "Manrope_600SemiBold";
|
||||
break;
|
||||
case "text50":
|
||||
fontFamily = "Manrope_400Regular";
|
||||
break;
|
||||
default:
|
||||
fontFamily = "Manrope_300Light";
|
||||
}
|
||||
}
|
||||
|
||||
switch (style.slice(0, 6) as TextStyleBase) {
|
||||
case "text10":
|
||||
fontSize = 64;
|
||||
break;
|
||||
case "text20":
|
||||
fontSize = 50;
|
||||
break;
|
||||
case "text30":
|
||||
fontSize = 36;
|
||||
break;
|
||||
case "text40":
|
||||
fontSize = 28;
|
||||
break;
|
||||
case "text50":
|
||||
fontSize = 24;
|
||||
break;
|
||||
case "text60":
|
||||
fontSize = 20;
|
||||
break;
|
||||
case "text70":
|
||||
fontSize = 16;
|
||||
break;
|
||||
case "text80":
|
||||
fontSize = 14;
|
||||
break;
|
||||
case "text90":
|
||||
fontSize = 12;
|
||||
break;
|
||||
case "text100":
|
||||
fontSize = 10;
|
||||
break;
|
||||
default:
|
||||
fontSize = 16;
|
||||
}
|
||||
|
||||
return { fontFamily, fontSize };
|
||||
};
|
||||
|
||||
export default function RootLayout() {
|
||||
const [loaded] = useFonts({
|
||||
Manrope_200ExtraLight,
|
||||
Manrope_300Light,
|
||||
Manrope_400Regular,
|
||||
@ -172,20 +8,25 @@ export default function RootLayout() {
|
||||
Manrope_600SemiBold,
|
||||
Manrope_700Bold,
|
||||
Manrope_800ExtraBold,
|
||||
useFonts,
|
||||
} from "@expo-google-fonts/manrope";
|
||||
import {
|
||||
PlusJakartaSans_200ExtraLight,
|
||||
PlusJakartaSans_300Light,
|
||||
PlusJakartaSans_400Regular,
|
||||
PlusJakartaSans_500Medium,
|
||||
PlusJakartaSans_600SemiBold,
|
||||
PlusJakartaSans_700Bold,
|
||||
PlusJakartaSans_800ExtraBold,
|
||||
PlusJakartaSans_200ExtraLight_Italic,
|
||||
PlusJakartaSans_300Light,
|
||||
PlusJakartaSans_300Light_Italic,
|
||||
PlusJakartaSans_400Regular,
|
||||
PlusJakartaSans_400Regular_Italic,
|
||||
PlusJakartaSans_500Medium,
|
||||
PlusJakartaSans_500Medium_Italic,
|
||||
PlusJakartaSans_600SemiBold,
|
||||
PlusJakartaSans_600SemiBold_Italic,
|
||||
PlusJakartaSans_700Bold,
|
||||
PlusJakartaSans_700Bold_Italic,
|
||||
PlusJakartaSans_800ExtraBold,
|
||||
PlusJakartaSans_800ExtraBold_Italic,
|
||||
} from "@expo-google-fonts/plus-jakarta-sans";
|
||||
import {
|
||||
Poppins_100Thin,
|
||||
Poppins_100Thin_Italic,
|
||||
Poppins_200ExtraLight,
|
||||
@ -204,72 +45,231 @@ export default function RootLayout() {
|
||||
Poppins_800ExtraBold_Italic,
|
||||
Poppins_900Black,
|
||||
Poppins_900Black_Italic,
|
||||
});
|
||||
} from "@expo-google-fonts/poppins";
|
||||
import {Stack} from "expo-router";
|
||||
import * as SplashScreen from "expo-splash-screen";
|
||||
import "react-native-reanimated";
|
||||
import {AuthContextProvider} from "@/contexts/AuthContext";
|
||||
import {QueryClient, QueryClientProvider} from "react-query";
|
||||
import {TextProps, ThemeManager, Toast, Typography,} from "react-native-ui-lib";
|
||||
import {Platform} from 'react-native';
|
||||
import KeyboardManager from 'react-native-keyboard-manager';
|
||||
|
||||
useEffect(() => {
|
||||
if (loaded) {
|
||||
SplashScreen.hideAsync();
|
||||
SplashScreen.preventAutoHideAsync();
|
||||
|
||||
const typographies: Partial<Record<TextStyle, FontStyle>> = {};
|
||||
(
|
||||
[
|
||||
"text10",
|
||||
"text20",
|
||||
"text30",
|
||||
"text40",
|
||||
"text50",
|
||||
"text60",
|
||||
"text70",
|
||||
"text80",
|
||||
"text90",
|
||||
"text100",
|
||||
] as const
|
||||
).forEach((baseStyle) => {
|
||||
typographies[baseStyle] = getManropeFontStyle(baseStyle);
|
||||
(["R", "M", "BO", "H", "BL", "L"] as const).forEach((modifier) => {
|
||||
const style = `${baseStyle}${modifier}` as TextStyle;
|
||||
typographies[style] = getManropeFontStyle(style);
|
||||
});
|
||||
});
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
Typography.loadTypographies(typographies);
|
||||
|
||||
ThemeManager.setComponentTheme(
|
||||
"Text",
|
||||
(props: ExtendedTextProps, context: unknown) => {
|
||||
const textStyle = (
|
||||
Object.keys(props) as Array<keyof ExtendedTextProps>
|
||||
).find((key) => typographies[key as TextStyle]) as
|
||||
| TextStyle
|
||||
| undefined;
|
||||
|
||||
return {
|
||||
style: [
|
||||
Typography.text50,
|
||||
textStyle ? typographies[textStyle] : undefined,
|
||||
],
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
}, [loaded]);
|
||||
|
||||
if (!loaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AuthContextProvider>
|
||||
<ThemeProvider value={DefaultTheme}>
|
||||
<Stack>
|
||||
<Stack.Screen name="(auth)" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="(unauth)" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="+not-found" />
|
||||
</Stack>
|
||||
<Toast />
|
||||
</ThemeProvider>
|
||||
</AuthContextProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
if (Platform.OS === 'ios') {
|
||||
KeyboardManager.setEnable(true);
|
||||
KeyboardManager.setToolbarPreviousNextButtonEnable(true);
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
// functions().useEmulator("localhost", 5001);
|
||||
// firestore().useEmulator("localhost", 5471);
|
||||
// auth().useEmulator("http://localhost:9099");
|
||||
}
|
||||
|
||||
type TextStyleBase =
|
||||
| "text10"
|
||||
| "text20"
|
||||
| "text30"
|
||||
| "text40"
|
||||
| "text50"
|
||||
| "text60"
|
||||
| "text70"
|
||||
| "text80"
|
||||
| "text90"
|
||||
| "text100";
|
||||
type TextStyleModifier = "R" | "M" | "BO" | "H" | "BL" | "L";
|
||||
type TextStyle = TextStyleBase | `${TextStyleBase}${TextStyleModifier}`;
|
||||
|
||||
type TextStyleProps = {
|
||||
[K in TextStyle]?: boolean;
|
||||
};
|
||||
|
||||
type ExtendedTextProps = TextProps & TextStyleProps;
|
||||
|
||||
interface FontStyle {
|
||||
fontFamily: string;
|
||||
fontSize: number;
|
||||
}
|
||||
|
||||
const getManropeFontStyle = (style: TextStyle): FontStyle => {
|
||||
let fontFamily: string;
|
||||
let fontSize: number;
|
||||
|
||||
if (style.includes("L") || style.includes("BL"))
|
||||
fontFamily = "Manrope_300Light";
|
||||
else if (style.includes("R")) fontFamily = "Manrope_400Regular";
|
||||
else if (style.includes("M")) fontFamily = "Manrope_500Medium";
|
||||
else if (style.includes("BO") || style.includes("H"))
|
||||
fontFamily = "Manrope_700Bold";
|
||||
else {
|
||||
const baseStyle = style.slice(0, 6) as TextStyleBase;
|
||||
switch (baseStyle) {
|
||||
case "text10":
|
||||
case "text20":
|
||||
fontFamily = "Manrope_700Bold";
|
||||
break;
|
||||
case "text30":
|
||||
case "text40":
|
||||
fontFamily = "Manrope_600SemiBold";
|
||||
break;
|
||||
case "text50":
|
||||
fontFamily = "Manrope_400Regular";
|
||||
break;
|
||||
default:
|
||||
fontFamily = "Manrope_300Light";
|
||||
}
|
||||
}
|
||||
|
||||
switch (style.slice(0, 6) as TextStyleBase) {
|
||||
case "text10":
|
||||
fontSize = 64;
|
||||
break;
|
||||
case "text20":
|
||||
fontSize = 50;
|
||||
break;
|
||||
case "text30":
|
||||
fontSize = 36;
|
||||
break;
|
||||
case "text40":
|
||||
fontSize = 28;
|
||||
break;
|
||||
case "text50":
|
||||
fontSize = 24;
|
||||
break;
|
||||
case "text60":
|
||||
fontSize = 20;
|
||||
break;
|
||||
case "text70":
|
||||
fontSize = 16;
|
||||
break;
|
||||
case "text80":
|
||||
fontSize = 14;
|
||||
break;
|
||||
case "text90":
|
||||
fontSize = 12;
|
||||
break;
|
||||
case "text100":
|
||||
fontSize = 10;
|
||||
break;
|
||||
default:
|
||||
fontSize = 16;
|
||||
}
|
||||
|
||||
return {fontFamily, fontSize};
|
||||
};
|
||||
|
||||
export default function RootLayout() {
|
||||
const [loaded] = useFonts({
|
||||
Manrope_200ExtraLight,
|
||||
Manrope_300Light,
|
||||
Manrope_400Regular,
|
||||
Manrope_500Medium,
|
||||
Manrope_600SemiBold,
|
||||
Manrope_700Bold,
|
||||
Manrope_800ExtraBold,
|
||||
PlusJakartaSans_200ExtraLight,
|
||||
PlusJakartaSans_300Light,
|
||||
PlusJakartaSans_400Regular,
|
||||
PlusJakartaSans_500Medium,
|
||||
PlusJakartaSans_600SemiBold,
|
||||
PlusJakartaSans_700Bold,
|
||||
PlusJakartaSans_800ExtraBold,
|
||||
PlusJakartaSans_200ExtraLight_Italic,
|
||||
PlusJakartaSans_300Light_Italic,
|
||||
PlusJakartaSans_400Regular_Italic,
|
||||
PlusJakartaSans_500Medium_Italic,
|
||||
PlusJakartaSans_600SemiBold_Italic,
|
||||
PlusJakartaSans_700Bold_Italic,
|
||||
PlusJakartaSans_800ExtraBold_Italic,
|
||||
Poppins_100Thin,
|
||||
Poppins_100Thin_Italic,
|
||||
Poppins_200ExtraLight,
|
||||
Poppins_200ExtraLight_Italic,
|
||||
Poppins_300Light,
|
||||
Poppins_300Light_Italic,
|
||||
Poppins_400Regular,
|
||||
Poppins_400Regular_Italic,
|
||||
Poppins_500Medium,
|
||||
Poppins_500Medium_Italic,
|
||||
Poppins_600SemiBold,
|
||||
Poppins_600SemiBold_Italic,
|
||||
Poppins_700Bold,
|
||||
Poppins_700Bold_Italic,
|
||||
Poppins_800ExtraBold,
|
||||
Poppins_800ExtraBold_Italic,
|
||||
Poppins_900Black,
|
||||
Poppins_900Black_Italic,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (loaded) {
|
||||
SplashScreen.hideAsync();
|
||||
|
||||
const typographies: Partial<Record<TextStyle, FontStyle>> = {};
|
||||
(
|
||||
[
|
||||
"text10",
|
||||
"text20",
|
||||
"text30",
|
||||
"text40",
|
||||
"text50",
|
||||
"text60",
|
||||
"text70",
|
||||
"text80",
|
||||
"text90",
|
||||
"text100",
|
||||
] as const
|
||||
).forEach((baseStyle) => {
|
||||
typographies[baseStyle] = getManropeFontStyle(baseStyle);
|
||||
(["R", "M", "BO", "H", "BL", "L"] as const).forEach((modifier) => {
|
||||
const style = `${baseStyle}${modifier}` as TextStyle;
|
||||
typographies[style] = getManropeFontStyle(style);
|
||||
});
|
||||
});
|
||||
|
||||
Typography.loadTypographies(typographies);
|
||||
|
||||
ThemeManager.setComponentTheme(
|
||||
"Text",
|
||||
(props: ExtendedTextProps) => {
|
||||
const textStyle = (
|
||||
Object.keys(props) as Array<keyof ExtendedTextProps>
|
||||
).find((key) => typographies[key as TextStyle]) as
|
||||
| TextStyle
|
||||
| undefined;
|
||||
|
||||
return {
|
||||
style: [
|
||||
Typography.text50,
|
||||
textStyle ? typographies[textStyle] : undefined,
|
||||
],
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
}, [loaded]);
|
||||
|
||||
if (!loaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AuthContextProvider>
|
||||
<ThemeProvider value={DefaultTheme}>
|
||||
<Stack>
|
||||
<Stack.Screen name="(auth)" options={{headerShown: false}}/>
|
||||
<Stack.Screen name="(unauth)" options={{headerShown: false}}/>
|
||||
<Stack.Screen name="+not-found"/>
|
||||
</Stack>
|
||||
<Toast/>
|
||||
</ThemeProvider>
|
||||
</AuthContextProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
||||
19
assets/svgs/ArrowRightIcon.tsx
Normal file
19
assets/svgs/ArrowRightIcon.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import * as React from "react"
|
||||
import Svg, { SvgProps, Path } from "react-native-svg"
|
||||
const ArrowRightIcon = (props: SvgProps) => (
|
||||
<Svg
|
||||
width={9}
|
||||
height={15}
|
||||
viewBox="0 0 9 15"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
stroke="#ACACAC"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={2}
|
||||
d="M1.272 1.803 7.16 7.69a.16.16 0 0 1 0 .226l-5.887 5.887"
|
||||
/>
|
||||
</Svg>
|
||||
)
|
||||
export default ArrowRightIcon
|
||||
22
assets/svgs/CircledXIcon.tsx
Normal file
22
assets/svgs/CircledXIcon.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import * as React from "react";
|
||||
import Svg, { SvgProps, Path } from "react-native-svg";
|
||||
const CircledXIcon = (props: SvgProps) => (
|
||||
<Svg
|
||||
width={props.width || 22}
|
||||
height={props.height || 21}
|
||||
viewBox="0 0 22 21"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
stroke={props.color || "#BBB"}
|
||||
d="M11 20.5c5.523 0 10-4.477 10-10S16.523.5 11 .5 1 4.977 1 10.5s4.477 10 10 10Z"
|
||||
/>
|
||||
<Path
|
||||
stroke={props.color || "#BBB"}
|
||||
strokeLinecap="round"
|
||||
d="m13.75 7.75-5.5 5.5m0-5.5 5.5 5.5"
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
export default CircledXIcon;
|
||||
17
assets/svgs/EmailIcon.tsx
Normal file
17
assets/svgs/EmailIcon.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import * as React from "react"
|
||||
import Svg, { SvgProps, Path } from "react-native-svg"
|
||||
const EmailIcon = (props: SvgProps) => (
|
||||
<Svg
|
||||
width={props.width || 20}
|
||||
height={props.height || 16}
|
||||
viewBox="0 0 20 16"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
fill={props.color || "#fff"}
|
||||
d="M19.948 2.385a2.767 2.767 0 0 0-.76-1.422A2.768 2.768 0 0 0 17.225.15H2.774A2.772 2.772 0 0 0 0 2.925v10.15c0 .389.083.763.23 1.101a2.719 2.719 0 0 0 .774 1.035c.48.398 1.1.638 1.77.638h14.452a2.755 2.755 0 0 0 1.961-.813c.245-.245.447-.537.586-.86v-.002c.147-.338.227-.71.227-1.1V2.925c0-.182-.018-.363-.052-.539ZM1.817 1.967a1.34 1.34 0 0 1 .957-.397h14.452a1.335 1.335 0 0 1 1.079.54l-7.575 6.6a1.11 1.11 0 0 1-1.46 0L1.698 2.107c.034-.048.075-.095.119-.139Zm-.398 11.107V3.575l5.482 4.782-5.479 4.777c-.003-.018-.003-.039-.003-.06Zm15.807 1.355H2.774c-.245 0-.475-.064-.67-.178l5.78-5.037.54.47a2.406 2.406 0 0 0 3.155 0l.54-.47 5.778 5.037a1.338 1.338 0 0 1-.671.178Zm1.355-1.354c0 .02 0 .04-.003.059L13.1 8.36l5.48-4.782v9.497Z"
|
||||
/>
|
||||
</Svg>
|
||||
)
|
||||
export default EmailIcon
|
||||
20
assets/svgs/QRIcon.tsx
Normal file
20
assets/svgs/QRIcon.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import * as React from "react"
|
||||
import Svg, { SvgProps, Path } from "react-native-svg"
|
||||
const QRIcon = (props: SvgProps) => (
|
||||
<Svg
|
||||
width={props.width || 19}
|
||||
height={props.height || 20}
|
||||
viewBox="0 0 19 20"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
stroke={props.color || "#fff"}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.764}
|
||||
d="M5.086 10.441h4.41v4.41m-7.93-4.41h-.008m4.418 4.41h-.008m3.536 3.528h-.008m7.946-7.938h-.008m-15.876 4.41H2.88m9.702-4.41h1.764M1.557 18.38h4.41M9.497 1.621v5.292m4.939 11.466h1.587c.494 0 .741 0 .93-.096.166-.085.3-.22.385-.386.097-.188.097-.435.097-.93V15.38c0-.494 0-.74-.097-.93a.882.882 0 0 0-.385-.385c-.189-.096-.436-.096-.93-.096h-1.587c-.494 0-.741 0-.93.096a.881.881 0 0 0-.385.386c-.096.188-.096.435-.096.93v1.587c0 .494 0 .74.096.93.084.166.22.3.385.385.189.096.436.096.93.096Zm0-11.466h1.587c.494 0 .741 0 .93-.096.166-.085.3-.22.385-.385.097-.19.097-.436.097-.93V3.914c0-.494 0-.74-.097-.93a.882.882 0 0 0-.385-.385c-.189-.096-.436-.096-.93-.096h-1.587c-.494 0-.741 0-.93.096a.881.881 0 0 0-.385.386c-.096.188-.096.435-.096.93v1.587c0 .494 0 .74.096.93.084.165.22.3.385.385.189.096.436.096.93.096Zm-11.466 0h1.587c.494 0 .741 0 .93-.096.166-.085.3-.22.385-.385.097-.19.097-.436.097-.93V3.914c0-.494 0-.74-.097-.93a.882.882 0 0 0-.385-.385c-.189-.096-.436-.096-.93-.096H2.97c-.494 0-.741 0-.93.096a.882.882 0 0 0-.385.386c-.096.188-.096.435-.096.93v1.587c0 .494 0 .74.096.93.084.165.22.3.385.385.189.096.436.096.93.096Z"
|
||||
/>
|
||||
</Svg>
|
||||
)
|
||||
export default QRIcon
|
||||
41
calendar-integration/apple-calendar-utils.js
Normal file
41
calendar-integration/apple-calendar-utils.js
Normal file
@ -0,0 +1,41 @@
|
||||
import * as Calendar from 'expo-calendar';
|
||||
|
||||
export async function fetchiPhoneCalendarEvents(familyId, email, startDate, endDate) {
|
||||
try {
|
||||
const {status} = await Calendar.requestCalendarPermissionsAsync();
|
||||
if (status !== 'granted') {
|
||||
throw new Error("Calendar permission not granted");
|
||||
}
|
||||
|
||||
const defaultCalendarSource = await Calendar.getDefaultCalendarAsync();
|
||||
|
||||
if (!defaultCalendarSource) {
|
||||
throw new Error("No calendar found");
|
||||
}
|
||||
|
||||
const events = await Calendar.getEventsAsync(
|
||||
[defaultCalendarSource.id],
|
||||
startDate,
|
||||
endDate
|
||||
);
|
||||
|
||||
return events.map((event) => {
|
||||
let isAllDay = event.allDay || false;
|
||||
const startDateTime = new Date(event.startDate);
|
||||
const endDateTime = new Date(event.endDate);
|
||||
|
||||
return {
|
||||
id: event.id,
|
||||
title: event.title,
|
||||
startDate: startDateTime,
|
||||
endDate: endDateTime,
|
||||
allDay: isAllDay,
|
||||
familyId,
|
||||
email
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching iPhone Calendar events: ", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
export async function fetchGoogleCalendarEvents(token, startDate, endDate) {
|
||||
export async function fetchGoogleCalendarEvents(token, email, familyId, startDate, endDate) {
|
||||
console.log(token);
|
||||
const response = await fetch(
|
||||
`https://www.googleapis.com/calendar/v3/calendars/primary/events?single_events=true&time_min=${startDate}&time_max=${endDate}`,
|
||||
@ -45,6 +45,8 @@ export async function fetchGoogleCalendarEvents(token, startDate, endDate) {
|
||||
startDate: startDateTime,
|
||||
endDate: endDateTime,
|
||||
allDay: isAllDay,
|
||||
familyId,
|
||||
email
|
||||
};
|
||||
googleEvents.push(googleEvent);
|
||||
});
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export async function fetchMicrosoftCalendarEvents(token, startDate, endDate) {
|
||||
export async function fetchMicrosoftCalendarEvents(token, email, familyId, startDate, endDate) {
|
||||
const response = await fetch(
|
||||
`https://graph.microsoft.com/v1.0/me/calendar/calendarView?startDateTime=${startDate}&endDateTime=${endDate}`,
|
||||
{
|
||||
@ -34,6 +34,8 @@ export async function fetchMicrosoftCalendarEvents(token, startDate, endDate) {
|
||||
startDate: startDateTime,
|
||||
endDate: endDateTime,
|
||||
allDay: item.isAllDay,
|
||||
familyId,
|
||||
email
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -1,17 +1,24 @@
|
||||
import { View, Text, Button, TextField } from "react-native-ui-lib";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
Button,
|
||||
TextField,
|
||||
TextFieldRef,
|
||||
TouchableOpacity,
|
||||
} from "react-native-ui-lib";
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import { Dialog } from "react-native-ui-lib";
|
||||
import { PanningDirectionsEnum } from "react-native-ui-lib/src/incubator/panView";
|
||||
import CloseXIcon from "@/assets/svgs/CloseXIcon";
|
||||
import { Dimensions, StyleSheet } from "react-native";
|
||||
import { Dimensions, Keyboard, StyleSheet } from "react-native";
|
||||
|
||||
import DropModalIcon from "@/assets/svgs/DropModalIcon";
|
||||
import MenuIcon from "@/assets/svgs/MenuIcon";
|
||||
import { useBrainDumpContext } from "@/contexts/DumpContext";
|
||||
|
||||
interface IAddBrainDumpProps {
|
||||
isVisible: boolean;
|
||||
setIsVisible: (value: boolean) => void;
|
||||
}
|
||||
|
||||
const AddBrainDump = ({
|
||||
addBrainDumpProps,
|
||||
}: {
|
||||
@ -20,7 +27,10 @@ const AddBrainDump = ({
|
||||
const { addBrainDump } = useBrainDumpContext();
|
||||
const [dumpTitle, setDumpTitle] = useState<string>("");
|
||||
const [dumpDesc, setDumpDesc] = useState<string>("");
|
||||
const { width, height } = Dimensions.get("screen");
|
||||
const { width } = Dimensions.get("screen");
|
||||
|
||||
// Refs for the two TextFields
|
||||
const descriptionRef = useRef<TextFieldRef>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setDumpDesc("");
|
||||
@ -34,14 +44,7 @@ const AddBrainDump = ({
|
||||
width={width}
|
||||
panDirection={PanningDirectionsEnum.DOWN}
|
||||
onDismiss={() => addBrainDumpProps.setIsVisible(false)}
|
||||
containerStyle={{
|
||||
borderTopRightRadius: 15,
|
||||
borderTopLeftRadius: 15,
|
||||
backgroundColor: "white",
|
||||
padding: 0,
|
||||
paddingTop: 3,
|
||||
margin: 0,
|
||||
}}
|
||||
containerStyle={styles.dialogContainer}
|
||||
visible={addBrainDumpProps.isVisible}
|
||||
>
|
||||
<View row spread style={styles.topBtns} marginB-20>
|
||||
@ -53,16 +56,15 @@ const AddBrainDump = ({
|
||||
addBrainDumpProps.setIsVisible(false);
|
||||
}}
|
||||
/>
|
||||
<DropModalIcon
|
||||
style={{ marginTop: 15 }}
|
||||
onPress={() => addBrainDumpProps.setIsVisible(false)}
|
||||
/>
|
||||
<TouchableOpacity onPress={() => addBrainDumpProps.setIsVisible(false)}>
|
||||
<DropModalIcon style={{ marginTop: 15 }} />
|
||||
</TouchableOpacity>
|
||||
<Button
|
||||
color="#05a8b6"
|
||||
label="Save"
|
||||
style={styles.topBtn}
|
||||
onPress={() => {
|
||||
addBrainDump({ id: 99, title: dumpTitle, description: dumpDesc });
|
||||
addBrainDump({ id: 99, title: dumpTitle.trimEnd().trimStart(), description: dumpDesc.trimEnd().trimStart() });
|
||||
addBrainDumpProps.setIsVisible(false);
|
||||
}}
|
||||
/>
|
||||
@ -70,20 +72,37 @@ const AddBrainDump = ({
|
||||
<View marginH-20>
|
||||
<TextField
|
||||
value={dumpTitle}
|
||||
autoFocus
|
||||
placeholder="Set Title"
|
||||
text60R
|
||||
onChangeText={(text) => {
|
||||
setDumpTitle(text);
|
||||
}}
|
||||
onSubmitEditing={() => {
|
||||
// Move focus to the description field
|
||||
descriptionRef.current?.focus();
|
||||
}}
|
||||
style={styles.title}
|
||||
blurOnSubmit={false} // Keep the keyboard open when moving focus
|
||||
returnKeyType="next"
|
||||
/>
|
||||
<View height={2} backgroundColor="#b3b3b3" width={"100%"} marginB-20 />
|
||||
<TextField
|
||||
ref={descriptionRef}
|
||||
value={dumpDesc}
|
||||
placeholder="Write Description"
|
||||
text70
|
||||
onChangeText={(text) => {
|
||||
setDumpDesc(text);
|
||||
}}
|
||||
style={styles.description}
|
||||
multiline
|
||||
numberOfLines={4}
|
||||
maxLength={255}
|
||||
onEndEditing={() => {
|
||||
descriptionRef.current?.blur();
|
||||
}}
|
||||
returnKeyType="done"
|
||||
/>
|
||||
</View>
|
||||
</Dialog>
|
||||
@ -91,11 +110,28 @@ const AddBrainDump = ({
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
dialogContainer: {
|
||||
borderTopRightRadius: 15,
|
||||
borderTopLeftRadius: 15,
|
||||
backgroundColor: "white",
|
||||
padding: 0,
|
||||
paddingTop: 3,
|
||||
margin: 0,
|
||||
},
|
||||
topBtns: {},
|
||||
topBtn: {
|
||||
backgroundColor: "white",
|
||||
color: "#05a8b6",
|
||||
},
|
||||
title: {
|
||||
fontSize: 22,
|
||||
fontFamily: "Manrope_500Medium",
|
||||
},
|
||||
description: {
|
||||
fontFamily: "Manrope_400Regular",
|
||||
fontSize: 14,
|
||||
textAlignVertical: 'top'
|
||||
},
|
||||
});
|
||||
|
||||
export default AddBrainDump;
|
||||
|
||||
@ -98,19 +98,18 @@ const MoveBrainDump = (props: {
|
||||
<TextField
|
||||
textAlignVertical="top"
|
||||
multiline
|
||||
autoFocus
|
||||
fieldStyle={{
|
||||
width: "94%",
|
||||
}}
|
||||
style={{
|
||||
fontFamily: "Manrope_400Regular",
|
||||
fontSize: 14,
|
||||
}}
|
||||
style={styles.description}
|
||||
placeholder="Add description"
|
||||
numberOfLines={3}
|
||||
value={description}
|
||||
onChangeText={(value) => {
|
||||
setDescription(value);
|
||||
}}
|
||||
returnKeyType="default"
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.divider} />
|
||||
@ -192,6 +191,10 @@ const styles = StyleSheet.create({
|
||||
fontSize: 22,
|
||||
fontFamily: "Manrope_500Medium",
|
||||
},
|
||||
description:{
|
||||
fontFamily: "Manrope_400Regular",
|
||||
fontSize: 14,
|
||||
}
|
||||
});
|
||||
|
||||
export default MoveBrainDump;
|
||||
|
||||
@ -1,188 +1,172 @@
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
AntDesign,
|
||||
Feather,
|
||||
MaterialCommunityIcons,
|
||||
MaterialIcons,
|
||||
} from "@expo/vector-icons";
|
||||
import {
|
||||
Button,
|
||||
ButtonSize,
|
||||
Card,
|
||||
Dialog,
|
||||
PanningProvider,
|
||||
Text,
|
||||
View,
|
||||
} from "react-native-ui-lib";
|
||||
import { StyleSheet, TouchableOpacity } from "react-native";
|
||||
import { ManuallyAddEventModal } from "@/components/pages/calendar/ManuallyAddEventModal";
|
||||
import React, {useState} from "react";
|
||||
import {MaterialIcons,} from "@expo/vector-icons";
|
||||
import {Button, Card, Dialog, PanningProvider, Text, View,} from "react-native-ui-lib";
|
||||
import {StyleSheet, TouchableOpacity} from "react-native";
|
||||
import AddChoreDialog from "../todos/AddChoreDialog";
|
||||
import { ToDosContextProvider } from "@/contexts/ToDosContext";
|
||||
import {ToDosContextProvider} from "@/contexts/ToDosContext";
|
||||
import UploadImageDialog from "./UploadImageDialog";
|
||||
import CameraIcon from "@/assets/svgs/CameraIcon";
|
||||
import CalendarIcon from "@/assets/svgs/CalendarIcon";
|
||||
import NavToDosIcon from "@/assets/svgs/NavToDosIcon";
|
||||
import {useSetAtom} from "jotai";
|
||||
import {selectedNewEventDateAtom} from "@/components/pages/calendar/atoms";
|
||||
|
||||
export const AddEventDialog = () => {
|
||||
const [show, setShow] = useState(false);
|
||||
const [showManualInputModal, setShowManualInputModal] = useState(false);
|
||||
const [choreDialogVisible, setChoreDialogVisible] = useState<boolean>(false);
|
||||
const [showUploadDialog, setShowUploadDialog] = useState<boolean>(false);
|
||||
const [show, setShow] = useState(false);
|
||||
const [choreDialogVisible, setChoreDialogVisible] = useState<boolean>(false);
|
||||
const [showUploadDialog, setShowUploadDialog] = useState<boolean>(false);
|
||||
const setSelectedNewEndDate = useSetAtom(selectedNewEventDateAtom)
|
||||
|
||||
const handleOpenManualInputModal = () => {
|
||||
setShow(false);
|
||||
setTimeout(() => {
|
||||
setShowManualInputModal(true);
|
||||
}, 500);
|
||||
};
|
||||
const handleOpenManualInputModal = () => {
|
||||
setShow(false);
|
||||
setTimeout(() => {
|
||||
setSelectedNewEndDate(new Date());
|
||||
}, 500);
|
||||
};
|
||||
|
||||
const handleScanImageDialog = () => {
|
||||
setShow(false);
|
||||
setTimeout(() => {
|
||||
setShowUploadDialog(true);
|
||||
}, 100);
|
||||
};
|
||||
const handleScanImageDialog = () => {
|
||||
setShow(false);
|
||||
setTimeout(() => {
|
||||
setShowUploadDialog(true);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
return (
|
||||
<ToDosContextProvider>
|
||||
<>
|
||||
<Button
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 20,
|
||||
right: 20,
|
||||
height: 40,
|
||||
borderRadius: 30,
|
||||
backgroundColor: "#fd1775",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
color="white"
|
||||
enableShadow
|
||||
onPress={() => setShow(true)}
|
||||
>
|
||||
<View row centerV centerH>
|
||||
<MaterialIcons name="add" size={22} color={"white"} />
|
||||
<Text white style={{ fontSize: 16, fontFamily: 'Manrope_600SemiBold' }}>
|
||||
New
|
||||
</Text>
|
||||
</View>
|
||||
</Button>
|
||||
return (
|
||||
<ToDosContextProvider>
|
||||
<>
|
||||
<Button
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 20,
|
||||
right: 20,
|
||||
height: 40,
|
||||
borderRadius: 30,
|
||||
backgroundColor: "#fd1775",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
color="white"
|
||||
enableShadow
|
||||
onPress={() => setShow(true)}
|
||||
>
|
||||
<View row centerV centerH>
|
||||
<MaterialIcons name="add" size={22} color={"white"}/>
|
||||
<Text white style={{fontSize: 16, fontFamily: 'Manrope_600SemiBold'}}>
|
||||
New
|
||||
</Text>
|
||||
</View>
|
||||
</Button>
|
||||
|
||||
<Dialog
|
||||
visible={show}
|
||||
onDismiss={() => setShow(false)}
|
||||
panDirection={PanningProvider.Directions.DOWN}
|
||||
center
|
||||
>
|
||||
<Card style={styles.dialogCard}>
|
||||
<Text text60 style={styles.modalTitle}>
|
||||
Create a new event
|
||||
</Text>
|
||||
<Dialog
|
||||
visible={show}
|
||||
onDismiss={() => setShow(false)}
|
||||
panDirection={PanningProvider.Directions.DOWN}
|
||||
center
|
||||
>
|
||||
<Card style={styles.dialogCard}>
|
||||
<Text text60 style={styles.modalTitle}>
|
||||
Create a new event
|
||||
</Text>
|
||||
|
||||
<View
|
||||
style={{ marginTop: 20, alignItems: "center", width: "100%" }}
|
||||
>
|
||||
<Button
|
||||
style={{
|
||||
marginBottom: 10,
|
||||
backgroundColor: "#ea156c",
|
||||
justifyContent: "center",
|
||||
width: "100%",
|
||||
paddingVertical: 13,
|
||||
}}
|
||||
label="Scan Image"
|
||||
labelStyle={styles.btnLabel}
|
||||
onPress={handleScanImageDialog}
|
||||
iconSource={() => (
|
||||
<CameraIcon color="white" style={styles.btnIcon} />
|
||||
)}
|
||||
/>
|
||||
<View
|
||||
style={{marginTop: 20, alignItems: "center", width: "100%"}}
|
||||
>
|
||||
<Button
|
||||
style={{
|
||||
marginBottom: 10,
|
||||
backgroundColor: "#ea156c",
|
||||
justifyContent: "center",
|
||||
width: "100%",
|
||||
paddingVertical: 13,
|
||||
}}
|
||||
label="Scan Image"
|
||||
labelStyle={styles.btnLabel}
|
||||
onPress={handleScanImageDialog}
|
||||
iconSource={() => (
|
||||
<CameraIcon color="white" style={styles.btnIcon}/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
style={{
|
||||
marginBottom: 10,
|
||||
backgroundColor: "#e28800",
|
||||
justifyContent: "center",
|
||||
width: "100%",
|
||||
paddingVertical: 13,
|
||||
}}
|
||||
label="Create Event"
|
||||
labelStyle={styles.btnLabel}
|
||||
onPress={handleOpenManualInputModal}
|
||||
iconSource={() => (
|
||||
<CalendarIcon color={"white"} style={styles.btnIcon} />
|
||||
)}
|
||||
/>
|
||||
<Button
|
||||
style={{
|
||||
marginBottom: 10,
|
||||
backgroundColor: "#e28800",
|
||||
justifyContent: "center",
|
||||
width: "100%",
|
||||
paddingVertical: 13,
|
||||
}}
|
||||
label="Create Event"
|
||||
labelStyle={styles.btnLabel}
|
||||
onPress={handleOpenManualInputModal}
|
||||
iconSource={() => (
|
||||
<CalendarIcon color={"white"} style={styles.btnIcon}/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
style={{
|
||||
marginBottom: 10,
|
||||
backgroundColor: "#05a8b6",
|
||||
justifyContent: "center",
|
||||
width: "100%",
|
||||
paddingVertical: 13,
|
||||
}}
|
||||
label="Add To Do"
|
||||
labelStyle={styles.btnLabel}
|
||||
onPress={() => setChoreDialogVisible(true)}
|
||||
iconSource={() => (
|
||||
<NavToDosIcon
|
||||
color={"white"}
|
||||
width={23}
|
||||
style={styles.btnIcon}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
<Button
|
||||
style={{
|
||||
marginBottom: 10,
|
||||
backgroundColor: "#05a8b6",
|
||||
justifyContent: "center",
|
||||
width: "100%",
|
||||
paddingVertical: 13,
|
||||
}}
|
||||
label="Add To Do"
|
||||
labelStyle={styles.btnLabel}
|
||||
onPress={() => setChoreDialogVisible(true)}
|
||||
iconSource={() => (
|
||||
<NavToDosIcon
|
||||
color={"white"}
|
||||
width={23}
|
||||
style={styles.btnIcon}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<TouchableOpacity onPress={() => setShow(false)}>
|
||||
<Text style={styles.bottomText} text70>
|
||||
Go back to calendar
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</Card>
|
||||
</Dialog>
|
||||
<AddChoreDialog
|
||||
isVisible={choreDialogVisible}
|
||||
setIsVisible={setChoreDialogVisible}
|
||||
/>
|
||||
<ManuallyAddEventModal
|
||||
show={showManualInputModal}
|
||||
close={() => setShowManualInputModal(false)}
|
||||
/>
|
||||
<UploadImageDialog
|
||||
show={showUploadDialog}
|
||||
setShow={setShowUploadDialog}
|
||||
/>
|
||||
</>
|
||||
</ToDosContextProvider>
|
||||
);
|
||||
<TouchableOpacity onPress={() => setShow(false)}>
|
||||
<Text style={styles.bottomText} text70>
|
||||
Go back to calendar
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</Card>
|
||||
</Dialog>
|
||||
<AddChoreDialog
|
||||
isVisible={choreDialogVisible}
|
||||
setIsVisible={setChoreDialogVisible}
|
||||
/>
|
||||
<UploadImageDialog
|
||||
show={showUploadDialog}
|
||||
setShow={setShowUploadDialog}
|
||||
/>
|
||||
</>
|
||||
</ToDosContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
modalTitle: {
|
||||
fontSize: 22,
|
||||
fontFamily: "Manrope_600SemiBold",
|
||||
marginBottom: 16,
|
||||
},
|
||||
bottomText: {
|
||||
marginTop: 20,
|
||||
color: "#999999",
|
||||
fontSize: 13.53,
|
||||
fontFamily: "Poppins_500Medium",
|
||||
},
|
||||
dialogCard: {
|
||||
paddingHorizontal: 40,
|
||||
paddingTop: 35,
|
||||
paddingBottom: 20,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
borderRadius: 20,
|
||||
},
|
||||
btnLabel: {
|
||||
fontSize: 15,
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
},
|
||||
btnIcon: { marginRight: 10 },
|
||||
modalTitle: {
|
||||
fontSize: 22,
|
||||
fontFamily: "Manrope_600SemiBold",
|
||||
marginBottom: 16,
|
||||
},
|
||||
bottomText: {
|
||||
marginTop: 20,
|
||||
color: "#999999",
|
||||
fontSize: 13.53,
|
||||
fontFamily: "Poppins_500Medium",
|
||||
},
|
||||
dialogCard: {
|
||||
paddingHorizontal: 40,
|
||||
paddingTop: 35,
|
||||
paddingBottom: 20,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
borderRadius: 20,
|
||||
},
|
||||
btnLabel: {
|
||||
fontSize: 15,
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
},
|
||||
btnIcon: {marginRight: 10},
|
||||
});
|
||||
|
||||
102
components/pages/calendar/CalendarHeader.tsx
Normal file
102
components/pages/calendar/CalendarHeader.tsx
Normal file
@ -0,0 +1,102 @@
|
||||
import React, {memo} from 'react';
|
||||
import {Picker, PickerModes, SegmentedControl, Text, View} from "react-native-ui-lib";
|
||||
import {MaterialIcons} from "@expo/vector-icons";
|
||||
import {modeMap, months} from './constants';
|
||||
import {StyleSheet} from "react-native";
|
||||
import {useAtom} from "jotai";
|
||||
import {modeAtom, selectedDateAtom} from "@/components/pages/calendar/atoms";
|
||||
|
||||
|
||||
export const CalendarHeader = memo(() => {
|
||||
const [selectedDate, setSelectedDate] = useAtom(selectedDateAtom)
|
||||
const [mode, setMode] = useAtom(modeAtom)
|
||||
|
||||
const handleSegmentChange = (index: number) => {
|
||||
const selectedMode = modeMap.get(index);
|
||||
if (selectedMode) {
|
||||
setMode(selectedMode as "day" | "week" | "month");
|
||||
}
|
||||
};
|
||||
|
||||
const handleMonthChange = (month: string) => {
|
||||
const currentDay = selectedDate.getDate();
|
||||
const currentYear = selectedDate.getFullYear();
|
||||
|
||||
const newMonthIndex = months.indexOf(month);
|
||||
|
||||
const updatedDate = new Date(currentYear, newMonthIndex, currentDay);
|
||||
|
||||
setSelectedDate(updatedDate);
|
||||
};
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 20,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
backgroundColor: "white",
|
||||
marginBottom: 10,
|
||||
}}
|
||||
>
|
||||
<View row centerV gap-3>
|
||||
<Text style={{fontFamily: "Manrope_500Medium", fontSize: 17}}>
|
||||
{selectedDate.getFullYear()}
|
||||
</Text>
|
||||
<Picker
|
||||
value={months[selectedDate.getMonth()]}
|
||||
placeholder={"Select Month"}
|
||||
style={{fontFamily: "Manrope_500Medium", fontSize: 17}}
|
||||
mode={PickerModes.SINGLE}
|
||||
onChange={(itemValue) => handleMonthChange(itemValue as string)}
|
||||
trailingAccessory={<MaterialIcons name={"keyboard-arrow-down"}/>}
|
||||
topBarProps={{
|
||||
title: selectedDate.getFullYear().toString(),
|
||||
titleStyle: {fontFamily: "Manrope_500Medium", fontSize: 17},
|
||||
}}
|
||||
>
|
||||
{months.map((month) => (
|
||||
<Picker.Item key={month} label={month} value={month}/>
|
||||
))}
|
||||
</Picker>
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<SegmentedControl
|
||||
segments={[{label: "D"}, {label: "W"}, {label: "M"}]}
|
||||
backgroundColor="#ececec"
|
||||
inactiveColor="#919191"
|
||||
activeBackgroundColor="#ea156c"
|
||||
activeColor="white"
|
||||
outlineColor="white"
|
||||
outlineWidth={3}
|
||||
segmentLabelStyle={styles.segmentslblStyle}
|
||||
onChangeIndex={handleSegmentChange}
|
||||
initialIndex={mode === "day" ? 0 : mode === "week" ? 1 : 2}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
segmentslblStyle: {
|
||||
fontSize: 12,
|
||||
fontFamily: "Manrope_600SemiBold",
|
||||
},
|
||||
calHeader: {
|
||||
borderWidth: 0,
|
||||
},
|
||||
dayModeHeader: {
|
||||
alignSelf: "flex-start",
|
||||
justifyContent: "space-between",
|
||||
alignContent: "center",
|
||||
width: 38,
|
||||
right: 42,
|
||||
},
|
||||
});
|
||||
@ -1,207 +1,20 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import { LayoutChangeEvent, StyleSheet } from "react-native";
|
||||
import { Calendar } from "react-native-big-calendar";
|
||||
import {
|
||||
Picker,
|
||||
PickerModes,
|
||||
SegmentedControl,
|
||||
View,
|
||||
} from "react-native-ui-lib";
|
||||
import { MaterialIcons } from "@expo/vector-icons";
|
||||
import { AddEventDialog } from "@/components/pages/calendar/AddEventDialog";
|
||||
import React from "react";
|
||||
import {View,} from "react-native-ui-lib";
|
||||
import HeaderTemplate from "@/components/shared/HeaderTemplate";
|
||||
import CalendarViewSwitch from "@/components/pages/calendar/CalendarViewSwitch";
|
||||
import { ManuallyAddEventModal } from "@/components/pages/calendar/ManuallyAddEventModal";
|
||||
import { CalendarEvent } from "@/contexts/CalendarContext";
|
||||
import { useSettingsContext } from "@/contexts/SettingsContext";
|
||||
import EditEventDialog from "./EditEventDialog";
|
||||
import { useGetEvents } from "@/hooks/firebase/useGetEvents";
|
||||
import { Text } from "react-native-ui-lib";
|
||||
|
||||
const modeMap = new Map([
|
||||
[0, "day"],
|
||||
[1, "week"],
|
||||
[2, "month"],
|
||||
]);
|
||||
|
||||
const months = [
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
];
|
||||
import {InnerCalendar} from "@/components/pages/calendar/InnerCalendar";
|
||||
|
||||
export default function CalendarPage() {
|
||||
const { calendarColor } = useSettingsContext();
|
||||
const [editVisible, setEditVisible] = useState<boolean>(false);
|
||||
const [eventForEdit, setEventForEdit] = useState<CalendarEvent>();
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
segmentslblStyle: {
|
||||
fontSize: 12,
|
||||
fontFamily: "Manrope_600SemiBold",
|
||||
},
|
||||
calHeader: {
|
||||
borderWidth: 0,
|
||||
},
|
||||
dayModeHeader: {
|
||||
alignSelf: "flex-start",
|
||||
justifyContent: "space-between",
|
||||
alignContent: "center",
|
||||
width: 38,
|
||||
right: 42,
|
||||
},
|
||||
});
|
||||
|
||||
const [isFamilyView, setIsFamilyView] = useState<boolean>(false);
|
||||
const [calendarHeight, setCalendarHeight] = useState(0);
|
||||
const [mode, setMode] = useState<"week" | "month" | "day">("week");
|
||||
const [selectedDate, setSelectedDate] = useState<Date>(new Date());
|
||||
const [selectedNewEventDate, setSelectedNewEndDate] = useState<
|
||||
Date | undefined
|
||||
>(undefined);
|
||||
|
||||
const calendarContainerRef = useRef(null);
|
||||
const { data: events } = useGetEvents(isFamilyView);
|
||||
|
||||
const onLayout = (event: LayoutChangeEvent) => {
|
||||
const { height } = event.nativeEvent.layout;
|
||||
setCalendarHeight(height);
|
||||
};
|
||||
|
||||
const handleSegmentChange = (index: number) => {
|
||||
const selectedMode = modeMap.get(index);
|
||||
if (selectedMode) {
|
||||
setMode(selectedMode as "day" | "week" | "month");
|
||||
}
|
||||
};
|
||||
|
||||
const handleMonthChange = (month: string) => {
|
||||
const currentDay = selectedDate.getDate();
|
||||
const currentYear = selectedDate.getFullYear();
|
||||
|
||||
const newMonthIndex = months.indexOf(month);
|
||||
|
||||
const updatedDate = new Date(currentYear, newMonthIndex, currentDay);
|
||||
|
||||
setSelectedDate(updatedDate);
|
||||
};
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{ flex: 1, height: "100%", padding: 10 }}
|
||||
paddingH-22
|
||||
paddingT-0
|
||||
>
|
||||
<HeaderTemplate
|
||||
message={"Let's get your week started!"}
|
||||
isWelcome={true}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={{ flex: 1, backgroundColor: "#fff", borderRadius: 30 }}
|
||||
ref={calendarContainerRef}
|
||||
onLayout={onLayout}
|
||||
>
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 20,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
backgroundColor: "white",
|
||||
marginBottom: 10,
|
||||
}}
|
||||
style={{flex: 1, height: "100%", padding: 10}}
|
||||
paddingH-22
|
||||
paddingT-0
|
||||
>
|
||||
<View row centerV gap-3>
|
||||
<Text style={{ fontFamily: "Manrope_500Medium", fontSize: 17 }}>
|
||||
{selectedDate.getFullYear()}
|
||||
</Text>
|
||||
<Picker
|
||||
value={months[selectedDate.getMonth()]} // Get the month from the date
|
||||
placeholder={"Select Month"}
|
||||
style={{ fontFamily: "Manrope_500Medium", fontSize: 17 }}
|
||||
mode={PickerModes.SINGLE}
|
||||
onChange={(itemValue) => handleMonthChange(itemValue as string)}
|
||||
trailingAccessory={<MaterialIcons name={"keyboard-arrow-down"} />}
|
||||
topBarProps={{
|
||||
title: selectedDate.getFullYear().toString(),
|
||||
titleStyle: { fontFamily: "Manrope_500Medium", fontSize: 17 },
|
||||
}}
|
||||
>
|
||||
{months.map((month) => (
|
||||
<Picker.Item key={month} label={month} value={month} />
|
||||
))}
|
||||
</Picker>
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<SegmentedControl
|
||||
segments={[{ label: "D" }, { label: "W" }, { label: "M" }]}
|
||||
backgroundColor="#ececec"
|
||||
inactiveColor="#919191"
|
||||
activeBackgroundColor="#ea156c"
|
||||
activeColor="white"
|
||||
outlineColor="white"
|
||||
outlineWidth={3}
|
||||
segmentLabelStyle={styles.segmentslblStyle}
|
||||
onChangeIndex={handleSegmentChange}
|
||||
initialIndex={mode === "day" ? 0 : mode === "week" ? 1 : 2}
|
||||
<HeaderTemplate
|
||||
message={"Let's get your week started!"}
|
||||
isWelcome
|
||||
/>
|
||||
</View>
|
||||
<InnerCalendar/>
|
||||
</View>
|
||||
|
||||
{calendarHeight > 0 && (
|
||||
<Calendar
|
||||
bodyContainerStyle={styles.calHeader}
|
||||
mode={mode}
|
||||
events={isFamilyView ? events ?? [] : events ?? []}
|
||||
eventCellStyle={(event) => ({ backgroundColor: event.eventColor })}
|
||||
onPressEvent={(event) => {
|
||||
setEditVisible(true);
|
||||
setEventForEdit(event);
|
||||
}}
|
||||
height={calendarHeight}
|
||||
activeDate={selectedDate}
|
||||
date={selectedDate}
|
||||
onPressCell={setSelectedNewEndDate}
|
||||
headerContentStyle={mode === "day" ? styles.dayModeHeader : {}}
|
||||
onSwipeEnd={(date) => {
|
||||
setSelectedDate(date);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<CalendarViewSwitch viewSwitch={setIsFamilyView} />
|
||||
<AddEventDialog />
|
||||
{eventForEdit && (
|
||||
<EditEventDialog
|
||||
isVisible={editVisible}
|
||||
setIsVisible={() => {
|
||||
setEditVisible(!editVisible);
|
||||
}}
|
||||
event={eventForEdit}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ManuallyAddEventModal
|
||||
key={`${selectedNewEventDate}`}
|
||||
initialDate={selectedNewEventDate}
|
||||
show={!!selectedNewEventDate}
|
||||
close={() => setSelectedNewEndDate(undefined)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,90 +1,90 @@
|
||||
import { View, Text, Button, TouchableOpacity } from "react-native-ui-lib";
|
||||
import React, { useState } from "react";
|
||||
import { MaterialIcons } from "@expo/vector-icons";
|
||||
import { StyleSheet } from "react-native";
|
||||
import {Text, TouchableOpacity, View} from "react-native-ui-lib";
|
||||
import React, {useState} from "react";
|
||||
import {StyleSheet} from "react-native";
|
||||
import {useSetAtom} from "jotai";
|
||||
import {isFamilyViewAtom} from "@/components/pages/calendar/atoms";
|
||||
|
||||
interface ICalendarViewProps {
|
||||
viewSwitch: (value: boolean) => void;
|
||||
}
|
||||
const CalendarViewSwitch = (calendarViewProps: ICalendarViewProps) => {
|
||||
const [calView, setCalView] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<View
|
||||
row
|
||||
spread
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 20,
|
||||
left: 20,
|
||||
borderRadius: 30,
|
||||
backgroundColor: "white",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
// iOS shadow
|
||||
shadowColor: "#000",
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.25,
|
||||
shadowRadius: 3.84,
|
||||
// Android shadow (elevation)
|
||||
elevation: 6,
|
||||
}}
|
||||
centerV
|
||||
>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
setCalView(true);
|
||||
calendarViewProps.viewSwitch(true);
|
||||
}}
|
||||
>
|
||||
const CalendarViewSwitch = () => {
|
||||
const [calView, setCalView] = useState<boolean>(false);
|
||||
const viewSwitch = useSetAtom(isFamilyViewAtom)
|
||||
|
||||
return (
|
||||
<View
|
||||
centerV
|
||||
centerH
|
||||
height={40}
|
||||
paddingH-15
|
||||
style={calView ? styles.switchBtnActive : styles.switchBtn}
|
||||
row
|
||||
spread
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 20,
|
||||
left: 20,
|
||||
borderRadius: 30,
|
||||
backgroundColor: "white",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
// iOS shadow
|
||||
shadowColor: "#000",
|
||||
shadowOffset: {width: 0, height: 2},
|
||||
shadowOpacity: 0.25,
|
||||
shadowRadius: 3.84,
|
||||
// Android shadow (elevation)
|
||||
elevation: 6,
|
||||
}}
|
||||
centerV
|
||||
>
|
||||
<Text color={calView ? "white" : "#a1a1a1"} style={styles.switchTxt}>
|
||||
Family View
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
setCalView(true);
|
||||
viewSwitch(true);
|
||||
}}
|
||||
>
|
||||
<View
|
||||
centerV
|
||||
centerH
|
||||
height={40}
|
||||
paddingH-15
|
||||
style={calView ? styles.switchBtnActive : styles.switchBtn}
|
||||
>
|
||||
<Text color={calView ? "white" : "#a1a1a1"} style={styles.switchTxt}>
|
||||
Family View
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
setCalView(false);
|
||||
calendarViewProps.viewSwitch(false);
|
||||
}}
|
||||
>
|
||||
<View
|
||||
centerV
|
||||
centerH
|
||||
height={40}
|
||||
paddingH-15
|
||||
style={!calView ? styles.switchBtnActive : styles.switchBtn}
|
||||
>
|
||||
<Text color={!calView ? "white" : "#a1a1a1"} style={styles.switchTxt}>
|
||||
My View
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
setCalView(false);
|
||||
viewSwitch(false);
|
||||
}}
|
||||
>
|
||||
<View
|
||||
centerV
|
||||
centerH
|
||||
height={40}
|
||||
paddingH-15
|
||||
style={!calView ? styles.switchBtnActive : styles.switchBtn}
|
||||
>
|
||||
<Text color={!calView ? "white" : "#a1a1a1"} style={styles.switchTxt}>
|
||||
My View
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export default CalendarViewSwitch;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
switchBtnActive: {
|
||||
backgroundColor: "#a1a1a1",
|
||||
borderRadius: 50,
|
||||
},
|
||||
switchBtn: {
|
||||
backgroundColor: "white",
|
||||
borderRadius: 50,
|
||||
},
|
||||
switchTxt:{
|
||||
fontSize: 16,
|
||||
fontFamily: 'Manrope_600SemiBold'
|
||||
}
|
||||
switchBtnActive: {
|
||||
backgroundColor: "#a1a1a1",
|
||||
borderRadius: 50,
|
||||
},
|
||||
switchBtn: {
|
||||
backgroundColor: "white",
|
||||
borderRadius: 50,
|
||||
},
|
||||
switchTxt: {
|
||||
fontSize: 16,
|
||||
fontFamily: 'Manrope_600SemiBold'
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,312 +1,303 @@
|
||||
import { View, Text, Button, Switch } from "react-native-ui-lib";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Feather, AntDesign, Ionicons } from "@expo/vector-icons";
|
||||
import {
|
||||
Dialog,
|
||||
TextField,
|
||||
DateTimePicker,
|
||||
Picker,
|
||||
ButtonSize,
|
||||
} from "react-native-ui-lib";
|
||||
import { PanningDirectionsEnum } from "react-native-ui-lib/src/incubator/panView";
|
||||
import { StyleSheet } from "react-native";
|
||||
import {Button, ButtonSize, DateTimePicker, Dialog, Switch, Text, TextField, View} from "react-native-ui-lib";
|
||||
import React from "react";
|
||||
import {AntDesign, Feather, Ionicons} from "@expo/vector-icons";
|
||||
import {PanningDirectionsEnum} from "react-native-ui-lib/src/incubator/panView";
|
||||
import {StyleSheet} from "react-native";
|
||||
import DropModalIcon from "@/assets/svgs/DropModalIcon";
|
||||
import { CalendarEvent } from "@/contexts/CalendarContext";
|
||||
import ClockIcon from "@/assets/svgs/ClockIcon";
|
||||
import LockIcon from "@/assets/svgs/LockIcon";
|
||||
import MenuIcon from "@/assets/svgs/MenuIcon";
|
||||
import { useUpdateEvent } from "@/hooks/firebase/useUpdateEvent";
|
||||
import {useUpdateEvent} from "@/hooks/firebase/useUpdateEvent";
|
||||
import {editVisibleAtom, eventForEditAtom} from "@/components/pages/calendar/atoms";
|
||||
import {useAtom} from "jotai";
|
||||
|
||||
interface IEditEventDialog {
|
||||
event: CalendarEvent;
|
||||
isVisible: boolean;
|
||||
setIsVisible: (value: boolean) => void;
|
||||
}
|
||||
const EditEventDialog = (editEventProps: IEditEventDialog) => {
|
||||
const [event, setEvent] = useState<CalendarEvent>(editEventProps.event);
|
||||
|
||||
const { mutateAsync: updateEvent } = useUpdateEvent();
|
||||
const EditEventDialog = () => {
|
||||
const [isVisible, setIsVisible] = useAtom(editVisibleAtom)
|
||||
const [event, setEvent] = useAtom(eventForEditAtom)
|
||||
|
||||
useEffect(() => {
|
||||
setEvent(editEventProps.event);
|
||||
}, [editEventProps.isVisible]);
|
||||
const {mutateAsync: updateEvent} = useUpdateEvent();
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
bottom={true}
|
||||
height={"90%"}
|
||||
panDirection={PanningDirectionsEnum.DOWN}
|
||||
onDismiss={() => editEventProps.setIsVisible(false)}
|
||||
containerStyle={{
|
||||
borderRadius: 10,
|
||||
backgroundColor: "white",
|
||||
width: "100%",
|
||||
alignSelf: "stretch",
|
||||
padding: 0,
|
||||
paddingTop: 4,
|
||||
margin: 0,
|
||||
}}
|
||||
visible={editEventProps.isVisible}
|
||||
>
|
||||
<View row spread>
|
||||
<Button
|
||||
color="#05a8b6"
|
||||
style={styles.topBtn}
|
||||
label="Cancel"
|
||||
onPress={() => {
|
||||
editEventProps.setIsVisible(false);
|
||||
}}
|
||||
/>
|
||||
<View marginT-12>
|
||||
<DropModalIcon
|
||||
onPress={() => {
|
||||
editEventProps.setIsVisible(false);
|
||||
if (!event) return null
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
bottom={true}
|
||||
height={"90%"}
|
||||
panDirection={PanningDirectionsEnum.DOWN}
|
||||
onDismiss={() => setIsVisible(false)}
|
||||
containerStyle={{
|
||||
borderRadius: 10,
|
||||
backgroundColor: "white",
|
||||
width: "100%",
|
||||
alignSelf: "stretch",
|
||||
padding: 0,
|
||||
paddingTop: 4,
|
||||
margin: 0,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<Button
|
||||
color="#05a8b6"
|
||||
style={styles.topBtn}
|
||||
label="Save"
|
||||
onPress={() => {
|
||||
try {
|
||||
if (event.id) {
|
||||
updateEvent(event).then(() => editEventProps.setIsVisible(false));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<TextField
|
||||
placeholder="Edit event title"
|
||||
value={event.title}
|
||||
onChangeText={(text) => {
|
||||
setEvent((prevEvent) => ({
|
||||
...prevEvent,
|
||||
title: text,
|
||||
}));
|
||||
}}
|
||||
placeholderTextColor="#2d2d30"
|
||||
text60R
|
||||
marginT-15
|
||||
marginL-30
|
||||
/>
|
||||
<View style={styles.divider} marginT-8 />
|
||||
|
||||
<View row spread marginB-10 marginL-30 centerV>
|
||||
<View row>
|
||||
<AntDesign name="clockcircleo" size={24} color="#919191" />
|
||||
<Text text70 marginL-10>
|
||||
All day
|
||||
</Text>
|
||||
</View>
|
||||
<View right marginR-30>
|
||||
<Switch
|
||||
onColor={"#ea156c"}
|
||||
offColor={"#e1e1e2"}
|
||||
marginL-10
|
||||
value={event.allDay}
|
||||
onValueChange={(value) =>
|
||||
setEvent((prev) => ({ ...prev, allDay: value }))
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View marginL-30 centerV>
|
||||
<View row marginB-10 spread>
|
||||
<View row centerV>
|
||||
<Feather name="calendar" size={25} color="#919191" />
|
||||
<DateTimePicker
|
||||
value={event.start}
|
||||
text70
|
||||
marginL-8
|
||||
maximumDate={event.end}
|
||||
onChange={(date) => {
|
||||
setEvent((prev) => ({ ...prev, start: date }));
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<DateTimePicker
|
||||
text70
|
||||
value={event.start}
|
||||
onChange={(date) => {
|
||||
setEvent((prev) => ({ ...prev, start: date }));
|
||||
}}
|
||||
maximumDate={event.end}
|
||||
dateTimeFormatter={(date, mode) => date.toLocaleTimeString("en-us",
|
||||
{ hour: "numeric",
|
||||
minute: "numeric"
|
||||
})}
|
||||
mode="time"
|
||||
marginR-30
|
||||
/>
|
||||
</View>
|
||||
|
||||
{!event.allDay && (
|
||||
<View row marginB-10 spread>
|
||||
<View row centerV>
|
||||
<Feather name="calendar" size={25} color="#919191" />
|
||||
<DateTimePicker
|
||||
value={event.end}
|
||||
minimumDate={event.start}
|
||||
text70
|
||||
marginL-8
|
||||
onChange={(date) => {
|
||||
setEvent((prev) => ({ ...prev, end: date }));
|
||||
}}
|
||||
/>
|
||||
visible={isVisible}
|
||||
>
|
||||
<View row spread>
|
||||
<Button
|
||||
color="#05a8b6"
|
||||
style={styles.topBtn}
|
||||
label="Cancel"
|
||||
onPress={() => {
|
||||
setIsVisible(false);
|
||||
}}
|
||||
/>
|
||||
<View marginT-12>
|
||||
<DropModalIcon
|
||||
onPress={() => {
|
||||
setIsVisible(false);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<Button
|
||||
color="#05a8b6"
|
||||
style={styles.topBtn}
|
||||
label="Save"
|
||||
onPress={() => {
|
||||
try {
|
||||
if (event.id) {
|
||||
updateEvent(event).then(() => setIsVisible(false));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<DateTimePicker
|
||||
text70
|
||||
value={event.end}
|
||||
minimumDate={event.start}
|
||||
onChange={(date) => {
|
||||
setEvent((prev) => ({ ...prev, end: date }));
|
||||
}}
|
||||
dateTimeFormatter={(date, mode) => date.toLocaleTimeString("en-us",
|
||||
{ hour: "numeric",
|
||||
minute: "numeric"
|
||||
})}
|
||||
mode="time"
|
||||
marginR-30
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<View style={styles.divider} />
|
||||
|
||||
<View marginH-30 marginB-10 row centerV>
|
||||
<Ionicons name="person-circle-outline" size={28} color="#919191" />
|
||||
<Text text70R marginL-10>
|
||||
Assignees
|
||||
</Text>
|
||||
<Button
|
||||
size={ButtonSize.small}
|
||||
paddingH-8
|
||||
iconSource={() => (
|
||||
<Ionicons name="add-outline" size={20} color="#ea156c" />
|
||||
)}
|
||||
style={{
|
||||
marginLeft: "auto",
|
||||
borderRadius: 8,
|
||||
backgroundColor: "#ffe8f1",
|
||||
borderColor: "#ea156c",
|
||||
borderWidth: 1,
|
||||
}}
|
||||
color="#ea156c"
|
||||
label="Assign"
|
||||
/>
|
||||
</View>
|
||||
<View row marginH-13 marginT-13>
|
||||
<View
|
||||
marginL-30
|
||||
style={{
|
||||
aspectRatio: 1,
|
||||
width: 50,
|
||||
backgroundColor: "red",
|
||||
borderRadius: 50,
|
||||
}}
|
||||
/>
|
||||
<View
|
||||
marginL-30
|
||||
style={{
|
||||
aspectRatio: 1,
|
||||
width: 50,
|
||||
backgroundColor: "red",
|
||||
borderRadius: 50,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.divider} />
|
||||
<View marginH-30 marginB-0 row spread centerV>
|
||||
<View row centerV>
|
||||
<ClockIcon />
|
||||
<Text text70 marginL-10>
|
||||
Reminder
|
||||
</Text>
|
||||
</View>
|
||||
<View>
|
||||
<Button
|
||||
size={ButtonSize.small}
|
||||
paddingH-8
|
||||
iconSource={() => (
|
||||
<Ionicons name="add-outline" size={20} color="#ea156c" />
|
||||
)}
|
||||
style={{
|
||||
marginLeft: "auto",
|
||||
borderRadius: 8,
|
||||
backgroundColor: "#ffe8f1",
|
||||
borderColor: "#ea156c",
|
||||
borderWidth: 1,
|
||||
}}
|
||||
color="#ea156c"
|
||||
label="Set Reminder"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.divider} />
|
||||
<View marginH-30 marginB-0 row spread centerV>
|
||||
<View row>
|
||||
<LockIcon />
|
||||
<Text text70 marginL-10>
|
||||
Mark as Private
|
||||
</Text>
|
||||
</View>
|
||||
<View>
|
||||
<Switch
|
||||
onColor={"#ea156c"}
|
||||
offColor={"#e1e1e2"}
|
||||
marginL-10
|
||||
value={event.private}
|
||||
onValueChange={(value) =>
|
||||
setEvent((prev) => ({ ...prev, private: value }))
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.divider} />
|
||||
<View marginH-30 marginB-0 row spread centerV>
|
||||
<View row centerV>
|
||||
<MenuIcon />
|
||||
<Text text70 marginL-10>
|
||||
Add Details
|
||||
</Text>
|
||||
</View>
|
||||
<View></View>
|
||||
</View>
|
||||
</Dialog>
|
||||
);
|
||||
<TextField
|
||||
placeholder="Edit event title"
|
||||
value={event.title}
|
||||
onChangeText={(text) => {
|
||||
setEvent((prevEvent) => ({
|
||||
...prevEvent!,
|
||||
title: text,
|
||||
}));
|
||||
}}
|
||||
placeholderTextColor="#2d2d30"
|
||||
text60R
|
||||
marginT-15
|
||||
marginL-30
|
||||
/>
|
||||
<View style={styles.divider} marginT-8/>
|
||||
|
||||
<View row spread marginB-10 marginL-30 centerV>
|
||||
<View row>
|
||||
<AntDesign name="clockcircleo" size={24} color="#919191"/>
|
||||
<Text text70 marginL-10>
|
||||
All day
|
||||
</Text>
|
||||
</View>
|
||||
<View right marginR-30>
|
||||
<Switch
|
||||
onColor={"#ea156c"}
|
||||
offColor={"#e1e1e2"}
|
||||
marginL-10
|
||||
value={event.allDay}
|
||||
onValueChange={(value) =>
|
||||
setEvent((prev) => ({...prev!, allDay: value}))
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View marginL-30 centerV>
|
||||
<View row marginB-10 spread>
|
||||
<View row centerV>
|
||||
<Feather name="calendar" size={25} color="#919191"/>
|
||||
<DateTimePicker
|
||||
value={event.start}
|
||||
text70
|
||||
marginL-8
|
||||
maximumDate={event.end}
|
||||
onChange={(date) => {
|
||||
setEvent((prev) => ({...prev!, start: date}));
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<DateTimePicker
|
||||
text70
|
||||
value={event.start}
|
||||
onChange={(date) => {
|
||||
setEvent((prev) => ({...prev!, start: date}));
|
||||
}}
|
||||
maximumDate={event.end}
|
||||
dateTimeFormatter={(date) => date.toLocaleTimeString("en-us",
|
||||
{
|
||||
hour: "numeric",
|
||||
minute: "numeric"
|
||||
})}
|
||||
mode="time"
|
||||
marginR-30
|
||||
/>
|
||||
</View>
|
||||
|
||||
{!event.allDay && (
|
||||
<View row marginB-10 spread>
|
||||
<View row centerV>
|
||||
<Feather name="calendar" size={25} color="#919191"/>
|
||||
<DateTimePicker
|
||||
value={event.end}
|
||||
minimumDate={event.start}
|
||||
text70
|
||||
marginL-8
|
||||
onChange={(date) => {
|
||||
setEvent((prev) => ({...prev!, end: date}));
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<DateTimePicker
|
||||
text70
|
||||
value={event.end}
|
||||
minimumDate={event.start}
|
||||
onChange={(date) => {
|
||||
setEvent((prev) => ({...prev!, end: date}));
|
||||
}}
|
||||
dateTimeFormatter={(date) => date.toLocaleTimeString("en-us",
|
||||
{
|
||||
hour: "numeric",
|
||||
minute: "numeric"
|
||||
})}
|
||||
mode="time"
|
||||
marginR-30
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<View style={styles.divider}/>
|
||||
|
||||
<View marginH-30 marginB-10 row centerV>
|
||||
<Ionicons name="person-circle-outline" size={28} color="#919191"/>
|
||||
<Text text70R marginL-10>
|
||||
Assignees
|
||||
</Text>
|
||||
<Button
|
||||
size={ButtonSize.small}
|
||||
paddingH-8
|
||||
iconSource={() => (
|
||||
<Ionicons name="add-outline" size={20} color="#ea156c"/>
|
||||
)}
|
||||
style={{
|
||||
marginLeft: "auto",
|
||||
borderRadius: 8,
|
||||
backgroundColor: "#ffe8f1",
|
||||
borderColor: "#ea156c",
|
||||
borderWidth: 1,
|
||||
}}
|
||||
color="#ea156c"
|
||||
label="Assign"
|
||||
/>
|
||||
</View>
|
||||
<View row marginH-13 marginT-13>
|
||||
<View
|
||||
marginL-30
|
||||
style={{
|
||||
aspectRatio: 1,
|
||||
width: 50,
|
||||
backgroundColor: "red",
|
||||
borderRadius: 50,
|
||||
}}
|
||||
/>
|
||||
<View
|
||||
marginL-30
|
||||
style={{
|
||||
aspectRatio: 1,
|
||||
width: 50,
|
||||
backgroundColor: "red",
|
||||
borderRadius: 50,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.divider}/>
|
||||
<View marginH-30 marginB-0 row spread centerV>
|
||||
<View row centerV>
|
||||
<ClockIcon/>
|
||||
<Text text70 marginL-10>
|
||||
Reminder
|
||||
</Text>
|
||||
</View>
|
||||
<View>
|
||||
<Button
|
||||
size={ButtonSize.small}
|
||||
paddingH-8
|
||||
iconSource={() => (
|
||||
<Ionicons name="add-outline" size={20} color="#ea156c"/>
|
||||
)}
|
||||
style={{
|
||||
marginLeft: "auto",
|
||||
borderRadius: 8,
|
||||
backgroundColor: "#ffe8f1",
|
||||
borderColor: "#ea156c",
|
||||
borderWidth: 1,
|
||||
}}
|
||||
color="#ea156c"
|
||||
label="Set Reminder"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.divider}/>
|
||||
<View marginH-30 marginB-0 row spread centerV>
|
||||
<View row>
|
||||
<LockIcon/>
|
||||
<Text text70 marginL-10>
|
||||
Mark as Private
|
||||
</Text>
|
||||
</View>
|
||||
<View>
|
||||
<Switch
|
||||
onColor={"#ea156c"}
|
||||
offColor={"#e1e1e2"}
|
||||
marginL-10
|
||||
value={event.private}
|
||||
onValueChange={(value) =>
|
||||
setEvent((prev) => ({...prev!, private: value}))
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.divider}/>
|
||||
<View marginH-30 marginB-0 row spread centerV>
|
||||
<View row centerV>
|
||||
<MenuIcon/>
|
||||
<Text text70 marginL-10>
|
||||
Add Details
|
||||
</Text>
|
||||
</View>
|
||||
<View></View>
|
||||
</View>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditEventDialog;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
divider: { height: 1, backgroundColor: "#e4e4e4", marginVertical: 15 },
|
||||
gradient: {
|
||||
height: "25%",
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
width: "100%",
|
||||
},
|
||||
buttonContainer: {
|
||||
position: "absolute",
|
||||
bottom: 25,
|
||||
width: "100%",
|
||||
},
|
||||
button: {
|
||||
backgroundColor: "rgb(253, 23, 117)",
|
||||
paddingVertical: 20,
|
||||
},
|
||||
topBtn: {
|
||||
backgroundColor: "white",
|
||||
color: "#05a8b6",
|
||||
},
|
||||
rotateSwitch: {
|
||||
marginLeft: 35,
|
||||
marginBottom: 10,
|
||||
marginTop: 25,
|
||||
},
|
||||
divider: {height: 1, backgroundColor: "#e4e4e4", marginVertical: 15},
|
||||
gradient: {
|
||||
height: "25%",
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
width: "100%",
|
||||
},
|
||||
buttonContainer: {
|
||||
position: "absolute",
|
||||
bottom: 25,
|
||||
width: "100%",
|
||||
},
|
||||
button: {
|
||||
backgroundColor: "rgb(253, 23, 117)",
|
||||
paddingVertical: 20,
|
||||
},
|
||||
topBtn: {
|
||||
backgroundColor: "white",
|
||||
color: "#05a8b6",
|
||||
},
|
||||
rotateSwitch: {
|
||||
marginLeft: 35,
|
||||
marginBottom: 10,
|
||||
marginTop: 25,
|
||||
},
|
||||
});
|
||||
|
||||
71
components/pages/calendar/EventCalendar.tsx
Normal file
71
components/pages/calendar/EventCalendar.tsx
Normal file
@ -0,0 +1,71 @@
|
||||
import React, {memo} from 'react';
|
||||
import {Calendar} from "react-native-big-calendar";
|
||||
import {StyleSheet} from "react-native";
|
||||
import {useGetEvents} from "@/hooks/firebase/useGetEvents";
|
||||
import {useAtom, useAtomValue, useSetAtom} from "jotai";
|
||||
import {
|
||||
editVisibleAtom,
|
||||
eventForEditAtom,
|
||||
modeAtom,
|
||||
selectedDateAtom,
|
||||
selectedNewEventDateAtom
|
||||
} from "@/components/pages/calendar/atoms";
|
||||
import {useAuthContext} from "@/contexts/AuthContext";
|
||||
|
||||
interface EventCalendarProps {
|
||||
calendarHeight: number;
|
||||
}
|
||||
|
||||
export const EventCalendar: React.FC<EventCalendarProps> = memo(({calendarHeight}) => {
|
||||
const {data: events} = useGetEvents();
|
||||
const {profileData} = useAuthContext()
|
||||
const [selectedDate, setSelectedDate] = useAtom(selectedDateAtom)
|
||||
const [mode, setMode] = useAtom(modeAtom)
|
||||
const setEditVisible = useSetAtom(editVisibleAtom)
|
||||
const setEventForEdit = useSetAtom(eventForEditAtom)
|
||||
const setSelectedNewEndDate = useSetAtom(selectedNewEventDateAtom)
|
||||
|
||||
console.log("Events: ", events)
|
||||
|
||||
return (
|
||||
<Calendar
|
||||
bodyContainerStyle={styles.calHeader}
|
||||
mode={mode}
|
||||
events={events ?? []}
|
||||
eventCellStyle={(event) => ({backgroundColor: event.eventColor})}
|
||||
onPressEvent={(event) => {
|
||||
setEditVisible(true);
|
||||
setEventForEdit(event);
|
||||
}}
|
||||
weekStartsOn={profileData?.firstDayOfWeek === "Mondays" ? 1 : 0}
|
||||
height={calendarHeight}
|
||||
activeDate={selectedDate}
|
||||
date={selectedDate}
|
||||
onPressCell={mode === "day" ? setSelectedNewEndDate: (date) => {
|
||||
setSelectedDate(date)
|
||||
setMode("day")
|
||||
}}
|
||||
headerContentStyle={mode === "day" ? styles.dayModeHeader : {}}
|
||||
onSwipeEnd={(date) => {
|
||||
setSelectedDate(date);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
segmentslblStyle: {
|
||||
fontSize: 12,
|
||||
fontFamily: "Manrope_600SemiBold",
|
||||
},
|
||||
calHeader: {
|
||||
borderWidth: 0,
|
||||
},
|
||||
dayModeHeader: {
|
||||
alignSelf: "flex-start",
|
||||
justifyContent: "space-between",
|
||||
alignContent: "center",
|
||||
width: 38,
|
||||
right: 42,
|
||||
},
|
||||
});
|
||||
41
components/pages/calendar/InnerCalendar.tsx
Normal file
41
components/pages/calendar/InnerCalendar.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
import {View} from "react-native-ui-lib";
|
||||
import React, {useRef, useState} from "react";
|
||||
import {LayoutChangeEvent} from "react-native";
|
||||
import CalendarViewSwitch from "@/components/pages/calendar/CalendarViewSwitch";
|
||||
import {AddEventDialog} from "@/components/pages/calendar/AddEventDialog";
|
||||
import EditEventDialog from "@/components/pages/calendar/EditEventDialog";
|
||||
import {ManuallyAddEventModal} from "@/components/pages/calendar/ManuallyAddEventModal";
|
||||
import {CalendarHeader} from "@/components/pages/calendar/CalendarHeader";
|
||||
import {EventCalendar} from "@/components/pages/calendar/EventCalendar";
|
||||
|
||||
export const InnerCalendar = () => {
|
||||
const [calendarHeight, setCalendarHeight] = useState(0);
|
||||
const calendarContainerRef = useRef(null);
|
||||
|
||||
const onLayout = (event: LayoutChangeEvent) => {
|
||||
const {height} = event.nativeEvent.layout;
|
||||
setCalendarHeight(height);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<View
|
||||
style={{flex: 1, backgroundColor: "#fff", borderRadius: 30, marginBottom: 60}}
|
||||
ref={calendarContainerRef}
|
||||
onLayout={onLayout}
|
||||
>
|
||||
<CalendarHeader/>
|
||||
{calendarHeight > 0 && (
|
||||
<EventCalendar
|
||||
calendarHeight={calendarHeight}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<CalendarViewSwitch/>
|
||||
|
||||
<AddEventDialog/>
|
||||
<EditEventDialog/>
|
||||
<ManuallyAddEventModal/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -1,488 +1,508 @@
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
ButtonSize,
|
||||
Colors,
|
||||
DateTimePicker,
|
||||
LoaderScreen,
|
||||
Modal,
|
||||
Picker,
|
||||
Switch,
|
||||
Text,
|
||||
TextField,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
Button,
|
||||
ButtonSize,
|
||||
Colors,
|
||||
DateTimePicker,
|
||||
LoaderScreen,
|
||||
Modal,
|
||||
Picker,
|
||||
PickerModes,
|
||||
Switch,
|
||||
Text,
|
||||
TextField,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from "react-native-ui-lib";
|
||||
import { ScrollView } from "react-native-gesture-handler";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
AntDesign,
|
||||
Feather,
|
||||
Ionicons,
|
||||
MaterialIcons,
|
||||
} from "@expo/vector-icons";
|
||||
import { PickerMultiValue } from "react-native-ui-lib/src/components/picker/types";
|
||||
import { useAuthContext } from "@/contexts/AuthContext";
|
||||
import { useCreateEvent } from "@/hooks/firebase/useCreateEvent";
|
||||
import { EventData } from "@/hooks/firebase/types/eventData";
|
||||
import { addHours, setDate } from "date-fns";
|
||||
import {ScrollView} from "react-native-gesture-handler";
|
||||
import {useSafeAreaInsets} from "react-native-safe-area-context";
|
||||
import {useState} from "react";
|
||||
import {AntDesign, Feather, Ionicons,} from "@expo/vector-icons";
|
||||
import {PickerMultiValue} from "react-native-ui-lib/src/components/picker/types";
|
||||
import {useCreateEvent} from "@/hooks/firebase/useCreateEvent";
|
||||
import {EventData} from "@/hooks/firebase/types/eventData";
|
||||
import {addHours} from "date-fns";
|
||||
import DropModalIcon from "@/assets/svgs/DropModalIcon";
|
||||
import { CalendarEvent, useCalendarContext } from "@/contexts/CalendarContext";
|
||||
import { repeatOptions } from "@/contexts/ToDosContext";
|
||||
import { ImageBackground, StyleSheet } from "react-native";
|
||||
import {StyleSheet} from "react-native";
|
||||
import ClockIcon from "@/assets/svgs/ClockIcon";
|
||||
import LockIcon from "@/assets/svgs/LockIcon";
|
||||
import MenuIcon from "@/assets/svgs/MenuIcon";
|
||||
import CameraIcon from "@/assets/svgs/CameraIcon";
|
||||
import AssigneesDisplay from "@/components/shared/AssigneesDisplay";
|
||||
import {useAtom} from "jotai";
|
||||
import {selectedNewEventDateAtom} from "@/components/pages/calendar/atoms";
|
||||
import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers";
|
||||
|
||||
const daysOfWeek = [
|
||||
{ label: "Monday", value: "monday" },
|
||||
{ label: "Tuesday", value: "tuesday" },
|
||||
{ label: "Wednesday", value: "wednesday" },
|
||||
{ label: "Thursday", value: "thursday" },
|
||||
{ label: "Friday", value: "friday" },
|
||||
{ label: "Saturday", value: "saturday" },
|
||||
{ label: "Sunday", value: "sunday" },
|
||||
{label: "Monday", value: "monday"},
|
||||
{label: "Tuesday", value: "tuesday"},
|
||||
{label: "Wednesday", value: "wednesday"},
|
||||
{label: "Thursday", value: "thursday"},
|
||||
{label: "Friday", value: "friday"},
|
||||
{label: "Saturday", value: "saturday"},
|
||||
{label: "Sunday", value: "sunday"},
|
||||
];
|
||||
|
||||
export const ManuallyAddEventModal = ({
|
||||
show,
|
||||
close,
|
||||
initialDate,
|
||||
}: {
|
||||
show: boolean;
|
||||
close: () => void;
|
||||
initialDate?: Date;
|
||||
}) => {
|
||||
const { addEvent } = useCalendarContext();
|
||||
const { user } = useAuthContext();
|
||||
const insets = useSafeAreaInsets();
|
||||
export const ManuallyAddEventModal = () => {
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const [title, setTitle] = useState<string>("");
|
||||
const [selectedNewEventDate, setSelectedNewEndDate] = useAtom(selectedNewEventDateAtom)
|
||||
|
||||
const [isAllDay, setIsAllDay] = useState(false);
|
||||
const [isPrivate, setIsPrivate] = useState<boolean>(false);
|
||||
const [startTime, setStartTime] = useState(() => {
|
||||
const date = initialDate ?? new Date();
|
||||
date.setSeconds(0, 0);
|
||||
return date;
|
||||
});
|
||||
const [endTime, setEndTime] = useState(() => {
|
||||
const date = initialDate
|
||||
? addHours(initialDate, 1)
|
||||
: addHours(new Date(), 1);
|
||||
date.setSeconds(0, 0);
|
||||
return date;
|
||||
});
|
||||
|
||||
const [startDate, setStartDate] = useState(initialDate ?? new Date());
|
||||
const [endDate, setEndDate] = useState(initialDate ?? new Date());
|
||||
|
||||
const [repeatInterval, setRepeatInterval] = useState<PickerMultiValue>([]);
|
||||
|
||||
const { mutateAsync: createEvent, isLoading, isError } = useCreateEvent();
|
||||
|
||||
const formatDateTime = (date?: Date | string) => {
|
||||
if (!date) return undefined;
|
||||
return new Date(date).toLocaleDateString("en-US", {
|
||||
weekday: "long",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
};
|
||||
|
||||
const combineDateAndTime = (date: Date, time: Date): Date => {
|
||||
const combined = new Date(date);
|
||||
combined.setHours(time.getHours());
|
||||
combined.setMinutes(time.getMinutes());
|
||||
combined.setSeconds(0);
|
||||
combined.setMilliseconds(0);
|
||||
return combined;
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
let finalStartDate: Date;
|
||||
let finalEndDate: Date;
|
||||
|
||||
if (isAllDay) {
|
||||
finalStartDate = new Date(startDate.setHours(0, 0, 0, 0));
|
||||
finalEndDate = new Date(startDate.setHours(0, 0, 0, 0));
|
||||
} else {
|
||||
finalStartDate = combineDateAndTime(startDate, startTime);
|
||||
finalEndDate = combineDateAndTime(endDate, endTime);
|
||||
const {show, close, initialDate} = {
|
||||
show: !!selectedNewEventDate,
|
||||
close: () => setSelectedNewEndDate(undefined),
|
||||
initialDate: selectedNewEventDate
|
||||
}
|
||||
|
||||
const eventData: Partial<EventData> = {
|
||||
title: title,
|
||||
startDate: finalStartDate,
|
||||
endDate: finalEndDate,
|
||||
allDay: isAllDay,
|
||||
const [title, setTitle] = useState<string>("");
|
||||
|
||||
const [isAllDay, setIsAllDay] = useState(false);
|
||||
const [isPrivate, setIsPrivate] = useState<boolean>(false);
|
||||
const [startTime, setStartTime] = useState(() => {
|
||||
const date = initialDate ?? new Date();
|
||||
date.setSeconds(0, 0);
|
||||
return date;
|
||||
});
|
||||
const [endTime, setEndTime] = useState(() => {
|
||||
const date = initialDate
|
||||
? addHours(initialDate, 1)
|
||||
: addHours(new Date(), 1);
|
||||
date.setSeconds(0, 0);
|
||||
return date;
|
||||
});
|
||||
|
||||
const [startDate, setStartDate] = useState(initialDate ?? new Date());
|
||||
const [endDate, setEndDate] = useState(initialDate ?? new Date());
|
||||
|
||||
const [selectedAttendees, setSelectedAttendees] = useState<string[]>([]);
|
||||
|
||||
const [repeatInterval, setRepeatInterval] = useState<PickerMultiValue>([]);
|
||||
|
||||
const {mutateAsync: createEvent, isLoading, isError} = useCreateEvent();
|
||||
const {data: members} = useGetFamilyMembers(true)
|
||||
|
||||
|
||||
if (!selectedNewEventDate) return null;
|
||||
|
||||
const formatDateTime = (date?: Date | string) => {
|
||||
if (!date) return undefined;
|
||||
return new Date(date).toLocaleDateString("en-US", {
|
||||
weekday: "long",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
};
|
||||
|
||||
await createEvent(eventData);
|
||||
const combineDateAndTime = (date: Date, time: Date): Date => {
|
||||
const combined = new Date(date);
|
||||
combined.setHours(time.getHours());
|
||||
combined.setMinutes(time.getMinutes());
|
||||
combined.setSeconds(0);
|
||||
combined.setMilliseconds(0);
|
||||
return combined;
|
||||
};
|
||||
|
||||
close();
|
||||
};
|
||||
const handleSave = async () => {
|
||||
let finalStartDate: Date;
|
||||
let finalEndDate: Date;
|
||||
|
||||
const getRepeatLabel = () => {
|
||||
const selectedDays = repeatInterval;
|
||||
const allDays = [
|
||||
"sunday",
|
||||
"monday",
|
||||
"tuesday",
|
||||
"wednesday",
|
||||
"thursday",
|
||||
"friday",
|
||||
"saturday",
|
||||
];
|
||||
const workDays = ["monday", "tuesday", "wednesday", "thursday", "friday"];
|
||||
if (isAllDay) {
|
||||
finalStartDate = new Date(startDate.setHours(0, 0, 0, 0));
|
||||
finalEndDate = new Date(startDate.setHours(0, 0, 0, 0));
|
||||
} else {
|
||||
finalStartDate = combineDateAndTime(startDate, startTime);
|
||||
finalEndDate = combineDateAndTime(endDate, endTime);
|
||||
}
|
||||
|
||||
const isEveryWorkDay = workDays.every((day) => selectedDays.includes(day));
|
||||
const eventData: Partial<EventData> = {
|
||||
title: title,
|
||||
startDate: finalStartDate,
|
||||
endDate: finalEndDate,
|
||||
allDay: isAllDay,
|
||||
attendees: selectedAttendees,
|
||||
};
|
||||
|
||||
const isEveryDay = allDays.every((day) => selectedDays.includes(day));
|
||||
await createEvent(eventData);
|
||||
|
||||
if (isEveryDay) {
|
||||
return "Every day";
|
||||
} else if (
|
||||
isEveryWorkDay &&
|
||||
!selectedDays.includes("saturday") &&
|
||||
!selectedDays.includes("sunday")
|
||||
) {
|
||||
return "Every work day";
|
||||
} else {
|
||||
return selectedDays
|
||||
.map((item) => daysOfWeek.find((day) => day.value === item)?.label)
|
||||
.join(", ");
|
||||
close();
|
||||
};
|
||||
|
||||
const getRepeatLabel = () => {
|
||||
const selectedDays = repeatInterval;
|
||||
const allDays = [
|
||||
"sunday",
|
||||
"monday",
|
||||
"tuesday",
|
||||
"wednesday",
|
||||
"thursday",
|
||||
"friday",
|
||||
"saturday",
|
||||
];
|
||||
const workDays = ["monday", "tuesday", "wednesday", "thursday", "friday"];
|
||||
|
||||
const isEveryWorkDay = workDays.every((day) => selectedDays.includes(day));
|
||||
|
||||
const isEveryDay = allDays.every((day) => selectedDays.includes(day));
|
||||
|
||||
if (isEveryDay) {
|
||||
return "Every day";
|
||||
} else if (
|
||||
isEveryWorkDay &&
|
||||
!selectedDays.includes("saturday") &&
|
||||
!selectedDays.includes("sunday")
|
||||
) {
|
||||
return "Every work day";
|
||||
} else {
|
||||
return selectedDays
|
||||
.map((item) => daysOfWeek.find((day) => day.value === item)?.label)
|
||||
.join(", ");
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading && !isError) {
|
||||
return (
|
||||
<Modal
|
||||
visible={show}
|
||||
animationType="slide"
|
||||
onRequestClose={close}
|
||||
transparent={false}
|
||||
>
|
||||
<LoaderScreen message={"Saving event..."} color={Colors.grey40}/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading && !isError) {
|
||||
return (
|
||||
<Modal
|
||||
visible={show}
|
||||
animationType="slide"
|
||||
onRequestClose={close}
|
||||
transparent={false}
|
||||
>
|
||||
<LoaderScreen message={"Saving event..."} color={Colors.grey40} />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={show}
|
||||
animationType="slide"
|
||||
onRequestClose={close}
|
||||
transparent={false}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: "#fff",
|
||||
paddingTop: insets.top, // Safe area inset for top
|
||||
paddingBottom: insets.bottom, // Safe area inset for bottom
|
||||
paddingLeft: insets.left, // Safe area inset for left
|
||||
paddingRight: insets.right, // Safe area inset for right
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
padding: 16,
|
||||
}}
|
||||
<Modal
|
||||
visible={show}
|
||||
animationType="slide"
|
||||
onRequestClose={close}
|
||||
transparent={false}
|
||||
>
|
||||
<TouchableOpacity onPress={close}>
|
||||
<Text
|
||||
style={{
|
||||
color: "#05a8b6",
|
||||
fontFamily: "PlusJakartaSans_400Regular",
|
||||
fontSize: 16,
|
||||
}}
|
||||
text70
|
||||
>
|
||||
Cancel
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<DropModalIcon onPress={close} />
|
||||
<TouchableOpacity onPress={handleSave}>
|
||||
<Text
|
||||
style={{
|
||||
color: "#05a8b6",
|
||||
fontFamily: "PlusJakartaSans_400Regular",
|
||||
fontSize: 16,
|
||||
}}
|
||||
text70
|
||||
>
|
||||
Save
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<ScrollView>
|
||||
<TextField
|
||||
placeholder="Add event title"
|
||||
value={title}
|
||||
onChangeText={(text) => {
|
||||
setTitle(text);
|
||||
}}
|
||||
placeholderTextColor="#2d2d30"
|
||||
style={{ fontFamily: "Manrope_500Medium", fontSize: 22 }}
|
||||
paddingT-15
|
||||
paddingL-30
|
||||
/>
|
||||
<View style={styles.divider} marginT-8 />
|
||||
<View marginL-30 centerV>
|
||||
<View row spread marginB-10 centerV>
|
||||
<View row>
|
||||
<AntDesign name="clockcircleo" size={24} color="#919191" />
|
||||
<Text
|
||||
style={{
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 16,
|
||||
}}
|
||||
marginL-10
|
||||
>
|
||||
All day
|
||||
</Text>
|
||||
</View>
|
||||
<View right marginR-30>
|
||||
<Switch
|
||||
onColor={"#ea156c"}
|
||||
offColor={"#e1e1e2"}
|
||||
marginL-10
|
||||
value={isAllDay}
|
||||
onValueChange={(value) => setIsAllDay(value)}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View row marginB-10 spread>
|
||||
<View row centerV>
|
||||
<Feather name="calendar" size={25} color="#919191" />
|
||||
<DateTimePicker
|
||||
value={startDate}
|
||||
onChange={(date) => {
|
||||
setStartDate(date);
|
||||
}}
|
||||
maximumDate={endDate}
|
||||
style={{
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 16,
|
||||
}}
|
||||
marginL-8
|
||||
/>
|
||||
</View>
|
||||
<DateTimePicker
|
||||
value={startTime}
|
||||
onChange={(date) => setStartTime(date)}
|
||||
maximumDate={endTime}
|
||||
minuteInterval={5}
|
||||
dateTimeFormatter={(date, mode) =>
|
||||
date.toLocaleTimeString("en-us", {
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
})
|
||||
}
|
||||
mode="time"
|
||||
<View
|
||||
style={{
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 16,
|
||||
flex: 1,
|
||||
backgroundColor: "#fff",
|
||||
paddingTop: insets.top, // Safe area inset for top
|
||||
paddingBottom: insets.bottom, // Safe area inset for bottom
|
||||
paddingLeft: insets.left, // Safe area inset for left
|
||||
paddingRight: insets.right, // Safe area inset for right
|
||||
}}
|
||||
marginR-30
|
||||
/>
|
||||
</View>
|
||||
|
||||
{!isAllDay && (
|
||||
<View row marginB-10 spread>
|
||||
<View row centerV>
|
||||
<Feather name="calendar" size={25} color="#919191" />
|
||||
<DateTimePicker
|
||||
value={endDate}
|
||||
minimumDate={startDate}
|
||||
text70
|
||||
marginL-8
|
||||
onChange={(date) => {
|
||||
setEndDate(date);
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 16,
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
padding: 16,
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<TouchableOpacity onPress={close}>
|
||||
<Text
|
||||
style={{
|
||||
color: "#05a8b6",
|
||||
fontFamily: "PlusJakartaSans_400Regular",
|
||||
fontSize: 16,
|
||||
}}
|
||||
text70
|
||||
>
|
||||
Cancel
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<DropModalIcon onPress={close}/>
|
||||
<TouchableOpacity onPress={handleSave}>
|
||||
<Text
|
||||
style={{
|
||||
color: "#05a8b6",
|
||||
fontFamily: "PlusJakartaSans_400Regular",
|
||||
fontSize: 16,
|
||||
}}
|
||||
text70
|
||||
>
|
||||
Save
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<DateTimePicker
|
||||
value={endTime}
|
||||
onChange={(date) => setEndTime(date)}
|
||||
minimumDate={startTime}
|
||||
minuteInterval={5}
|
||||
dateTimeFormatter={(date, mode) =>
|
||||
date.toLocaleTimeString("en-us", {
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
})
|
||||
}
|
||||
mode="time"
|
||||
style={{
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 16,
|
||||
}}
|
||||
marginR-30
|
||||
<ScrollView>
|
||||
<TextField
|
||||
placeholder="Add event title"
|
||||
value={title}
|
||||
autoFocus
|
||||
onChangeText={(text) => {
|
||||
setTitle(text);
|
||||
}}
|
||||
placeholderTextColor="#2d2d30"
|
||||
style={{fontFamily: "Manrope_500Medium", fontSize: 22}}
|
||||
paddingT-15
|
||||
paddingL-30
|
||||
returnKeyType="next"
|
||||
/>
|
||||
<View style={styles.divider} marginT-8/>
|
||||
<View marginL-30 centerV>
|
||||
<View row spread marginB-10 centerV>
|
||||
<View row>
|
||||
<AntDesign name="clockcircleo" size={24} color="#919191"/>
|
||||
<Text
|
||||
style={{
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 16,
|
||||
}}
|
||||
marginL-10
|
||||
>
|
||||
All day
|
||||
</Text>
|
||||
</View>
|
||||
<View right marginR-30>
|
||||
<Switch
|
||||
onColor={"#ea156c"}
|
||||
offColor={"#e1e1e2"}
|
||||
marginL-10
|
||||
value={isAllDay}
|
||||
onValueChange={(value) => setIsAllDay(value)}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View row marginB-10 spread>
|
||||
<View row centerV>
|
||||
<Feather name="calendar" size={25} color="#919191"/>
|
||||
<DateTimePicker
|
||||
value={startDate}
|
||||
onChange={(date) => {
|
||||
setStartDate(date);
|
||||
}}
|
||||
maximumDate={endDate}
|
||||
style={{
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 16,
|
||||
}}
|
||||
marginL-8
|
||||
/>
|
||||
</View>
|
||||
<DateTimePicker
|
||||
value={startTime}
|
||||
onChange={(date) => setStartTime(date)}
|
||||
maximumDate={endTime}
|
||||
minuteInterval={5}
|
||||
dateTimeFormatter={(date, mode) =>
|
||||
date.toLocaleTimeString("en-us", {
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
})
|
||||
}
|
||||
mode="time"
|
||||
style={{
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 16,
|
||||
}}
|
||||
marginR-30
|
||||
/>
|
||||
</View>
|
||||
|
||||
{!isAllDay && (
|
||||
<View row marginB-10 spread>
|
||||
<View row centerV>
|
||||
<Feather name="calendar" size={25} color="#919191"/>
|
||||
<DateTimePicker
|
||||
value={endDate}
|
||||
minimumDate={startDate}
|
||||
text70
|
||||
marginL-8
|
||||
onChange={(date) => {
|
||||
setEndDate(date);
|
||||
}}
|
||||
style={{
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 16,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<DateTimePicker
|
||||
value={endTime}
|
||||
onChange={(date) => setEndTime(date)}
|
||||
minimumDate={startTime}
|
||||
minuteInterval={5}
|
||||
dateTimeFormatter={(date, mode) =>
|
||||
date.toLocaleTimeString("en-us", {
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
})
|
||||
}
|
||||
mode="time"
|
||||
style={{
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 16,
|
||||
}}
|
||||
marginR-30
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View style={styles.divider}/>
|
||||
|
||||
<View marginH-30 marginB-10 row centerV>
|
||||
<Ionicons name="person-circle-outline" size={28} color="#919191"/>
|
||||
<Text
|
||||
style={{fontFamily: "Manrope_600SemiBold", fontSize: 18}}
|
||||
marginL-10
|
||||
>
|
||||
Attendees
|
||||
</Text>
|
||||
<View flex-1/>
|
||||
<Picker
|
||||
value={selectedAttendees}
|
||||
onChange={(value) => setSelectedAttendees(value as string[] ?? [])}
|
||||
style={{marginLeft: "auto"}}
|
||||
mode={PickerModes.MULTI}
|
||||
renderInput={() =>
|
||||
<Button
|
||||
size={ButtonSize.small}
|
||||
paddingH-8
|
||||
iconSource={() => (
|
||||
<Ionicons name="add-outline" size={20} color="#ea156c"/>
|
||||
)}
|
||||
style={{
|
||||
marginLeft: "auto",
|
||||
borderRadius: 8,
|
||||
backgroundColor: "#ffe8f1",
|
||||
borderColor: "#ea156c",
|
||||
borderWidth: 1,
|
||||
}}
|
||||
color="#ea156c"
|
||||
label="Add"
|
||||
labelStyle={{fontFamily: "Manrope_600SemiBold", fontSize: 14}}
|
||||
/>
|
||||
}>
|
||||
{members?.map((member) => (
|
||||
<Picker.Item
|
||||
key={member?.uid}
|
||||
value={member?.uid!}
|
||||
label={member?.firstName + " " + member?.lastName}
|
||||
/>
|
||||
))}
|
||||
</Picker>
|
||||
</View>
|
||||
|
||||
<View marginL-35>
|
||||
<AssigneesDisplay setSlectedAttendees={setSelectedAttendees} selectedAttendees={selectedAttendees}/>
|
||||
</View>
|
||||
|
||||
<View style={styles.divider}/>
|
||||
<View marginH-30 marginB-0 row spread centerV>
|
||||
<View row centerV>
|
||||
<ClockIcon/>
|
||||
<Text
|
||||
style={{
|
||||
fontFamily: "Manrope_600SemiBold",
|
||||
fontSize: 18,
|
||||
}}
|
||||
marginL-10
|
||||
>
|
||||
Reminders
|
||||
</Text>
|
||||
</View>
|
||||
<View>
|
||||
<Button
|
||||
size={ButtonSize.small}
|
||||
paddingH-8
|
||||
iconSource={() => (
|
||||
<Ionicons name="add-outline" size={20} color="#ea156c"/>
|
||||
)}
|
||||
style={{
|
||||
marginLeft: "auto",
|
||||
borderRadius: 8,
|
||||
backgroundColor: "#ffe8f1",
|
||||
borderColor: "#ea156c",
|
||||
borderWidth: 1,
|
||||
}}
|
||||
labelStyle={{fontFamily: "Manrope_600SemiBold", fontSize: 14}}
|
||||
color="#ea156c"
|
||||
label="Set Reminder"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.divider}/>
|
||||
<View marginH-30 marginB-0 row spread centerV>
|
||||
<View row>
|
||||
<LockIcon/>
|
||||
<Text
|
||||
style={{
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 16,
|
||||
}}
|
||||
marginL-10
|
||||
>
|
||||
Mark as Private
|
||||
</Text>
|
||||
</View>
|
||||
<View>
|
||||
<Switch
|
||||
onColor={"#ea156c"}
|
||||
offColor={"#e1e1e2"}
|
||||
marginL-10
|
||||
value={isPrivate}
|
||||
onValueChange={(value) => setIsPrivate(value)}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.divider}/>
|
||||
<View marginH-30 marginB-0 row spread centerV>
|
||||
<View row centerV>
|
||||
<MenuIcon/>
|
||||
<Text
|
||||
style={{
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 16,
|
||||
}}
|
||||
marginL-10
|
||||
>
|
||||
Add Details
|
||||
</Text>
|
||||
</View>
|
||||
<View></View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
<Button
|
||||
marginH-30
|
||||
marginB-15
|
||||
label="Create event from image"
|
||||
text70
|
||||
style={{height: 47}}
|
||||
labelStyle={{fontFamily: "PlusJakartaSans_500Medium", fontSize: 15}}
|
||||
backgroundColor="#05a8b6"
|
||||
iconSource={() => (
|
||||
<View marginR-5>
|
||||
<CameraIcon color="white"/>
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View style={styles.divider} />
|
||||
|
||||
<View marginH-30 marginB-10 row centerV>
|
||||
<Ionicons name="person-circle-outline" size={28} color="#919191" />
|
||||
<Text
|
||||
style={{ fontFamily: "Manrope_600SemiBold", fontSize: 18 }}
|
||||
marginL-10
|
||||
>
|
||||
Attendees
|
||||
</Text>
|
||||
<Button
|
||||
size={ButtonSize.small}
|
||||
paddingH-8
|
||||
iconSource={() => (
|
||||
<Ionicons name="add-outline" size={20} color="#ea156c" />
|
||||
)}
|
||||
style={{
|
||||
marginLeft: "auto",
|
||||
borderRadius: 8,
|
||||
backgroundColor: "#ffe8f1",
|
||||
borderColor: "#ea156c",
|
||||
borderWidth: 1,
|
||||
}}
|
||||
color="#ea156c"
|
||||
label="Add"
|
||||
labelStyle={{ fontFamily: "Manrope_600SemiBold", fontSize: 14 }}
|
||||
/>
|
||||
</View>
|
||||
<View marginL-35>
|
||||
<AssigneesDisplay />
|
||||
</View>
|
||||
<View style={styles.divider} />
|
||||
<View marginH-30 marginB-0 row spread centerV>
|
||||
<View row centerV>
|
||||
<ClockIcon />
|
||||
<Text
|
||||
style={{
|
||||
fontFamily: "Manrope_600SemiBold",
|
||||
fontSize: 18,
|
||||
}}
|
||||
marginL-10
|
||||
>
|
||||
Reminders
|
||||
</Text>
|
||||
</View>
|
||||
<View>
|
||||
<Button
|
||||
size={ButtonSize.small}
|
||||
paddingH-8
|
||||
iconSource={() => (
|
||||
<Ionicons name="add-outline" size={20} color="#ea156c" />
|
||||
)}
|
||||
style={{
|
||||
marginLeft: "auto",
|
||||
borderRadius: 8,
|
||||
backgroundColor: "#ffe8f1",
|
||||
borderColor: "#ea156c",
|
||||
borderWidth: 1,
|
||||
}}
|
||||
labelStyle={{ fontFamily: "Manrope_600SemiBold", fontSize: 14 }}
|
||||
color="#ea156c"
|
||||
label="Set Reminder"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.divider} />
|
||||
<View marginH-30 marginB-0 row spread centerV>
|
||||
<View row>
|
||||
<LockIcon />
|
||||
<Text
|
||||
style={{
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 16,
|
||||
}}
|
||||
marginL-10
|
||||
>
|
||||
Mark as Private
|
||||
</Text>
|
||||
</View>
|
||||
<View>
|
||||
<Switch
|
||||
onColor={"#ea156c"}
|
||||
offColor={"#e1e1e2"}
|
||||
marginL-10
|
||||
value={isPrivate}
|
||||
onValueChange={(value) => setIsPrivate(value)}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.divider} />
|
||||
<View marginH-30 marginB-0 row spread centerV>
|
||||
<View row centerV>
|
||||
<MenuIcon />
|
||||
<Text
|
||||
style={{
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 16,
|
||||
}}
|
||||
marginL-10
|
||||
>
|
||||
Add Details
|
||||
</Text>
|
||||
</View>
|
||||
<View></View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
<Button
|
||||
marginH-30
|
||||
marginB-15
|
||||
label="Create event from image"
|
||||
text70
|
||||
style={{ height: 47 }}
|
||||
labelStyle={{ fontFamily: "PlusJakartaSans_500Medium", fontSize: 15 }}
|
||||
backgroundColor="#05a8b6"
|
||||
iconSource={() => (
|
||||
<View marginR-5>
|
||||
<CameraIcon color="white" />
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
divider: { height: 1, backgroundColor: "#e4e4e4", marginVertical: 15 },
|
||||
gradient: {
|
||||
height: "25%",
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
width: "100%",
|
||||
},
|
||||
buttonContainer: {
|
||||
position: "absolute",
|
||||
bottom: 25,
|
||||
width: "100%",
|
||||
},
|
||||
button: {
|
||||
backgroundColor: "rgb(253, 23, 117)",
|
||||
paddingVertical: 20,
|
||||
},
|
||||
topBtn: {
|
||||
backgroundColor: "white",
|
||||
color: "#05a8b6",
|
||||
},
|
||||
rotateSwitch: {
|
||||
marginLeft: 35,
|
||||
marginBottom: 10,
|
||||
marginTop: 25,
|
||||
},
|
||||
divider: {height: 1, backgroundColor: "#e4e4e4", marginVertical: 15},
|
||||
gradient: {
|
||||
height: "25%",
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
width: "100%",
|
||||
},
|
||||
buttonContainer: {
|
||||
position: "absolute",
|
||||
bottom: 25,
|
||||
width: "100%",
|
||||
},
|
||||
button: {
|
||||
backgroundColor: "rgb(253, 23, 117)",
|
||||
paddingVertical: 20,
|
||||
},
|
||||
topBtn: {
|
||||
backgroundColor: "white",
|
||||
color: "#05a8b6",
|
||||
},
|
||||
rotateSwitch: {
|
||||
marginLeft: 35,
|
||||
marginBottom: 10,
|
||||
marginTop: 25,
|
||||
},
|
||||
});
|
||||
|
||||
9
components/pages/calendar/atoms.ts
Normal file
9
components/pages/calendar/atoms.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { atom } from 'jotai';
|
||||
import {CalendarEvent} from "@/components/pages/calendar/interfaces";
|
||||
|
||||
export const editVisibleAtom = atom<boolean>(false);
|
||||
export const eventForEditAtom = atom<CalendarEvent | undefined>(undefined);
|
||||
export const isFamilyViewAtom = atom<boolean>(false);
|
||||
export const modeAtom = atom<"week" | "month" | "day">("week");
|
||||
export const selectedDateAtom = atom<Date>(new Date());
|
||||
export const selectedNewEventDateAtom = atom<Date | undefined>(undefined);
|
||||
20
components/pages/calendar/constants.ts
Normal file
20
components/pages/calendar/constants.ts
Normal file
@ -0,0 +1,20 @@
|
||||
export const modeMap = new Map([
|
||||
[0, "day"],
|
||||
[1, "week"],
|
||||
[2, "month"],
|
||||
]);
|
||||
|
||||
export const months = [
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
];
|
||||
13
components/pages/calendar/interfaces.ts
Normal file
13
components/pages/calendar/interfaces.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export interface CalendarEvent {
|
||||
id?: number | string; // Unique identifier for the event
|
||||
user?: string;
|
||||
title: string; // Event title or name
|
||||
description?: string; // Optional description for the event
|
||||
start: Date; // Start date and time of the event
|
||||
end: Date; // End date and time of the event
|
||||
location?: string; // Optional event location
|
||||
allDay?: boolean; // Specifies if the event lasts all day
|
||||
eventColor?: string; // Optional color to represent the event
|
||||
participants?: string[]; // Optional list of participants or attendees
|
||||
private?: boolean;
|
||||
}
|
||||
@ -23,7 +23,7 @@ const CategoryDropdown = (props: {
|
||||
padding: 10,
|
||||
}}
|
||||
>
|
||||
<Text>{category}</Text>
|
||||
<Text style={{fontFamily: "Manrope_400Regular"}}>{category}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import {Text, View} from "react-native";
|
||||
import React, {useEffect, useRef} from "react";
|
||||
import React, {useEffect, useRef, useState} from "react";
|
||||
import {TextField, TextFieldRef} from "react-native-ui-lib";
|
||||
import {GroceryCategory, useGroceryContext,} from "@/contexts/GroceryContext";
|
||||
import CategoryDropdown from "./CategoryDropdown";
|
||||
|
||||
interface IEditGrocery {
|
||||
id?: string;
|
||||
@ -17,6 +18,7 @@ interface IEditGrocery {
|
||||
const EditGroceryItem = ({editGrocery}: { editGrocery: IEditGrocery }) => {
|
||||
const {fuzzyMatchGroceryCategory} = useGroceryContext();
|
||||
const inputRef = useRef<TextFieldRef>(null);
|
||||
const [category, setCategory] = useState<GroceryCategory>(GroceryCategory.None);
|
||||
|
||||
useEffect(() => {
|
||||
if (editGrocery.setCategory)
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
import {Button, ButtonSize, Dialog, Text, TextField, View} from "react-native-ui-lib";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import React, {useState} from "react";
|
||||
import {useSignIn} from "@/hooks/firebase/useSignIn";
|
||||
import {StyleSheet} from "react-native";
|
||||
import Toast from 'react-native-toast-message';
|
||||
import {useLoginWithQrCode} from "@/hooks/firebase/useLoginWithQrCode";
|
||||
import {Camera, CameraView} from 'expo-camera';
|
||||
import {BarCodeScanner} from "expo-barcode-scanner";
|
||||
|
||||
const SignInPage = ({setTab}: { setTab: React.Dispatch<React.SetStateAction<"register" | "login" | "reset-password">> }) => {
|
||||
const SignInPage = ({setTab}: {
|
||||
setTab: React.Dispatch<React.SetStateAction<"register" | "login" | "reset-password">>
|
||||
}) => {
|
||||
const [email, setEmail] = useState<string>("");
|
||||
const [password, setPassword] = useState<string>("");
|
||||
const [hasPermission, setHasPermission] = useState<boolean | null>(null);
|
||||
const [scanned, setScanned] = useState<boolean>(false);
|
||||
const [showCameraDialog, setShowCameraDialog] = useState<boolean>(false);
|
||||
|
||||
const {mutateAsync: signIn, error, isError} = useSignIn();
|
||||
@ -19,7 +19,7 @@ const SignInPage = ({setTab}: { setTab: React.Dispatch<React.SetStateAction<"re
|
||||
|
||||
const handleSignIn = async () => {
|
||||
await signIn({email, password});
|
||||
if(!isError) {
|
||||
if (!isError) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Login successful!"
|
||||
@ -33,10 +33,10 @@ const SignInPage = ({setTab}: { setTab: React.Dispatch<React.SetStateAction<"re
|
||||
}
|
||||
};
|
||||
|
||||
const handleQrCodeScanned = async ({ data }: { data: string }) => {
|
||||
const handleQrCodeScanned = async ({data}: { data: string }) => {
|
||||
setShowCameraDialog(false);
|
||||
try {
|
||||
await signInWithQrCode({ userId: data });
|
||||
await signInWithQrCode({userId: data});
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Login successful with QR code!"
|
||||
@ -51,9 +51,9 @@ const SignInPage = ({setTab}: { setTab: React.Dispatch<React.SetStateAction<"re
|
||||
};
|
||||
|
||||
const getCameraPermissions = async (callback: () => void) => {
|
||||
const { status } = await Camera.requestCameraPermissionsAsync();
|
||||
const {status} = await Camera.requestCameraPermissionsAsync();
|
||||
setHasPermission(status === 'granted');
|
||||
if(status === 'granted') {
|
||||
if (status === 'granted') {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
@ -83,12 +83,12 @@ const SignInPage = ({setTab}: { setTab: React.Dispatch<React.SetStateAction<"re
|
||||
label="Login with a QR Code"
|
||||
onPress={() => {
|
||||
getCameraPermissions(() => setShowCameraDialog(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
style={{marginBottom: 20}}
|
||||
backgroundColor="#fd1775"
|
||||
/>
|
||||
{isError && <Text center style={{marginBottom: 20}}>{`${error}`}</Text>}
|
||||
{isError && <Text center style={{marginBottom: 20}}>{`${error?.toString()?.split("]")?.[1]}`}</Text>}
|
||||
|
||||
<View row centerH marginB-5 gap-5>
|
||||
<Text text70>
|
||||
@ -131,7 +131,7 @@ const SignInPage = ({setTab}: { setTab: React.Dispatch<React.SetStateAction<"re
|
||||
bottom
|
||||
width="100%"
|
||||
height="70%"
|
||||
containerStyle={{ padding: 0 }}
|
||||
containerStyle={{padding: 0}}
|
||||
>
|
||||
{hasPermission === null ? (
|
||||
<Text>Requesting camera permissions...</Text>
|
||||
@ -139,7 +139,7 @@ const SignInPage = ({setTab}: { setTab: React.Dispatch<React.SetStateAction<"re
|
||||
<Text>No access to camera</Text>
|
||||
) : (
|
||||
<CameraView
|
||||
style={{ flex: 1 }}
|
||||
style={{flex: 1}}
|
||||
onBarcodeScanned={handleQrCodeScanned}
|
||||
barcodeScannerSettings={{
|
||||
barcodeTypes: ["qr"],
|
||||
@ -150,7 +150,7 @@ const SignInPage = ({setTab}: { setTab: React.Dispatch<React.SetStateAction<"re
|
||||
label="Cancel"
|
||||
onPress={() => setShowCameraDialog(false)}
|
||||
backgroundColor="#fd1775"
|
||||
style={{ margin: 10 }}
|
||||
style={{margin: 10}}
|
||||
/>
|
||||
</Dialog>
|
||||
</View>
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useRef, useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
ButtonSize,
|
||||
Checkbox,
|
||||
Text,
|
||||
TextField,
|
||||
TextFieldRef,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from "react-native-ui-lib";
|
||||
@ -13,7 +14,13 @@ import { ProfileType } from "@/contexts/AuthContext";
|
||||
import { StyleSheet } from "react-native";
|
||||
import { AntDesign } from "@expo/vector-icons";
|
||||
|
||||
const SignUpPage = ({setTab}: { setTab: React.Dispatch<React.SetStateAction<"register" | "login" | "reset-password">> }) => {
|
||||
const SignUpPage = ({
|
||||
setTab,
|
||||
}: {
|
||||
setTab: React.Dispatch<
|
||||
React.SetStateAction<"register" | "login" | "reset-password">
|
||||
>;
|
||||
}) => {
|
||||
const [email, setEmail] = useState<string>("");
|
||||
const [firstName, setFirstName] = useState<string>("");
|
||||
const [lastName, setLastName] = useState<string>("");
|
||||
@ -24,6 +31,10 @@ const SignUpPage = ({setTab}: { setTab: React.Dispatch<React.SetStateAction<"re
|
||||
const [acceptTerms, setAcceptTerms] = useState<boolean>(false);
|
||||
const { mutateAsync: signUp } = useSignUp();
|
||||
|
||||
const lnameRef = useRef<TextFieldRef>(null);
|
||||
const emailRef = useRef<TextFieldRef>(null);
|
||||
const passwordRef = useRef<TextFieldRef>(null);
|
||||
|
||||
const handleSignUp = async () => {
|
||||
await signUp({ email, password, firstName, lastName });
|
||||
};
|
||||
@ -36,24 +47,34 @@ const SignUpPage = ({setTab}: { setTab: React.Dispatch<React.SetStateAction<"re
|
||||
<Text center>Please enter your details.</Text>
|
||||
<TextField
|
||||
marginT-60
|
||||
autoFocus
|
||||
placeholder="First name"
|
||||
value={firstName}
|
||||
onChangeText={setFirstName}
|
||||
style={styles.textfield}
|
||||
onSubmitEditing={() => {lnameRef.current?.focus()}}
|
||||
blurOnSubmit={false}
|
||||
/>
|
||||
<TextField
|
||||
ref={lnameRef}
|
||||
placeholder="Last name"
|
||||
value={lastName}
|
||||
onChangeText={setLastName}
|
||||
style={styles.textfield}
|
||||
onSubmitEditing={() => {emailRef.current?.focus()}}
|
||||
blurOnSubmit={false}
|
||||
/>
|
||||
<TextField
|
||||
ref={emailRef}
|
||||
placeholder="Email"
|
||||
value={email}
|
||||
onChangeText={setEmail}
|
||||
style={styles.textfield}
|
||||
onSubmitEditing={() => {passwordRef.current?.focus()}}
|
||||
blurOnSubmit={false}
|
||||
/>
|
||||
<TextField
|
||||
ref={passwordRef}
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
|
||||
@ -1,14 +1,11 @@
|
||||
import { AntDesign, Ionicons } from "@expo/vector-icons";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { Button, Checkbox, Text, View } from "react-native-ui-lib";
|
||||
import { ScrollView, StyleSheet } from "react-native";
|
||||
import { colorMap } from "@/contexts/SettingsContext";
|
||||
import { TouchableOpacity } from "react-native-gesture-handler";
|
||||
import { fetchGoogleCalendarEvents } from "@/calendar-integration/google-calendar-utils";
|
||||
import { fetchMicrosoftCalendarEvents } from "@/calendar-integration/microsoft-calendar-utils";
|
||||
import { useCreateEventFromProvider } from "@/hooks/firebase/useCreateEvent";
|
||||
import { useAuthContext } from "@/contexts/AuthContext";
|
||||
import { useUpdateUserData } from "@/hooks/firebase/useUpdateUserData";
|
||||
import {AntDesign, Ionicons} from "@expo/vector-icons";
|
||||
import React, {useCallback, useEffect, useState} from "react";
|
||||
import {Button, Checkbox, Text, View} from "react-native-ui-lib";
|
||||
import {ActivityIndicator, ScrollView, StyleSheet} from "react-native";
|
||||
import {colorMap} from "@/contexts/SettingsContext";
|
||||
import {TouchableOpacity} from "react-native-gesture-handler";
|
||||
import {useAuthContext} from "@/contexts/AuthContext";
|
||||
import {useUpdateUserData} from "@/hooks/firebase/useUpdateUserData";
|
||||
import debounce from "debounce";
|
||||
import AppleIcon from "@/assets/svgs/AppleIcon";
|
||||
import GoogleIcon from "@/assets/svgs/GoogleIcon";
|
||||
@ -16,418 +13,575 @@ import OutlookIcon from "@/assets/svgs/OutlookIcon";
|
||||
import * as AuthSession from "expo-auth-session";
|
||||
import * as Google from "expo-auth-session/providers/google";
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
import {UserProfile} from "@firebase/auth";
|
||||
import {useFetchAndSaveGoogleEvents} from "@/hooks/useFetchAndSaveGoogleEvents";
|
||||
import {useFetchAndSaveOutlookEvents} from "@/hooks/useFetchAndSaveOutlookEvents";
|
||||
import {useFetchAndSaveAppleEvents} from "@/hooks/useFetchAndSaveAppleEvents";
|
||||
import * as AppleAuthentication from 'expo-apple-authentication';
|
||||
import ExpoLocalization from "expo-localization/src/ExpoLocalization";
|
||||
|
||||
const googleConfig = {
|
||||
androidClientId:
|
||||
"406146460310-2u67ab2nbhu23trp8auho1fq4om29fc0.apps.googleusercontent.com",
|
||||
iosClientId:
|
||||
"406146460310-2u67ab2nbhu23trp8auho1fq4om29fc0.apps.googleusercontent.com",
|
||||
webClientId:
|
||||
"406146460310-2u67ab2nbhu23trp8auho1fq4om29fc0.apps.googleusercontent.com",
|
||||
scopes: [
|
||||
"email",
|
||||
"profile",
|
||||
"https://www.googleapis.com/auth/calendar.events.owned",
|
||||
],
|
||||
androidClientId:
|
||||
"406146460310-2u67ab2nbhu23trp8auho1fq4om29fc0.apps.googleusercontent.com",
|
||||
iosClientId:
|
||||
"406146460310-2u67ab2nbhu23trp8auho1fq4om29fc0.apps.googleusercontent.com",
|
||||
webClientId:
|
||||
"406146460310-2u67ab2nbhu23trp8auho1fq4om29fc0.apps.googleusercontent.com",
|
||||
scopes: [
|
||||
"email",
|
||||
"profile",
|
||||
"https://www.googleapis.com/auth/calendar.events.owned",
|
||||
],
|
||||
};
|
||||
|
||||
const microsoftConfig = {
|
||||
clientId: "13c79071-1066-40a9-9f71-b8c4b138b4af", // Replace with your Microsoft client ID
|
||||
redirectUri: AuthSession.makeRedirectUri({ path: "settings" }), // Generate redirect URI automatically for Expo
|
||||
scopes: [
|
||||
"openid",
|
||||
"profile",
|
||||
"email",
|
||||
"offline_access",
|
||||
"Calendars.ReadWrite", // Scope for reading calendar events
|
||||
],
|
||||
authorizationEndpoint:
|
||||
"https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
|
||||
tokenEndpoint: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
|
||||
clientId: "13c79071-1066-40a9-9f71-b8c4b138b4af",
|
||||
redirectUri: AuthSession.makeRedirectUri({path: "settings"}),
|
||||
scopes: [
|
||||
"openid",
|
||||
"profile",
|
||||
"email",
|
||||
"offline_access",
|
||||
"Calendars.ReadWrite",
|
||||
"User.Read"
|
||||
],
|
||||
authorizationEndpoint: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
|
||||
tokenEndpoint: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
|
||||
};
|
||||
|
||||
const CalendarSettingsPage = (props: {
|
||||
setSelectedPage: (page: number) => void;
|
||||
setSelectedPage: (page: number) => void;
|
||||
}) => {
|
||||
const [startDate, setStartDate] = useState<boolean>(false);
|
||||
const { profileData } = useAuthContext();
|
||||
const {profileData} = useAuthContext();
|
||||
const [firstDayOfWeek, setFirstDayOfWeek] = useState<string>(profileData?.firstDayOfWeek ?? ExpoLocalization.getCalendars()[0].firstWeekday === 1 ? "Mondays" : "Sundays");
|
||||
|
||||
const [selectedColor, setSelectedColor] = useState<string>(
|
||||
profileData?.eventColor ?? colorMap.pink
|
||||
);
|
||||
const [previousSelectedColor, setPreviousSelectedColor] = useState<string>(
|
||||
profileData?.eventColor ?? colorMap.pink
|
||||
);
|
||||
|
||||
const { mutateAsync: createEventFromProvider } = useCreateEventFromProvider();
|
||||
const { mutateAsync: updateUserData } = useUpdateUserData();
|
||||
|
||||
WebBrowser.maybeCompleteAuthSession();
|
||||
const [request, response, promptAsync] = Google.useAuthRequest(googleConfig);
|
||||
|
||||
useEffect(() => {
|
||||
signInWithGoogle();
|
||||
}, [response]);
|
||||
|
||||
const fetchAndSaveGoogleEvents = () => {
|
||||
console.log("fetch");
|
||||
const timeMin = new Date(new Date().setHours(0, 0, 0, 0));
|
||||
const timeMax = new Date(
|
||||
new Date(new Date().setHours(0, 0, 0, 0)).setDate(timeMin.getDate() + 30)
|
||||
const [selectedColor, setSelectedColor] = useState<string>(
|
||||
profileData?.eventColor ?? colorMap.pink
|
||||
);
|
||||
const [previousSelectedColor, setPreviousSelectedColor] = useState<string>(
|
||||
profileData?.eventColor ?? colorMap.pink
|
||||
);
|
||||
|
||||
fetchGoogleCalendarEvents(
|
||||
profileData?.googleToken,
|
||||
timeMin.toISOString().slice(0, -5) + "Z",
|
||||
timeMax.toISOString().slice(0, -5) + "Z"
|
||||
).then((response) => {
|
||||
response?.forEach((item) => saveData(item));
|
||||
});
|
||||
};
|
||||
const {mutateAsync: updateUserData} = useUpdateUserData();
|
||||
const {mutateAsync: fetchAndSaveGoogleEvents, isLoading: isSyncingGoogle} = useFetchAndSaveGoogleEvents();
|
||||
const {mutateAsync: fetchAndSaveOutlookEvents, isLoading: isSyncingOutlook} = useFetchAndSaveOutlookEvents();
|
||||
const {mutateAsync: fetchAndSaveAppleEvents, isLoading: isSyncingApple} = useFetchAndSaveAppleEvents();
|
||||
|
||||
async function saveData(item: any) {
|
||||
await createEventFromProvider(item);
|
||||
}
|
||||
|
||||
const fetchAndSaveMicrosoftEvents = () => {
|
||||
const startDateTime = new Date(new Date().setHours(0, 0, 0, 0));
|
||||
const endDateTime = new Date(
|
||||
new Date(new Date().setHours(0, 0, 0, 0)).setDate(
|
||||
startDateTime.getDate() + 30
|
||||
)
|
||||
WebBrowser.maybeCompleteAuthSession();
|
||||
const [_, response, promptAsync] = Google.useAuthRequest(googleConfig);
|
||||
|
||||
useEffect(() => {
|
||||
signInWithGoogle();
|
||||
}, [response]);
|
||||
|
||||
const signInWithGoogle = async () => {
|
||||
try {
|
||||
if (response?.type === "success") {
|
||||
const accessToken = response.authentication?.accessToken;
|
||||
|
||||
const userInfoResponse = await fetch(
|
||||
"https://www.googleapis.com/oauth2/v3/userinfo",
|
||||
{
|
||||
headers: {Authorization: `Bearer ${accessToken}`},
|
||||
}
|
||||
);
|
||||
|
||||
const userInfo = await userInfoResponse.json();
|
||||
const googleMail = userInfo.email;
|
||||
|
||||
await updateUserData({
|
||||
newUserData: {googleToken: accessToken, googleMail: googleMail},
|
||||
});
|
||||
|
||||
await fetchAndSaveGoogleEvents(accessToken, googleMail)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error during Google sign-in:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMicrosoftSignIn = async () => {
|
||||
try {
|
||||
console.log("Starting Microsoft sign-in...");
|
||||
|
||||
const authRequest = new AuthSession.AuthRequest({
|
||||
clientId: microsoftConfig.clientId,
|
||||
scopes: microsoftConfig.scopes,
|
||||
redirectUri: microsoftConfig.redirectUri,
|
||||
responseType: AuthSession.ResponseType.Code,
|
||||
usePKCE: true, // Enable PKCE
|
||||
});
|
||||
|
||||
console.log("Auth request created:", authRequest);
|
||||
|
||||
const authResult = await authRequest.promptAsync({
|
||||
authorizationEndpoint: microsoftConfig.authorizationEndpoint,
|
||||
});
|
||||
|
||||
console.log("Auth result:", authResult);
|
||||
|
||||
if (authResult.type === "success" && authResult.params?.code) {
|
||||
const code = authResult.params.code;
|
||||
console.log("Authorization code received:", code);
|
||||
|
||||
// Exchange authorization code for tokens
|
||||
const tokenResponse = await fetch(microsoftConfig.tokenEndpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: `client_id=${
|
||||
microsoftConfig.clientId
|
||||
}&redirect_uri=${encodeURIComponent(
|
||||
microsoftConfig.redirectUri
|
||||
)}&grant_type=authorization_code&code=${code}&code_verifier=${
|
||||
authRequest.codeVerifier
|
||||
}&scope=${encodeURIComponent(
|
||||
"https://graph.microsoft.com/Calendars.ReadWrite offline_access User.Read"
|
||||
)}`,
|
||||
});
|
||||
|
||||
console.log("Token response status:", tokenResponse.status);
|
||||
|
||||
if (!tokenResponse.ok) {
|
||||
const errorText = await tokenResponse.text();
|
||||
console.error("Token exchange failed:", errorText);
|
||||
return;
|
||||
}
|
||||
|
||||
const tokenData = await tokenResponse.json();
|
||||
console.log("Token data received:", tokenData);
|
||||
|
||||
if (tokenData?.access_token) {
|
||||
console.log("Access token received, fetching user info...");
|
||||
|
||||
// Fetch user info from Microsoft Graph API to get the email
|
||||
const userInfoResponse = await fetch("https://graph.microsoft.com/v1.0/me", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokenData.access_token}`,
|
||||
},
|
||||
});
|
||||
|
||||
const userInfo = await userInfoResponse.json();
|
||||
console.log("User info received:", userInfo);
|
||||
|
||||
if (userInfo.error) {
|
||||
console.error("Error fetching user info:", userInfo.error);
|
||||
} else {
|
||||
const outlookMail = userInfo.mail || userInfo.userPrincipalName;
|
||||
|
||||
// Update user data with Microsoft token and email
|
||||
await updateUserData({
|
||||
newUserData: {microsoftToken: tokenData.access_token, outlookMail: outlookMail},
|
||||
});
|
||||
|
||||
await fetchAndSaveOutlookEvents(tokenData.access_token, outlookMail)
|
||||
console.log("User data updated successfully.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn("Authentication was not successful:", authResult);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error during Microsoft sign-in:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAppleSignIn = async () => {
|
||||
try {
|
||||
console.log("Starting Apple Sign-in...");
|
||||
|
||||
const credential = await AppleAuthentication.signInAsync({
|
||||
requestedScopes: [
|
||||
AppleAuthentication.AppleAuthenticationScope.EMAIL,
|
||||
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
|
||||
],
|
||||
});
|
||||
|
||||
console.log("Apple sign-in result:", credential);
|
||||
|
||||
const appleToken = credential.identityToken;
|
||||
const appleMail = credential.email;
|
||||
|
||||
if (appleToken) {
|
||||
console.log("Apple ID token received. Fetch user info if needed...");
|
||||
|
||||
// Example: Store user token and email
|
||||
await updateUserData({
|
||||
newUserData: {appleToken, appleMail},
|
||||
});
|
||||
|
||||
console.log("User data updated with Apple ID token.");
|
||||
} else {
|
||||
console.warn("Apple authentication was not successful or email was hidden.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error during Apple Sign-in:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const debouncedUpdateUserData = useCallback(
|
||||
debounce(async (color: string) => {
|
||||
try {
|
||||
await updateUserData({
|
||||
newUserData: {
|
||||
eventColor: color,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to update color:", error);
|
||||
setSelectedColor(previousSelectedColor);
|
||||
}
|
||||
}, 500),
|
||||
[]
|
||||
);
|
||||
|
||||
fetchMicrosoftCalendarEvents(
|
||||
profileData?.microsoftToken,
|
||||
startDateTime.toISOString().slice(0, -5) + "Z",
|
||||
endDateTime.toISOString().slice(0, -5) + "Z"
|
||||
).then((response) => {
|
||||
console.log(response);
|
||||
response?.forEach((item) => saveData(item));
|
||||
});
|
||||
};
|
||||
const debouncedUpdateFirstDayOfWeek = useCallback(
|
||||
debounce(async (firstDayOfWeek: string) => {
|
||||
try {
|
||||
await updateUserData({
|
||||
newUserData: {
|
||||
firstDayOfWeek,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to update first day of week:", error);
|
||||
}
|
||||
}, 500),
|
||||
[]
|
||||
);
|
||||
|
||||
const signInWithGoogle = async () => {
|
||||
try {
|
||||
// Attempt to retrieve user information from AsyncStorage
|
||||
if (response?.type === "success") {
|
||||
console.log(response.authentication);
|
||||
await updateUserData({
|
||||
newUserData: { googleToken: response.authentication?.accessToken },
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// Handle any errors that occur during AsyncStorage retrieval or other operations
|
||||
console.error("Error retrieving user data from AsyncStorage:", error);
|
||||
const handleChangeFirstDayOfWeek = () => {
|
||||
setFirstDayOfWeek(firstDayOfWeek === "Sundays" ? "Mondays" : "Sundays");
|
||||
debouncedUpdateFirstDayOfWeek(firstDayOfWeek === "Sundays" ? "Mondays" : "Sundays");
|
||||
}
|
||||
};
|
||||
|
||||
const handleMicrosoftSignIn = async () => {
|
||||
try {
|
||||
console.log("Starting Microsoft sign-in...");
|
||||
const handleChangeColor = (color: string) => {
|
||||
setPreviousSelectedColor(selectedColor);
|
||||
setSelectedColor(color);
|
||||
debouncedUpdateUserData(color);
|
||||
};
|
||||
|
||||
const authRequest = new AuthSession.AuthRequest({
|
||||
clientId: microsoftConfig.clientId,
|
||||
scopes: microsoftConfig.scopes,
|
||||
redirectUri: microsoftConfig.redirectUri,
|
||||
responseType: AuthSession.ResponseType.Code,
|
||||
usePKCE: true, // Enable PKCE
|
||||
});
|
||||
|
||||
console.log("Auth request created:", authRequest);
|
||||
|
||||
const authResult = await authRequest.promptAsync({
|
||||
authorizationEndpoint: microsoftConfig.authorizationEndpoint,
|
||||
});
|
||||
|
||||
console.log("Auth result:", authResult);
|
||||
|
||||
if (authResult.type === "success" && authResult.params?.code) {
|
||||
const code = authResult.params.code;
|
||||
console.log("Authorization code received:", code);
|
||||
|
||||
// Exchange authorization code for tokens
|
||||
const tokenResponse = await fetch(microsoftConfig.tokenEndpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: `client_id=${
|
||||
microsoftConfig.clientId
|
||||
}&redirect_uri=${encodeURIComponent(
|
||||
microsoftConfig.redirectUri
|
||||
)}&grant_type=authorization_code&code=${code}&code_verifier=${
|
||||
authRequest.codeVerifier
|
||||
}&scope=${encodeURIComponent(
|
||||
"https://graph.microsoft.com/Calendars.ReadWrite offline_access"
|
||||
)}`,
|
||||
});
|
||||
|
||||
console.log("Token response status:", tokenResponse.status);
|
||||
|
||||
if (!tokenResponse.ok) {
|
||||
console.error("Token exchange failed:", await tokenResponse.text());
|
||||
return;
|
||||
const clearToken = async (provider: "google" | "outlook" | "apple") => {
|
||||
const newUserData: Partial<UserProfile> = {};
|
||||
if (provider === "google") {
|
||||
newUserData.googleToken = null;
|
||||
newUserData.googleMail = null;
|
||||
} else if (provider === "outlook") {
|
||||
newUserData.microsoftToken = null;
|
||||
newUserData.outlookMail = null;
|
||||
} else if (provider === "apple") {
|
||||
newUserData.appleToken = null;
|
||||
newUserData.appleMail = null;
|
||||
}
|
||||
await updateUserData({newUserData});
|
||||
};
|
||||
|
||||
const tokenData = await tokenResponse.json();
|
||||
console.log("Token data received:", tokenData);
|
||||
|
||||
if (tokenData?.id_token) {
|
||||
console.log("ID token received, updating user data...");
|
||||
await updateUserData({
|
||||
newUserData: { microsoftToken: tokenData.access_token },
|
||||
});
|
||||
console.log("User data updated successfully.");
|
||||
}
|
||||
} else {
|
||||
console.warn("Authentication was not successful:", authResult);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error during Microsoft sign-in:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const debouncedUpdateUserData = useCallback(
|
||||
debounce(async (color: string) => {
|
||||
try {
|
||||
await updateUserData({
|
||||
newUserData: {
|
||||
eventColor: color,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to update color:", error);
|
||||
setSelectedColor(previousSelectedColor);
|
||||
}
|
||||
}, 500),
|
||||
[]
|
||||
);
|
||||
|
||||
const handleChangeColor = (color: string) => {
|
||||
setPreviousSelectedColor(selectedColor);
|
||||
setSelectedColor(color);
|
||||
debouncedUpdateUserData(color);
|
||||
};
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
<View marginH-30>
|
||||
<TouchableOpacity onPress={() => props.setSelectedPage(0)}>
|
||||
<View row marginT-20 marginB-35 centerV>
|
||||
<Ionicons
|
||||
name="chevron-back"
|
||||
size={14}
|
||||
color="#979797"
|
||||
style={{ paddingBottom: 3 }}
|
||||
/>
|
||||
<Text
|
||||
style={{ fontFamily: "Poppins_400Regular", fontSize: 14.71 }}
|
||||
color="#979797"
|
||||
>
|
||||
Return to main settings
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.subTitle}>Calendar settings</Text>
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.cardTitle} marginB-14>
|
||||
Event Color Preference
|
||||
</Text>
|
||||
<View row spread>
|
||||
<TouchableOpacity onPress={() => handleChangeColor(colorMap.pink)}>
|
||||
<View style={styles.colorBox} backgroundColor={colorMap.pink}>
|
||||
{selectedColor == colorMap.pink && (
|
||||
<AntDesign name="check" size={30} color="white" />
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={() => handleChangeColor(colorMap.orange)}
|
||||
>
|
||||
<View style={styles.colorBox} backgroundColor={colorMap.orange}>
|
||||
{selectedColor == colorMap.orange && (
|
||||
<AntDesign name="check" size={30} color="white" />
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={() => handleChangeColor(colorMap.green)}>
|
||||
<View style={styles.colorBox} backgroundColor={colorMap.green}>
|
||||
{selectedColor == colorMap.green && (
|
||||
<AntDesign name="check" size={30} color="white" />
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={() => handleChangeColor(colorMap.teal)}>
|
||||
<View style={styles.colorBox} backgroundColor={colorMap.teal}>
|
||||
{selectedColor == colorMap.teal && (
|
||||
<AntDesign name="check" size={30} color="white" />
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={() => handleChangeColor(colorMap.purple)}
|
||||
>
|
||||
<View style={styles.colorBox} backgroundColor={colorMap.purple}>
|
||||
{selectedColor == colorMap.purple && (
|
||||
<AntDesign name="check" size={30} color="white" />
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.cardTitle}>Weekly Start Date</Text>
|
||||
<View row marginV-5 marginT-20>
|
||||
<Checkbox
|
||||
value={startDate}
|
||||
style={styles.checkbox}
|
||||
color="#ea156d"
|
||||
onValueChange={() => setStartDate(true)}
|
||||
/>
|
||||
<View row marginL-8>
|
||||
<Text text70>Sundays</Text>
|
||||
<Text text70 color="gray">
|
||||
{" "}
|
||||
(default)
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View row marginV-5>
|
||||
<Checkbox
|
||||
value={!startDate}
|
||||
style={styles.checkbox}
|
||||
color="#ea156d"
|
||||
onValueChange={() => setStartDate(false)}
|
||||
/>
|
||||
<Text text70 marginL-8>
|
||||
Mondays
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Text style={styles.subTitle} marginT-30 marginB-25>
|
||||
Add Calendar
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
onPress={() => promptAsync()}
|
||||
label="Connect Google"
|
||||
labelStyle={styles.addCalLbl}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<GoogleIcon />
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
<Button
|
||||
label="Connect Apple"
|
||||
labelStyle={styles.addCalLbl}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<AppleIcon />
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
<Button
|
||||
onPress={handleMicrosoftSignIn}
|
||||
label="Connect Outlook"
|
||||
labelStyle={styles.addCalLbl}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<OutlookIcon />
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
|
||||
<Text style={styles.subTitle} marginT-30 marginB-20>
|
||||
Connected Calendars
|
||||
</Text>
|
||||
<View style={styles.card}>
|
||||
<View style={{ marginTop: 20 }}>
|
||||
<Button
|
||||
onPress={fetchAndSaveGoogleEvents}
|
||||
label="Sync Google"
|
||||
labelStyle={styles.addCalLbl}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<GoogleIcon />
|
||||
return (
|
||||
<ScrollView>
|
||||
<View marginH-30 marginB-30>
|
||||
<TouchableOpacity onPress={() => props.setSelectedPage(0)}>
|
||||
<View row marginT-20 marginB-35 centerV>
|
||||
<Ionicons
|
||||
name="chevron-back"
|
||||
size={14}
|
||||
color="#979797"
|
||||
style={{paddingBottom: 3}}
|
||||
/>
|
||||
<Text
|
||||
style={{fontFamily: "Poppins_400Regular", fontSize: 14.71}}
|
||||
color="#979797"
|
||||
>
|
||||
Return to main settings
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.subTitle}>Calendar settings</Text>
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.cardTitle} marginB-14>
|
||||
Event Color Preference
|
||||
</Text>
|
||||
<View row spread>
|
||||
<TouchableOpacity onPress={() => handleChangeColor(colorMap.pink)}>
|
||||
<View style={styles.colorBox} backgroundColor={colorMap.pink}>
|
||||
{selectedColor == colorMap.pink && (
|
||||
<AntDesign name="check" size={30} color="white"/>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={() => handleChangeColor(colorMap.orange)}
|
||||
>
|
||||
<View style={styles.colorBox} backgroundColor={colorMap.orange}>
|
||||
{selectedColor == colorMap.orange && (
|
||||
<AntDesign name="check" size={30} color="white"/>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={() => handleChangeColor(colorMap.green)}>
|
||||
<View style={styles.colorBox} backgroundColor={colorMap.green}>
|
||||
{selectedColor == colorMap.green && (
|
||||
<AntDesign name="check" size={30} color="white"/>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={() => handleChangeColor(colorMap.teal)}>
|
||||
<View style={styles.colorBox} backgroundColor={colorMap.teal}>
|
||||
{selectedColor == colorMap.teal && (
|
||||
<AntDesign name="check" size={30} color="white"/>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={() => handleChangeColor(colorMap.purple)}
|
||||
>
|
||||
<View style={styles.colorBox} backgroundColor={colorMap.purple}>
|
||||
{selectedColor == colorMap.purple && (
|
||||
<AntDesign name="check" size={30} color="white"/>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
<Button
|
||||
onPress={fetchAndSaveMicrosoftEvents}
|
||||
label="Sync Outlook"
|
||||
labelStyle={styles.addCalLbl}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<OutlookIcon />
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.cardTitle}>Weekly Start Date</Text>
|
||||
<View row marginV-5 marginT-20>
|
||||
<Checkbox
|
||||
value={firstDayOfWeek === "Sundays"}
|
||||
style={styles.checkbox}
|
||||
color="#ea156d"
|
||||
onValueChange={() => handleChangeFirstDayOfWeek("Sundays")}
|
||||
/>
|
||||
<View row marginL-8>
|
||||
<Text text70>Sundays</Text>
|
||||
<Text text70 color="gray">
|
||||
{" "}
|
||||
(default)
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View row marginV-5>
|
||||
<Checkbox
|
||||
value={firstDayOfWeek === "Mondays"}
|
||||
style={styles.checkbox}
|
||||
color="#ea156d"
|
||||
onValueChange={() => handleChangeFirstDayOfWeek("Mondays")}
|
||||
/>
|
||||
<Text text70 marginL-8>
|
||||
Mondays
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
<Text style={styles.subTitle} marginT-30 marginB-25>
|
||||
Add Calendar
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
onPress={() => !profileData?.googleToken ? promptAsync() : clearToken("google")}
|
||||
label={profileData?.googleToken ? `Disconnect ${profileData.googleMail}` : "Connect Google"}
|
||||
labelStyle={styles.addCalLbl}
|
||||
labelProps={{
|
||||
numberOfLines: 2
|
||||
}}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<GoogleIcon/>
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
<Button
|
||||
onPress={() => !profileData?.appleToken ? handleAppleSignIn() : clearToken("google")}
|
||||
label={profileData?.appleToken ? `Disconnect ${profileData.appleMail}` : "Connect Apple"}
|
||||
labelStyle={styles.addCalLbl}
|
||||
labelProps={{
|
||||
numberOfLines: 2
|
||||
}}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<AppleIcon/>
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
<Button
|
||||
onPress={() => !profileData?.microsoftToken ? handleMicrosoftSignIn() : clearToken("outlook")}
|
||||
label={profileData?.microsoftToken ? `Disconnect ${profileData.outlookMail}` : "Connect Outlook"}
|
||||
labelStyle={styles.addCalLbl}
|
||||
labelProps={{
|
||||
numberOfLines: 2
|
||||
}}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<OutlookIcon/>
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
|
||||
{(profileData?.googleMail || profileData?.outlookMail || profileData?.appleMail) && (
|
||||
<>
|
||||
<Text style={styles.subTitle} marginT-30 marginB-20>
|
||||
Connected Calendars
|
||||
</Text>
|
||||
|
||||
<View style={styles.noPaddingCard}>
|
||||
<View style={{marginTop: 20}}>
|
||||
{!!profileData?.googleMail && (
|
||||
<TouchableOpacity
|
||||
onPress={() => fetchAndSaveGoogleEvents(undefined, undefined)}
|
||||
>
|
||||
<View row paddingR-20 center>
|
||||
<Button
|
||||
disabled={isSyncingGoogle}
|
||||
onPress={() => fetchAndSaveGoogleEvents(undefined, undefined)}
|
||||
label={`Sync ${profileData?.googleMail}`}
|
||||
labelStyle={styles.addCalLbl}
|
||||
labelProps={{numberOfLines: 3}}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<GoogleIcon/>
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
|
||||
{isSyncingGoogle ? (
|
||||
<ActivityIndicator/>
|
||||
) : (
|
||||
<Ionicons name={"refresh"} size={20} color={"#000000"}/>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
|
||||
{!!profileData?.appleMail && (
|
||||
<TouchableOpacity>
|
||||
<View row paddingR-20 center>
|
||||
<Button
|
||||
disabled={isSyncingApple}
|
||||
onPress={() => fetchAndSaveAppleEvents(undefined, undefined)}
|
||||
label={`Sync ${profileData?.appleMail}`}
|
||||
labelStyle={styles.addCalLbl}
|
||||
labelProps={{numberOfLines: 3}}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<AppleIcon/>
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
{isSyncingApple ? (
|
||||
<ActivityIndicator/>
|
||||
) : (
|
||||
<Ionicons name={"refresh"} size={20} color={"#000000"}/>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
{!!profileData?.outlookMail && (
|
||||
<TouchableOpacity
|
||||
onPress={() => fetchAndSaveOutlookEvents(undefined, undefined)}
|
||||
>
|
||||
<View row paddingR-20 center>
|
||||
<Button
|
||||
disabled={isSyncingOutlook}
|
||||
onPress={() => fetchAndSaveOutlookEvents(undefined, undefined)}
|
||||
label={`Sync ${profileData?.outlookMail}`}
|
||||
labelStyle={styles.addCalLbl}
|
||||
labelProps={{numberOfLines: 3}}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<OutlookIcon/>
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
{isSyncingOutlook ? (
|
||||
<ActivityIndicator/>
|
||||
) : (
|
||||
<Ionicons name={"refresh"} size={20} color={"#000000"}/>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
addCalBtn: {
|
||||
backgroundColor: "#ffffff",
|
||||
marginBottom: 15,
|
||||
justifyContent: "flex-start",
|
||||
paddingLeft: 25,
|
||||
},
|
||||
backBtn: {
|
||||
backgroundColor: "red",
|
||||
marginLeft: -2,
|
||||
justifyContent: "flex-start",
|
||||
},
|
||||
card: {
|
||||
backgroundColor: "white",
|
||||
width: "100%",
|
||||
padding: 20,
|
||||
paddingBottom: 30,
|
||||
marginTop: 20,
|
||||
borderRadius: 12,
|
||||
},
|
||||
colorBox: {
|
||||
aspectRatio: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: 51,
|
||||
borderRadius: 12,
|
||||
},
|
||||
checkbox: {
|
||||
borderRadius: 50,
|
||||
},
|
||||
addCalLbl: {
|
||||
fontSize: 16,
|
||||
fontFamily: "PlusJakartaSan_500Medium",
|
||||
},
|
||||
subTitle: {
|
||||
fontFamily: "Manrope_600SemiBold",
|
||||
fontSize: 18,
|
||||
},
|
||||
cardTitle: {
|
||||
fontFamily: "Manrope_500Medium",
|
||||
fontSize: 15,
|
||||
},
|
||||
addCalBtn: {
|
||||
backgroundColor: "#ffffff",
|
||||
marginBottom: 15,
|
||||
justifyContent: "flex-start",
|
||||
paddingLeft: 25,
|
||||
},
|
||||
backBtn: {
|
||||
backgroundColor: "red",
|
||||
marginLeft: -2,
|
||||
justifyContent: "flex-start",
|
||||
},
|
||||
card: {
|
||||
backgroundColor: "white",
|
||||
width: "100%",
|
||||
padding: 20,
|
||||
paddingBottom: 30,
|
||||
marginTop: 20,
|
||||
borderRadius: 12,
|
||||
},
|
||||
noPaddingCard: {
|
||||
backgroundColor: "white",
|
||||
width: "100%",
|
||||
marginTop: 20,
|
||||
borderRadius: 12,
|
||||
},
|
||||
colorBox: {
|
||||
aspectRatio: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: 51,
|
||||
borderRadius: 12,
|
||||
},
|
||||
checkbox: {
|
||||
borderRadius: 50,
|
||||
},
|
||||
addCalLbl: {
|
||||
fontSize: 16,
|
||||
fontFamily: "PlusJakartaSan_500Medium",
|
||||
flexWrap: "wrap",
|
||||
width: "75%",
|
||||
textAlign: "left",
|
||||
lineHeight: 20,
|
||||
overflow: "visible"
|
||||
},
|
||||
subTitle: {
|
||||
fontFamily: "Manrope_600SemiBold",
|
||||
fontSize: 18,
|
||||
},
|
||||
cardTitle: {
|
||||
fontFamily: "Manrope_500Medium",
|
||||
fontSize: 15,
|
||||
},
|
||||
});
|
||||
|
||||
export default CalendarSettingsPage;
|
||||
|
||||
@ -1,105 +1,118 @@
|
||||
import { View, Text, Button } from "react-native-ui-lib";
|
||||
import React, { useState } from "react";
|
||||
import { StyleSheet } from "react-native";
|
||||
import { Entypo, Ionicons, Octicons } from "@expo/vector-icons";
|
||||
import {Button, Text, View} from "react-native-ui-lib";
|
||||
import React, {useState} from "react";
|
||||
import {StyleSheet} from "react-native";
|
||||
import {Octicons} from "@expo/vector-icons";
|
||||
import CalendarSettingsPage from "./CalendarSettingsPage";
|
||||
import ChoreRewardSettings from "./ChoreRewardSettings";
|
||||
import UserSettings from "./UserSettings";
|
||||
import { AuthContextProvider } from "@/contexts/AuthContext";
|
||||
import ProfileIcon from "@/assets/svgs/ProfileIcon";
|
||||
import CalendarIcon from "@/assets/svgs/CalendarIcon";
|
||||
import PrivacyPolicyIcon from "@/assets/svgs/PrivacyPolicyIcon";
|
||||
import ArrowRightIcon from "@/assets/svgs/ArrowRightIcon";
|
||||
|
||||
const pageIndex = {
|
||||
main: 0,
|
||||
user: 1,
|
||||
calendar: 2,
|
||||
chore: 3,
|
||||
policy: 4,
|
||||
main: 0,
|
||||
user: 1,
|
||||
calendar: 2,
|
||||
chore: 3,
|
||||
policy: 4,
|
||||
};
|
||||
|
||||
const SettingsPage = () => {
|
||||
const [selectedPage, setSelectedPage] = useState<number>(0);
|
||||
return (
|
||||
<View flexG>
|
||||
{selectedPage == 0 && (
|
||||
<View flexG centerH marginH-30 marginT-30>
|
||||
<Button
|
||||
backgroundColor="white"
|
||||
style={styles.mainBtn}
|
||||
label="Manage My Profile"
|
||||
labelStyle={styles.label}
|
||||
color="#07b8c7"
|
||||
iconSource={() => (
|
||||
<ProfileIcon style={{marginRight: 10}} color="#07b9c8" />
|
||||
)}
|
||||
onPress={() => setSelectedPage(pageIndex.user)}
|
||||
/>
|
||||
<Button
|
||||
backgroundColor="white"
|
||||
style={styles.mainBtn}
|
||||
label="Calendar Settings"
|
||||
labelStyle={styles.label}
|
||||
color="#fd1775"
|
||||
iconSource={() => (
|
||||
<CalendarIcon style={{marginRight: 10}}/>
|
||||
)}
|
||||
onPress={() => {
|
||||
setSelectedPage(pageIndex.calendar);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
backgroundColor="white"
|
||||
style={styles.mainBtn}
|
||||
label="To-Do Reward Settings"
|
||||
labelStyle={styles.label}
|
||||
color="#ff9900"
|
||||
iconSource={() => (
|
||||
<Octicons
|
||||
name="gear"
|
||||
size={24}
|
||||
color="#ff9900"
|
||||
style={{ marginRight: 10 }}
|
||||
/>
|
||||
)}
|
||||
onPress={() => setSelectedPage(pageIndex.chore)}
|
||||
/>
|
||||
<Button
|
||||
backgroundColor="white"
|
||||
style={styles.mainBtn}
|
||||
label="Cally Privacy Policy"
|
||||
labelStyle={styles.label}
|
||||
iconSource={() => (
|
||||
<PrivacyPolicyIcon style={{marginRight: 10}}/>
|
||||
)}
|
||||
color="#6c645b"
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
{selectedPage == pageIndex.calendar && (
|
||||
<CalendarSettingsPage setSelectedPage={setSelectedPage} />
|
||||
)}
|
||||
{selectedPage == pageIndex.chore && (
|
||||
<ChoreRewardSettings setSelectedPage={setSelectedPage} />
|
||||
)}
|
||||
{selectedPage == pageIndex.user && (
|
||||
<UserSettings setSelectedPage={setSelectedPage} />
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
const [selectedPage, setSelectedPage] = useState<number>(0);
|
||||
return (
|
||||
<View flexG>
|
||||
{selectedPage == 0 && (
|
||||
<View flexG centerH marginH-30 marginT-30>
|
||||
<Button
|
||||
backgroundColor="white"
|
||||
style={styles.mainBtn}
|
||||
children={
|
||||
<View row centerV width={"100%"}>
|
||||
<ProfileIcon style={{marginRight: 10}} color="#07b9c8"/>
|
||||
<Text style={styles.label} color="#07b8c7">
|
||||
Manage My Profile
|
||||
</Text>
|
||||
<ArrowRightIcon style={{marginLeft: "auto"}}/>
|
||||
</View>
|
||||
}
|
||||
onPress={() => setSelectedPage(pageIndex.user)}
|
||||
/>
|
||||
<Button
|
||||
backgroundColor="white"
|
||||
style={styles.mainBtn}
|
||||
children={
|
||||
<View row centerV width={"100%"}>
|
||||
<CalendarIcon style={{marginRight: 10}}/>
|
||||
<Text style={styles.label} color="#fd1775">
|
||||
Calendar Settings
|
||||
</Text>
|
||||
<ArrowRightIcon style={{marginLeft: "auto"}}/>
|
||||
</View>
|
||||
}
|
||||
onPress={() => {
|
||||
setSelectedPage(pageIndex.calendar);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
backgroundColor="white"
|
||||
style={styles.mainBtn}
|
||||
children={
|
||||
<View row centerV width={"100%"}>
|
||||
<Octicons
|
||||
name="gear"
|
||||
size={24}
|
||||
color="#ff9900"
|
||||
style={{marginRight: 10}}
|
||||
/>
|
||||
<Text style={styles.label} color="#ff9900">
|
||||
To-Do Reward Settings
|
||||
</Text>
|
||||
<ArrowRightIcon style={{marginLeft: "auto"}}/>
|
||||
</View>
|
||||
}
|
||||
onPress={() => setSelectedPage(pageIndex.chore)}
|
||||
/>
|
||||
<Button
|
||||
backgroundColor="white"
|
||||
style={styles.mainBtn}
|
||||
children={
|
||||
<View row centerV width={"100%"}>
|
||||
<PrivacyPolicyIcon style={{marginRight: 10}}/>
|
||||
<Text style={styles.label} color="#6c645b">
|
||||
Cally Privacy Policy
|
||||
</Text>
|
||||
<ArrowRightIcon style={{marginLeft: "auto"}}/>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
{selectedPage == pageIndex.calendar && (
|
||||
<CalendarSettingsPage setSelectedPage={setSelectedPage}/>
|
||||
)}
|
||||
{selectedPage == pageIndex.chore && (
|
||||
<ChoreRewardSettings setSelectedPage={setSelectedPage}/>
|
||||
)}
|
||||
{selectedPage == pageIndex.user && (
|
||||
<UserSettings setSelectedPage={setSelectedPage}/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsPage;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
mainBtn: {
|
||||
width: "100%",
|
||||
justifyContent: "flex-start",
|
||||
marginBottom: 20,
|
||||
height: 60,
|
||||
},
|
||||
label:{
|
||||
fontFamily: "Poppins_400Regular",
|
||||
fontSize: 14.71,
|
||||
textAlignVertical: 'center'
|
||||
}
|
||||
mainBtn: {
|
||||
width: 311,
|
||||
justifyContent: "flex-start",
|
||||
marginBottom: 20,
|
||||
height: 57.61,
|
||||
},
|
||||
label: {
|
||||
fontFamily: "Poppins_400Regular",
|
||||
fontSize: 14.71,
|
||||
textAlignVertical: "center",
|
||||
},
|
||||
});
|
||||
|
||||
@ -5,14 +5,16 @@ import {
|
||||
Colors,
|
||||
Dialog,
|
||||
FloatingButton,
|
||||
KeyboardAwareScrollView,
|
||||
PanningProvider,
|
||||
Picker,
|
||||
Text,
|
||||
TextField,
|
||||
TextFieldRef,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from "react-native-ui-lib";
|
||||
import React, {useState} from "react";
|
||||
import React, {useEffect, useRef, useState} from "react";
|
||||
import {ScrollView, StyleSheet} from "react-native";
|
||||
import {PickerSingleValue} from "react-native-ui-lib/src/components/picker/types";
|
||||
import {useCreateSubUser} from "@/hooks/firebase/useCreateSubUser";
|
||||
@ -20,6 +22,13 @@ import {ProfileType} from "@/contexts/AuthContext";
|
||||
import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers";
|
||||
import UserMenu from "@/components/pages/settings/user_settings_views/UserMenu";
|
||||
import {uuidv4} from "@firebase/util";
|
||||
import QRIcon from "@/assets/svgs/QRIcon";
|
||||
import EmailIcon from "@/assets/svgs/EmailIcon";
|
||||
import CircledXIcon from "@/assets/svgs/CircledXIcon";
|
||||
import ProfileIcon from "@/assets/svgs/ProfileIcon";
|
||||
import NavToDosIcon from "@/assets/svgs/NavToDosIcon";
|
||||
import Ionicons from "@expo/vector-icons/Ionicons";
|
||||
import {PreviousNextView} from "react-native-keyboard-manager";
|
||||
|
||||
const MyGroup = () => {
|
||||
const [showAddUserDialog, setShowAddUserDialog] = useState(false);
|
||||
@ -31,6 +40,9 @@ const MyGroup = () => {
|
||||
const [lastName, setLastName] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
|
||||
const lNameRef = useRef<TextFieldRef>(null);
|
||||
const emailRef = useRef<TextFieldRef>(null);
|
||||
|
||||
const [showQRCodeDialog, setShowQRCodeDialog] = useState("");
|
||||
|
||||
const {mutateAsync: createSubUser, isLoading, isError} = useCreateSubUser();
|
||||
@ -43,10 +55,14 @@ const MyGroup = () => {
|
||||
const caregivers =
|
||||
familyMembers?.filter((x) => x.userType === ProfileType.CAREGIVER) ?? [];
|
||||
const familyDevices =
|
||||
familyMembers?.filter((x) => x.userType === ProfileType.FAMILY_DEVICE) ?? [];
|
||||
familyMembers?.filter((x) => x.userType === ProfileType.FAMILY_DEVICE) ??
|
||||
[];
|
||||
|
||||
const handleCreateSubUser = async () => {
|
||||
if (!firstName || (selectedStatus !== ProfileType.FAMILY_DEVICE && !lastName)) {
|
||||
if (
|
||||
!firstName ||
|
||||
(selectedStatus !== ProfileType.FAMILY_DEVICE && !lastName)
|
||||
) {
|
||||
console.error("First name and last name are required");
|
||||
return;
|
||||
}
|
||||
@ -68,23 +84,29 @@ const MyGroup = () => {
|
||||
password: uuidv4(),
|
||||
userType: selectedStatus as ProfileType,
|
||||
});
|
||||
console.log(res)
|
||||
console.log(res);
|
||||
|
||||
if (!isError) {
|
||||
setShowNewUserInfoDialog(false);
|
||||
|
||||
if(res?.data?.userId) {
|
||||
if (res?.data?.userId) {
|
||||
setTimeout(() => {
|
||||
setShowQRCodeDialog(res.data.userId)
|
||||
}, 500)
|
||||
setShowQRCodeDialog(res.data.userId);
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setFirstName("");
|
||||
setLastName("");
|
||||
setEmail("");
|
||||
}, [])
|
||||
|
||||
|
||||
// @ts-ignore
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<View style={{flex: 1, minHeight: 500}}>
|
||||
<View>
|
||||
<ScrollView style={styles.card}>
|
||||
{!parents.length && !children.length && !caregivers.length && (
|
||||
@ -126,7 +148,11 @@ const MyGroup = () => {
|
||||
|
||||
<View flex-1/>
|
||||
|
||||
<UserMenu setShowQRCodeDialog={(val) => setShowQRCodeDialog("")} showQRCodeDialog={showQRCodeDialog === member?.uid} userId={member?.uid!}/>
|
||||
<UserMenu
|
||||
setShowQRCodeDialog={(val) => setShowQRCodeDialog("")}
|
||||
showQRCodeDialog={showQRCodeDialog === member?.uid}
|
||||
userId={member?.uid!}
|
||||
/>
|
||||
</Card>
|
||||
))}
|
||||
</>
|
||||
@ -161,7 +187,11 @@ const MyGroup = () => {
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<UserMenu setShowQRCodeDialog={(val) => setShowQRCodeDialog("")} showQRCodeDialog={showQRCodeDialog === member?.uid} userId={member?.uid!}/>
|
||||
<UserMenu
|
||||
setShowQRCodeDialog={(val) => setShowQRCodeDialog("")}
|
||||
showQRCodeDialog={showQRCodeDialog === member?.uid}
|
||||
userId={member?.uid!}
|
||||
/>
|
||||
</Card>
|
||||
))}
|
||||
</>
|
||||
@ -188,15 +218,17 @@ const MyGroup = () => {
|
||||
backgroundColor={Colors.grey60}
|
||||
/>
|
||||
<View marginL-10>
|
||||
<Text text70M>
|
||||
{member.firstName}
|
||||
</Text>
|
||||
<Text text70M>{member.firstName}</Text>
|
||||
<Text text90 grey40>
|
||||
Family Device
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<UserMenu setShowQRCodeDialog={(val) => setShowQRCodeDialog("")} showQRCodeDialog={showQRCodeDialog === member?.uid} userId={member?.uid!}/>
|
||||
<UserMenu
|
||||
setShowQRCodeDialog={(val) => setShowQRCodeDialog("")}
|
||||
showQRCodeDialog={showQRCodeDialog === member?.uid}
|
||||
userId={member?.uid!}
|
||||
/>
|
||||
</Card>
|
||||
))}
|
||||
</>
|
||||
@ -211,6 +243,7 @@ const MyGroup = () => {
|
||||
button={{
|
||||
label: "+ Add a user device",
|
||||
onPress: () => setShowAddUserDialog(true),
|
||||
style: styles.bottomButton,
|
||||
}}
|
||||
/>
|
||||
|
||||
@ -219,13 +252,26 @@ const MyGroup = () => {
|
||||
onDismiss={() => setShowAddUserDialog(false)}
|
||||
panDirection={PanningProvider.Directions.DOWN}
|
||||
>
|
||||
<Card padding-25 gap-10>
|
||||
<Text>Add a new user device</Text>
|
||||
<Card
|
||||
paddingH-25
|
||||
paddingT-40
|
||||
paddingB-20
|
||||
gap-10
|
||||
centerH
|
||||
borderRadius={20}
|
||||
>
|
||||
<Text style={styles.dialogTitle} marginB-35>
|
||||
Add a new user device
|
||||
</Text>
|
||||
|
||||
<Button backgroundColor={"#FD1775"}>
|
||||
<Text white>Show a QR Code</Text>
|
||||
<Button backgroundColor={"#FD1775"} style={styles.dialogBtn}>
|
||||
<QRIcon/>
|
||||
<Text style={styles.dialogBtnLbl} marginL-7>
|
||||
Show a QR Code
|
||||
</Text>
|
||||
</Button>
|
||||
<Button
|
||||
style={styles.dialogBtn}
|
||||
backgroundColor={"#05A8B6"}
|
||||
onPress={() => {
|
||||
setShowAddUserDialog(false);
|
||||
@ -234,11 +280,18 @@ const MyGroup = () => {
|
||||
}, 500);
|
||||
}}
|
||||
>
|
||||
<Text white>Enter email address</Text>
|
||||
<EmailIcon/>
|
||||
<Text style={styles.dialogBtnLbl} marginL-7>
|
||||
Enter email address
|
||||
</Text>
|
||||
</Button>
|
||||
|
||||
<TouchableOpacity onPress={() => setShowAddUserDialog(false)} center>
|
||||
<Text>Return to user settings</Text>
|
||||
<TouchableOpacity
|
||||
onPress={() => setShowAddUserDialog(false)}
|
||||
center
|
||||
marginT-30
|
||||
>
|
||||
<Text style={styles.dialogBackBtn}>Return to user settings</Text>
|
||||
</TouchableOpacity>
|
||||
</Card>
|
||||
</Dialog>
|
||||
@ -248,98 +301,167 @@ const MyGroup = () => {
|
||||
visible={showNewUserInfoDialog}
|
||||
onDismiss={() => setShowNewUserInfoDialog(false)}
|
||||
>
|
||||
<Card padding-25 style={styles.dialogCard}>
|
||||
<View row spread>
|
||||
<Text text60M>New User Information</Text>
|
||||
<TouchableOpacity onPress={() => setShowAddUserDialog(false)}>
|
||||
<Text>X</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<PreviousNextView>
|
||||
<KeyboardAwareScrollView>
|
||||
<Card padding-25 style={styles.dialogCard}>
|
||||
<View row spread>
|
||||
<Text style={{fontFamily: "Manrope_500Medium", fontSize: 16}}>
|
||||
New User Information
|
||||
</Text>
|
||||
<TouchableOpacity onPress={() => {
|
||||
setShowNewUserInfoDialog(false)
|
||||
}}>
|
||||
<CircledXIcon/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.divider} spread/>
|
||||
|
||||
<View row centerV gap-20 marginV-20>
|
||||
<Avatar
|
||||
imageStyle={{borderRadius: 10}}
|
||||
containerStyle={{borderRadius: 10}}
|
||||
size={60}
|
||||
backgroundColor={Colors.grey60}
|
||||
/>
|
||||
<TouchableOpacity onPress={() => {
|
||||
}}>
|
||||
<Text style={{color: Colors.green10}}>
|
||||
Upload User Profile Photo
|
||||
<View row centerV gap-20 marginV-20>
|
||||
<View
|
||||
height={65.54}
|
||||
width={65.54}
|
||||
children={
|
||||
<ProfileIcon color={"#d6d6d6"} width={37} height={37}/>
|
||||
}
|
||||
backgroundColor={Colors.grey60}
|
||||
style={{borderRadius: 25}}
|
||||
center
|
||||
/>
|
||||
<TouchableOpacity onPress={() => {
|
||||
}}>
|
||||
<Text color="#50be0c" style={styles.jakarta13} marginL-15>
|
||||
Upload User Profile Photo
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<Text style={styles.jakarta12}>Member Status</Text>
|
||||
<View style={styles.viewPicker}>
|
||||
<Picker
|
||||
editable={!isLoading}
|
||||
value={selectedStatus}
|
||||
onChange={(item) => setSelectedStatus(item)}
|
||||
showSearch
|
||||
floatingPlaceholder
|
||||
style={styles.inViewPicker}
|
||||
trailingAccessory={
|
||||
<View style={{
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
marginTop: -38,
|
||||
paddingRight: 15
|
||||
}}>
|
||||
<Ionicons name={"chevron-down"} style={{alignSelf: "center"}} size={20}
|
||||
color={"#000000"}/>
|
||||
</View>
|
||||
}
|
||||
>
|
||||
<Picker.Item label="Child" value={ProfileType.CHILD}/>
|
||||
<Picker.Item label="Parent" value={ProfileType.PARENT}/>
|
||||
<Picker.Item label="Caregiver" value={ProfileType.CAREGIVER}/>
|
||||
<Picker.Item
|
||||
label="Family Device"
|
||||
value={ProfileType.FAMILY_DEVICE}
|
||||
/>
|
||||
</Picker>
|
||||
</View>
|
||||
|
||||
<Text style={styles.jakarta12}>
|
||||
{selectedStatus === ProfileType.FAMILY_DEVICE
|
||||
? "Device Name"
|
||||
: "First Name"}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<Text style={styles.label}>Member Status</Text>
|
||||
<Picker
|
||||
editable={!isLoading}
|
||||
value={selectedStatus}
|
||||
//@ts-ignore
|
||||
onChange={(item) => setSelectedStatus(item)}
|
||||
style={styles.picker}
|
||||
showSearch
|
||||
floatingPlaceholder
|
||||
>
|
||||
<Picker.Item label="Child" value={ProfileType.CHILD}/>
|
||||
<Picker.Item label="Parent" value={ProfileType.PARENT}/>
|
||||
<Picker.Item label="Caregiver" value={ProfileType.CAREGIVER}/>
|
||||
<Picker.Item label="Family Device" value={ProfileType.FAMILY_DEVICE}/>
|
||||
</Picker>
|
||||
|
||||
<Text style={styles.label}>
|
||||
{selectedStatus === ProfileType.FAMILY_DEVICE ? "Device Name" : "First Name"}
|
||||
</Text>
|
||||
<TextField
|
||||
editable={!isLoading}
|
||||
placeholder={selectedStatus === ProfileType.FAMILY_DEVICE ? "Device name" : "First name"}
|
||||
value={firstName}
|
||||
onChangeText={setFirstName}
|
||||
style={styles.inputField}
|
||||
/>
|
||||
|
||||
{selectedStatus !== ProfileType.FAMILY_DEVICE && (
|
||||
<>
|
||||
<Text style={styles.label}>Last Name</Text>
|
||||
<TextField
|
||||
editable={!isLoading}
|
||||
placeholder="Last name"
|
||||
value={lastName}
|
||||
onChangeText={setLastName}
|
||||
placeholder={
|
||||
selectedStatus === ProfileType.FAMILY_DEVICE
|
||||
? "Device name"
|
||||
: "First name"
|
||||
}
|
||||
value={firstName}
|
||||
onChangeText={setFirstName}
|
||||
style={styles.inputField}
|
||||
onSubmitEditing={() => {
|
||||
lNameRef.current?.focus()
|
||||
}}
|
||||
blurOnSubmit={false}
|
||||
returnKeyType="next"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{selectedStatus !== ProfileType.FAMILY_DEVICE && (
|
||||
<>
|
||||
<Text style={styles.label}>Email Address (Optional)</Text>
|
||||
<TextField
|
||||
editable={!isLoading}
|
||||
placeholder="Email address"
|
||||
value={email}
|
||||
onChangeText={setEmail}
|
||||
keyboardType="email-address"
|
||||
autoCapitalize="none"
|
||||
style={styles.inputField}
|
||||
{selectedStatus !== ProfileType.FAMILY_DEVICE && (
|
||||
<>
|
||||
<Text style={styles.jakarta12}>Last Name</Text>
|
||||
<TextField
|
||||
ref={lNameRef}
|
||||
editable={!isLoading}
|
||||
placeholder="Last name"
|
||||
value={lastName}
|
||||
onChangeText={setLastName}
|
||||
style={styles.inputField}
|
||||
onSubmitEditing={() => {
|
||||
emailRef.current?.focus()
|
||||
}}
|
||||
blurOnSubmit={false}
|
||||
returnKeyType="next"
|
||||
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{selectedStatus !== ProfileType.FAMILY_DEVICE && (
|
||||
<>
|
||||
<Text style={styles.jakarta12}>Email Address (Optional)</Text>
|
||||
<TextField
|
||||
ref={emailRef}
|
||||
editable={!isLoading}
|
||||
placeholder="Email address"
|
||||
value={email}
|
||||
onChangeText={setEmail}
|
||||
keyboardType="email-address"
|
||||
autoCapitalize="none"
|
||||
style={styles.inputField}
|
||||
returnKeyType="done"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Button
|
||||
disabled={
|
||||
!firstName ||
|
||||
(selectedStatus !== ProfileType.FAMILY_DEVICE && !lastName) ||
|
||||
isLoading
|
||||
}
|
||||
label={isLoading ? "Adding..." : "Add group member"}
|
||||
backgroundColor="#fd1775"
|
||||
labelStyle={{
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 15,
|
||||
marginLeft: 7,
|
||||
}}
|
||||
style={{marginTop: 20, backgroundColor: "#fd1775"}}
|
||||
iconSource={() => <NavToDosIcon width={22} color={"white"}/>}
|
||||
onPress={handleCreateSubUser}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Button
|
||||
disabled={!firstName || (selectedStatus !== ProfileType.FAMILY_DEVICE && !lastName) || isLoading}
|
||||
label={isLoading ? "Adding..." : "Add group member"}
|
||||
backgroundColor="#FD1775"
|
||||
style={{marginTop: 20}}
|
||||
onPress={handleCreateSubUser}
|
||||
/>
|
||||
</Card>
|
||||
</Card>
|
||||
</KeyboardAwareScrollView>
|
||||
</PreviousNextView>
|
||||
</Dialog>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
dialogBtn: {
|
||||
height: 47,
|
||||
width: 279,
|
||||
},
|
||||
dialogTitle: {fontFamily: "Manrope_600SemiBold", fontSize: 22},
|
||||
dialogBackBtn: {
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 15,
|
||||
color: "#a7a7a7",
|
||||
},
|
||||
card: {
|
||||
marginVertical: 15,
|
||||
backgroundColor: "white",
|
||||
@ -347,6 +469,11 @@ const styles = StyleSheet.create({
|
||||
borderRadius: 15,
|
||||
padding: 20,
|
||||
},
|
||||
bottomButton: {
|
||||
position: "absolute",
|
||||
bottom: 80,
|
||||
width: "100%",
|
||||
},
|
||||
familyCard: {
|
||||
marginBottom: 10,
|
||||
borderRadius: 10,
|
||||
@ -354,6 +481,9 @@ const styles = StyleSheet.create({
|
||||
width: "100%",
|
||||
},
|
||||
inputField: {
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 13,
|
||||
color: "#565656",
|
||||
borderRadius: 50,
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 16,
|
||||
@ -373,6 +503,26 @@ const styles = StyleSheet.create({
|
||||
borderWidth: 1,
|
||||
marginTop: -20,
|
||||
height: 40,
|
||||
zIndex: 10,
|
||||
},
|
||||
viewPicker: {
|
||||
borderRadius: 50,
|
||||
backgroundColor: Colors.grey80,
|
||||
marginBottom: 16,
|
||||
borderColor: Colors.grey50,
|
||||
borderWidth: 1,
|
||||
marginTop: 0,
|
||||
height: 40,
|
||||
zIndex: 10,
|
||||
},
|
||||
inViewPicker: {
|
||||
borderRadius: 50,
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 16,
|
||||
marginBottom: 16,
|
||||
marginTop: -20,
|
||||
height: 40,
|
||||
zIndex: 10,
|
||||
},
|
||||
label: {
|
||||
marginBottom: 5,
|
||||
@ -386,7 +536,22 @@ const styles = StyleSheet.create({
|
||||
subTit: {
|
||||
fontFamily: "Manrope_500Medium",
|
||||
fontSize: 15,
|
||||
},
|
||||
},
|
||||
dialogBtnLbl: {
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 15,
|
||||
color: "white",
|
||||
},
|
||||
divider: {height: 0.7, backgroundColor: "#e6e6e6", width: "100%"},
|
||||
jakarta12: {
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 12,
|
||||
color: "#a1a1a1",
|
||||
},
|
||||
jakarta13: {
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 13,
|
||||
},
|
||||
});
|
||||
|
||||
export default MyGroup;
|
||||
|
||||
@ -1,122 +1,207 @@
|
||||
import { Text, TextField, View } from "react-native-ui-lib";
|
||||
import React, { useState } from "react";
|
||||
import { ImageBackground, StyleSheet } from "react-native";
|
||||
import { ScrollView } from "react-native-gesture-handler";
|
||||
import { useAuthContext } from "@/contexts/AuthContext";
|
||||
import { useUpdateUserData } from "@/hooks/firebase/useUpdateUserData";
|
||||
import {Colors, Picker, Text, TextField, View} from "react-native-ui-lib";
|
||||
import React, {useEffect, useRef, useState} from "react";
|
||||
import {ImageBackground, StyleSheet} from "react-native";
|
||||
import {ScrollView} from "react-native-gesture-handler";
|
||||
import {useAuthContext} from "@/contexts/AuthContext";
|
||||
import {useUpdateUserData} from "@/hooks/firebase/useUpdateUserData";
|
||||
import Ionicons from "@expo/vector-icons/Ionicons";
|
||||
import * as tz from 'tzdata';
|
||||
import * as Localization from 'expo-localization';
|
||||
import debounce from "debounce";
|
||||
|
||||
const MyProfile = () => {
|
||||
const { user, profileData } = useAuthContext();
|
||||
const {user, profileData} = useAuthContext();
|
||||
|
||||
const [lastName, setLastName] = useState<string>(profileData?.lastName || "");
|
||||
const [firstName, setFirstName] = useState<string>(
|
||||
profileData?.firstName || ""
|
||||
);
|
||||
const [timeZone, setTimeZone] = useState<string>(profileData?.timeZone! ?? Localization.getCalendars()[0].timeZone);
|
||||
const [lastName, setLastName] = useState<string>(profileData?.lastName || "");
|
||||
const [firstName, setFirstName] = useState<string>(
|
||||
profileData?.firstName || ""
|
||||
);
|
||||
|
||||
const { mutateAsync: updateUserData } = useUpdateUserData();
|
||||
return (
|
||||
<ScrollView style={{ paddingBottom: 100, flex: 1 }}>
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.subTit}>Your Profile</Text>
|
||||
<View row spread paddingH-15 centerV marginV-15>
|
||||
<ImageBackground
|
||||
style={styles.pfp}
|
||||
source={require("../../../../assets/images/profile-picture.png")}
|
||||
/>
|
||||
const {mutateAsync: updateUserData} = useUpdateUserData();
|
||||
const isFirstRender = useRef(true);
|
||||
|
||||
<Text style={styles.photoSet} color="#50be0c">
|
||||
Change Photo
|
||||
</Text>
|
||||
<Text style={styles.photoSet}>Remove Photo</Text>
|
||||
</View>
|
||||
<View paddingH-15>
|
||||
<Text text80 marginT-10 marginB-7 style={styles.label}>
|
||||
First name
|
||||
</Text>
|
||||
<TextField
|
||||
text70
|
||||
placeholder="First name"
|
||||
style={styles.txtBox}
|
||||
value={firstName}
|
||||
onChangeText={async (value) => {
|
||||
setFirstName(value);
|
||||
await updateUserData({ newUserData: { firstName: value } });
|
||||
}}
|
||||
/>
|
||||
<Text text80 marginT-10 marginB-7 style={styles.label}>
|
||||
Last name
|
||||
</Text>
|
||||
<TextField
|
||||
text70
|
||||
placeholder="Last name"
|
||||
style={styles.txtBox}
|
||||
value={lastName}
|
||||
onChangeText={async (value) => {
|
||||
setLastName(value);
|
||||
await updateUserData({ newUserData: { lastName: value } });
|
||||
}}
|
||||
/>
|
||||
<Text text80 marginT-10 marginB-7 style={styles.label}>
|
||||
Email address
|
||||
</Text>
|
||||
<TextField
|
||||
text70
|
||||
placeholder="Email address"
|
||||
value={user?.email?.toString()}
|
||||
style={styles.txtBox}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.subTit}>Settings</Text>
|
||||
<Text text80 marginT-20 marginB-7 style={styles.label}>
|
||||
Time Zone
|
||||
</Text>
|
||||
<TextField text70 placeholder="Time Zone" style={styles.txtBox} />
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
const handleUpdateUserData = async () => {
|
||||
await updateUserData({newUserData: {firstName, lastName, timeZone}});
|
||||
}
|
||||
|
||||
const debouncedUserDataUpdate = debounce(handleUpdateUserData, 500);
|
||||
|
||||
useEffect(() => {
|
||||
if (isFirstRender.current) {
|
||||
isFirstRender.current = false;
|
||||
return;
|
||||
}
|
||||
debouncedUserDataUpdate();
|
||||
}, [timeZone, lastName, firstName]);
|
||||
|
||||
return (
|
||||
<ScrollView style={{paddingBottom: 100, flex: 1}}>
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.subTit}>Your Profile</Text>
|
||||
<View row spread paddingH-15 centerV marginV-15>
|
||||
<ImageBackground
|
||||
style={styles.pfp}
|
||||
source={require("../../../../assets/images/profile-picture.png")}
|
||||
/>
|
||||
|
||||
<Text style={styles.photoSet} color="#50be0c">
|
||||
Change Photo
|
||||
</Text>
|
||||
<Text style={styles.photoSet}>Remove Photo</Text>
|
||||
</View>
|
||||
<View paddingH-15>
|
||||
<Text text80 marginT-10 marginB-7 style={styles.label}>
|
||||
First name
|
||||
</Text>
|
||||
<TextField
|
||||
text70
|
||||
placeholder="First name"
|
||||
style={styles.txtBox}
|
||||
value={firstName}
|
||||
onChangeText={async (value) => {
|
||||
setFirstName(value);
|
||||
}}
|
||||
/>
|
||||
<Text text80 marginT-10 marginB-7 style={styles.label}>
|
||||
Last name
|
||||
</Text>
|
||||
<TextField
|
||||
text70
|
||||
placeholder="Last name"
|
||||
style={styles.txtBox}
|
||||
value={lastName}
|
||||
onChangeText={async (value) => {
|
||||
setLastName(value);
|
||||
}}
|
||||
/>
|
||||
<Text text80 marginT-10 marginB-7 style={styles.label}>
|
||||
Email address
|
||||
</Text>
|
||||
<TextField
|
||||
editable={false}
|
||||
text70
|
||||
placeholder="Email address"
|
||||
value={user?.email?.toString()}
|
||||
style={styles.txtBox}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.subTit}>Settings</Text>
|
||||
<Text style={styles.jakarta12}>Time Zone</Text>
|
||||
<View style={styles.viewPicker}>
|
||||
<Picker
|
||||
// editable={!isLoading}
|
||||
value={timeZone}
|
||||
onChange={(item) => {
|
||||
setTimeZone(item as string)
|
||||
}}
|
||||
showSearch
|
||||
floatingPlaceholder
|
||||
style={styles.inViewPicker}
|
||||
trailingAccessory={
|
||||
<View style={{
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
marginTop: -38,
|
||||
paddingRight: 15
|
||||
}}>
|
||||
<Ionicons name={"chevron-down"} style={{alignSelf: "center"}} size={20}
|
||||
color={"#000000"}/>
|
||||
</View>
|
||||
}
|
||||
>
|
||||
{timeZoneItems}
|
||||
</Picker>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const timeZoneItems = Object.keys(tz.zones).sort().map((zone) => (
|
||||
<Picker.Item key={zone} label={zone.replace("/", " / ").replace("_", " ")} value={zone}/>
|
||||
));
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
card: {
|
||||
marginVertical: 15,
|
||||
backgroundColor: "white",
|
||||
width: "100%",
|
||||
borderRadius: 12,
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 21,
|
||||
},
|
||||
pfp: {
|
||||
aspectRatio: 1,
|
||||
width: 65.54,
|
||||
backgroundColor: "green",
|
||||
borderRadius: 20,
|
||||
},
|
||||
txtBox: {
|
||||
backgroundColor: "#fafafa",
|
||||
borderRadius: 50,
|
||||
borderWidth: 2,
|
||||
borderColor: "#cecece",
|
||||
padding: 15,
|
||||
height: 45,
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 13
|
||||
},
|
||||
subTit: {
|
||||
fontFamily: "Manrope_500Medium",
|
||||
fontSize: 15,
|
||||
},
|
||||
label: {
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 12,
|
||||
color: "#a1a1a1"
|
||||
},
|
||||
photoSet:{
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 13.07
|
||||
}
|
||||
card: {
|
||||
marginVertical: 15,
|
||||
backgroundColor: "white",
|
||||
width: "100%",
|
||||
borderRadius: 12,
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 21,
|
||||
},
|
||||
pfp: {
|
||||
aspectRatio: 1,
|
||||
width: 65.54,
|
||||
backgroundColor: "green",
|
||||
borderRadius: 20,
|
||||
},
|
||||
txtBox: {
|
||||
backgroundColor: "#fafafa",
|
||||
borderRadius: 50,
|
||||
borderWidth: 2,
|
||||
borderColor: "#cecece",
|
||||
padding: 15,
|
||||
height: 45,
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 13
|
||||
},
|
||||
subTit: {
|
||||
fontFamily: "Manrope_500Medium",
|
||||
fontSize: 15,
|
||||
},
|
||||
label: {
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 12,
|
||||
color: "#a1a1a1"
|
||||
},
|
||||
photoSet: {
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 13.07
|
||||
},
|
||||
jakarta12: {
|
||||
paddingVertical: 10,
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 12,
|
||||
color: "#a1a1a1",
|
||||
},
|
||||
picker: {
|
||||
borderRadius: 50,
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 16,
|
||||
backgroundColor: Colors.grey80,
|
||||
marginBottom: 16,
|
||||
borderColor: Colors.grey50,
|
||||
borderWidth: 1,
|
||||
marginTop: -20,
|
||||
height: 40,
|
||||
zIndex: 10,
|
||||
},
|
||||
viewPicker: {
|
||||
borderRadius: 50,
|
||||
backgroundColor: Colors.grey80,
|
||||
marginBottom: 16,
|
||||
borderColor: Colors.grey50,
|
||||
borderWidth: 1,
|
||||
marginTop: 0,
|
||||
height: 40,
|
||||
zIndex: 10,
|
||||
},
|
||||
inViewPicker: {
|
||||
borderRadius: 50,
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 16,
|
||||
marginBottom: 16,
|
||||
marginTop: -20,
|
||||
height: 40,
|
||||
zIndex: 10,
|
||||
},
|
||||
});
|
||||
|
||||
export default MyProfile;
|
||||
|
||||
@ -30,7 +30,7 @@ const UserMenu = ({
|
||||
customContent={
|
||||
<View height={18}>
|
||||
<ListItem onPress={handleShowQRCode}>
|
||||
<Text>Show Login QR Code</Text>
|
||||
<Text style={{fontFamily: "Manrope_500Medium"}}>Show Login QR Code</Text>
|
||||
</ListItem>
|
||||
</View>
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { View, Text, Button, Switch } from "react-native-ui-lib";
|
||||
import React, { useState } from "react";
|
||||
import React, { useRef, useState } from "react";
|
||||
import PointsSlider from "@/components/shared/PointsSlider";
|
||||
import { repeatOptions, useToDosContext } from "@/contexts/ToDosContext";
|
||||
import { Feather, AntDesign, Ionicons } from "@expo/vector-icons";
|
||||
@ -14,6 +14,7 @@ import { PanningDirectionsEnum } from "react-native-ui-lib/src/incubator/panView
|
||||
import { Dimensions, StyleSheet } from "react-native";
|
||||
import DropModalIcon from "@/assets/svgs/DropModalIcon";
|
||||
import { IToDo } from "@/hooks/firebase/types/todoData";
|
||||
import AssigneesDisplay from "@/components/shared/AssigneesDisplay";
|
||||
|
||||
interface IAddChoreDialog {
|
||||
isVisible: boolean;
|
||||
@ -111,6 +112,7 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
|
||||
</View>
|
||||
<TextField
|
||||
placeholder="Add a To Do"
|
||||
autoFocus
|
||||
value={todo?.title}
|
||||
onChangeText={(text) => {
|
||||
setTodo((oldValue: IToDo) => ({ ...oldValue, title: text }));
|
||||
@ -197,25 +199,8 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
|
||||
label="Assign"
|
||||
/>
|
||||
</View>
|
||||
<View row marginH-13 marginT-13>
|
||||
<View
|
||||
marginL-30
|
||||
style={{
|
||||
aspectRatio: 1,
|
||||
width: 50,
|
||||
backgroundColor: "red",
|
||||
borderRadius: 50,
|
||||
}}
|
||||
/>
|
||||
<View
|
||||
marginL-30
|
||||
style={{
|
||||
aspectRatio: 1,
|
||||
width: 50,
|
||||
backgroundColor: "red",
|
||||
borderRadius: 50,
|
||||
}}
|
||||
/>
|
||||
<View row marginL-27 marginT-0>
|
||||
<AssigneesDisplay />
|
||||
</View>
|
||||
<View row centerV style={styles.rotateSwitch}>
|
||||
<Text text80>Take Turns</Text>
|
||||
|
||||
@ -1,23 +1,77 @@
|
||||
import React from "react";
|
||||
import { ImageBackground, StyleSheet } from "react-native";
|
||||
import { View } from "react-native-ui-lib";
|
||||
import {ImageBackground, StyleSheet} from "react-native";
|
||||
import {Text, TouchableOpacity, View} from "react-native-ui-lib";
|
||||
import RemoveAssigneeBtn from "./RemoveAssigneeBtn";
|
||||
import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers";
|
||||
|
||||
const AssigneesDisplay = () => {
|
||||
return (
|
||||
<View row marginH-13 marginT-13 gap-20>
|
||||
<ImageBackground
|
||||
source={require("../../assets/images/child-picture.png")}
|
||||
style={{ aspectRatio: 1, width: 58.08, overflow: "hidden" }}
|
||||
children={<RemoveAssigneeBtn />}
|
||||
/>
|
||||
<ImageBackground
|
||||
source={require("../../assets/images/child1-picture.png")}
|
||||
style={{ aspectRatio: 1, width: 58.08, overflow: "hidden" }}
|
||||
children={<RemoveAssigneeBtn />}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
const AssigneesDisplay = ({selectedAttendees, setSlectedAttendees}: {
|
||||
selectedAttendees: string[],
|
||||
setSlectedAttendees: (value: React.SetStateAction<string[]>) => void
|
||||
}) => {
|
||||
const {data: members} = useGetFamilyMembers(true);
|
||||
|
||||
const selectedMembers = members?.filter((x) => selectedAttendees.includes(x?.uid!));
|
||||
|
||||
const getInitials = (firstName: string, lastName: string) => {
|
||||
return `${firstName.charAt(0)}${lastName.charAt(0)}`;
|
||||
};
|
||||
|
||||
const removeAttendee = (uid: string) => {
|
||||
setSlectedAttendees((prev) => prev.filter((x) => x !== uid));
|
||||
}
|
||||
|
||||
return (
|
||||
<View row marginH-13 marginT-13 gap-20>
|
||||
{selectedMembers?.map((member) => (
|
||||
<TouchableOpacity key={member.uid} style={styles.assigneeWrapper}
|
||||
onPress={() => removeAttendee(member.uid!)}>
|
||||
{member?.pfp ? (
|
||||
<ImageBackground
|
||||
source={{uri: member?.pfp}}
|
||||
style={styles.image}
|
||||
children={<RemoveAssigneeBtn/>}
|
||||
/>
|
||||
) : (
|
||||
<View style={styles.initialsCircle}>
|
||||
<Text style={styles.initialsText}>
|
||||
{getInitials(member.firstName, member.lastName)}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
<RemoveAssigneeBtn/>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
|
||||
{selectedAttendees.length === 0 && <Text>No attendees added</Text>}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default AssigneesDisplay;
|
||||
const styles = StyleSheet.create({
|
||||
assigneeWrapper: {
|
||||
position: 'relative',
|
||||
width: 58.08,
|
||||
aspectRatio: 1,
|
||||
},
|
||||
image: {
|
||||
aspectRatio: 1,
|
||||
width: '100%',
|
||||
borderRadius: 100, // Makes the image circular
|
||||
overflow: 'hidden',
|
||||
},
|
||||
initialsCircle: {
|
||||
backgroundColor: '#ccc',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderRadius: 100, // Circular shape
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
initialsText: {
|
||||
color: '#fff',
|
||||
fontSize: 24,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
});
|
||||
|
||||
export default AssigneesDisplay;
|
||||
@ -99,6 +99,7 @@ export const AuthContextProvider: FC<{ children: ReactNode }> = ({children}) =>
|
||||
const onAuthStateChangedHandler = async (authUser: FirebaseAuthTypes.User | null) => {
|
||||
setUser(authUser);
|
||||
|
||||
|
||||
if (authUser) {
|
||||
await refreshProfileData(authUser);
|
||||
const pushToken = await registerForPushNotificationsAsync();
|
||||
|
||||
@ -1,194 +0,0 @@
|
||||
// CalendarContext.tsx
|
||||
import React, { createContext, useContext, useState, ReactNode } from "react";
|
||||
|
||||
// Define the CalendarEvent interface
|
||||
export interface CalendarEvent {
|
||||
id?: number | string; // Unique identifier for the event
|
||||
user?: string;
|
||||
title: string; // Event title or name
|
||||
description?: string; // Optional description for the event
|
||||
start: Date; // Start date and time of the event
|
||||
end: Date; // End date and time of the event
|
||||
location?: string; // Optional event location
|
||||
allDay?: boolean; // Specifies if the event lasts all day
|
||||
color?: string; // Optional color to represent the event
|
||||
participants?: string[]; // Optional list of participants or attendees
|
||||
private?: boolean;
|
||||
}
|
||||
|
||||
// Define the context type
|
||||
interface CalendarContextType {
|
||||
events: CalendarEvent[];
|
||||
familyEvents: CalendarEvent[];
|
||||
addEvent: (event: CalendarEvent) => void; // Function to add an event
|
||||
removeEvent: (id: number) => void; // Function to remove an event by ID
|
||||
updateEvent: (changes: Partial<CalendarEvent>, id?: number) => void;
|
||||
}
|
||||
|
||||
// Create the CalendarContext
|
||||
const CalendarContext = createContext<CalendarContextType | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
// Create a provider component
|
||||
export const CalendarProvider: React.FC<{ children: ReactNode }> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [events, setEvents] = useState<CalendarEvent[]>([
|
||||
{
|
||||
id: 1,
|
||||
title: "Team Meeting",
|
||||
description: "Discuss project milestones and deadlines.",
|
||||
start: new Date("2024-09-15T10:00:00"),
|
||||
end: new Date("2024-09-15T11:00:00"),
|
||||
location: "Office Conference Room",
|
||||
allDay: false,
|
||||
color: "#FF5733",
|
||||
participants: ["Alice", "Bob", "Charlie"],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Doctor's Appointment",
|
||||
description: "Annual check-up with Dr. Smith.",
|
||||
start: new Date("2024-09-20T14:30:00"),
|
||||
end: new Date("2024-09-20T15:30:00"),
|
||||
location: "Health Clinic",
|
||||
allDay: false,
|
||||
color: "#33FF57",
|
||||
participants: ["You"],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Birthday Party",
|
||||
description: "Celebrating Sarah's 30th birthday.",
|
||||
start: new Date("2024-09-25T18:00:00"),
|
||||
end: new Date("2024-09-25T21:00:00"),
|
||||
location: "Sarah's House",
|
||||
allDay: false,
|
||||
color: "#3357FF",
|
||||
participants: ["You", "Sarah", "Tom", "Lily"],
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "Project Deadline",
|
||||
description: "Final submission for the project.",
|
||||
start: new Date("2024-10-01T00:00:00"),
|
||||
end: new Date("2024-10-01T23:59:00"),
|
||||
location: "Online",
|
||||
allDay: false,
|
||||
color: "#FF33A1",
|
||||
participants: ["You"],
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: "Halloween Costume Party",
|
||||
description: "Join us for a spooky night of fun!",
|
||||
start: new Date("2024-10-31T19:00:00"),
|
||||
end: new Date("2024-10-31T23:00:00"),
|
||||
location: "Downtown Club",
|
||||
allDay: false,
|
||||
color: "#FFB733",
|
||||
participants: ["You", "Friends"],
|
||||
},
|
||||
]);
|
||||
|
||||
const [familyEvents, setFamilyEvents] = useState<CalendarEvent[]>([
|
||||
{
|
||||
id: 1,
|
||||
user: "jakesId",
|
||||
title: "Team Meeting",
|
||||
description: "Discuss project milestones and deadlines.",
|
||||
start: new Date("2024-09-10T10:00:00"),
|
||||
end: new Date("2024-09-10T11:00:00"),
|
||||
location: "Office Conference Room",
|
||||
allDay: false,
|
||||
color: "#FF5733",
|
||||
participants: ["Alice", "Bob", "Charlie"],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
user: "mikesId",
|
||||
title: "Doctor's Appointment",
|
||||
description: "Annual check-up with Dr. Smith.",
|
||||
start: new Date("2024-09-21T14:30:00"),
|
||||
end: new Date("2024-09-21T15:30:00"),
|
||||
location: "Health Clinic",
|
||||
allDay: false,
|
||||
color: "#33FF57",
|
||||
participants: ["You"],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
user: "jakesId",
|
||||
title: "Birthday Party",
|
||||
description: "Celebrating Sarah's 30th birthday.",
|
||||
start: new Date("2024-09-5T18:00:00"),
|
||||
end: new Date("2024-09-5T21:00:00"),
|
||||
location: "Sarah's House",
|
||||
allDay: false,
|
||||
color: "#3357FF",
|
||||
participants: ["You", "Sarah", "Tom", "Lily"],
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
user: "davidsId",
|
||||
title: "Project Deadline",
|
||||
description: "Final submission for the project.",
|
||||
start: new Date("2024-10-03T00:00:00"),
|
||||
end: new Date("2024-10-03T23:59:00"),
|
||||
location: "Online",
|
||||
allDay: false,
|
||||
color: "#FF33A1",
|
||||
participants: ["You"],
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
user: "jakesId",
|
||||
title: "Halloween Costume Party",
|
||||
description: "Join us for a spooky night of fun!",
|
||||
start: new Date("2024-10-02T19:00:00"),
|
||||
end: new Date("2024-10-02T23:00:00"),
|
||||
location: "Downtown Club",
|
||||
allDay: false,
|
||||
color: "#FFB733",
|
||||
participants: ["You", "Friends"],
|
||||
},
|
||||
]);
|
||||
|
||||
// Function to add an event
|
||||
const addEvent = (event: CalendarEvent) => {
|
||||
event.id = events.length + 1;
|
||||
setEvents((prevEvents) => [...prevEvents, event]);
|
||||
};
|
||||
|
||||
// Function to remove an event by ID
|
||||
const removeEvent = (id: number) => {
|
||||
setEvents((prevEvents) => prevEvents.filter((event) => event.id !== id));
|
||||
};
|
||||
|
||||
// Function to update an event
|
||||
const updateEvent = ( changes: Partial<CalendarEvent>, id?: number) => {
|
||||
setEvents((prevEvents) =>
|
||||
prevEvents.map((event) =>
|
||||
event.id === id ? { ...event, ...changes } : event
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<CalendarContext.Provider
|
||||
value={{ events, addEvent, removeEvent, updateEvent, familyEvents }}
|
||||
>
|
||||
{children}
|
||||
</CalendarContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
// Custom hook to use the CalendarContext
|
||||
export const useCalendarContext = () => {
|
||||
const context = useContext(CalendarContext);
|
||||
if (!context) {
|
||||
throw new Error("useCalendar must be used within a CalendarProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@ -11,7 +11,6 @@ const db = admin.firestore();
|
||||
|
||||
let expo = new Expo({accessToken: process.env.EXPO_ACCESS_TOKEN});
|
||||
|
||||
// Firestore trigger that listens for new events in the 'Events' collection
|
||||
exports.sendNotificationOnEventCreation = functions.firestore
|
||||
.document('Events/{eventId}')
|
||||
.onCreate(async (snapshot, context) => {
|
||||
@ -193,6 +192,73 @@ exports.generateCustomToken = onRequest(async (request, response) => {
|
||||
}
|
||||
});
|
||||
|
||||
exports.refreshTokens = functions.pubsub.schedule('every 12 hours').onRun(async (context) => {
|
||||
console.log('Running token refresh job...');
|
||||
|
||||
const profilesSnapshot = await db.collection('Profiles').get();
|
||||
|
||||
profilesSnapshot.forEach(async (profileDoc) => {
|
||||
const profileData = profileDoc.data();
|
||||
|
||||
if (profileData.googleToken) {
|
||||
try {
|
||||
const refreshedGoogleToken = await refreshGoogleToken(profileData.googleToken);
|
||||
await profileDoc.ref.update({ googleToken: refreshedGoogleToken });
|
||||
console.log(`Google token updated for user ${profileDoc.id}`);
|
||||
} catch (error) {
|
||||
console.error(`Error refreshing Google token for user ${profileDoc.id}:`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
if (profileData.microsoftToken) {
|
||||
try {
|
||||
const refreshedMicrosoftToken = await refreshMicrosoftToken(profileData.microsoftToken);
|
||||
await profileDoc.ref.update({ microsoftToken: refreshedMicrosoftToken });
|
||||
console.log(`Microsoft token updated for user ${profileDoc.id}`);
|
||||
} catch (error) {
|
||||
console.error(`Error refreshing Microsoft token for user ${profileDoc.id}:`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
if (profileData.appleToken) {
|
||||
try {
|
||||
const refreshedAppleToken = await refreshAppleToken(profileData.appleToken);
|
||||
await profileDoc.ref.update({ appleToken: refreshedAppleToken });
|
||||
console.log(`Apple token updated for user ${profileDoc.id}`);
|
||||
} catch (error) {
|
||||
console.error(`Error refreshing Apple token for user ${profileDoc.id}:`, error.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
// Function to refresh Google token
|
||||
async function refreshGoogleToken(token) {
|
||||
// Assuming you use OAuth2 token refresh flow
|
||||
const response = await axios.post('https://oauth2.googleapis.com/token', {
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: token, // Add refresh token stored previously
|
||||
client_id: 'YOUR_GOOGLE_CLIENT_ID',
|
||||
client_secret: 'YOUR_GOOGLE_CLIENT_SECRET',
|
||||
});
|
||||
|
||||
return response.data.access_token; // Return new access token
|
||||
}
|
||||
|
||||
async function refreshMicrosoftToken(token) {
|
||||
const response = await axios.post('https://login.microsoftonline.com/common/oauth2/v2.0/token', {
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: token, // Add refresh token stored previously
|
||||
client_id: 'YOUR_MICROSOFT_CLIENT_ID',
|
||||
client_secret: 'YOUR_MICROSOFT_CLIENT_SECRET',
|
||||
scope: 'https://graph.microsoft.com/Calendars.ReadWrite offline_access',
|
||||
});
|
||||
|
||||
return response.data.access_token; // Return new access token
|
||||
}
|
||||
|
||||
async function getPushTokensForEvent() {
|
||||
const usersRef = db.collection('Profiles');
|
||||
const snapshot = await usersRef.get();
|
||||
@ -223,4 +289,4 @@ async function getPushTokensForFamilyExcludingCreator(familyId, creatorId) {
|
||||
});
|
||||
|
||||
return pushTokens;
|
||||
}
|
||||
}
|
||||
@ -10,5 +10,6 @@ export interface EventData {
|
||||
surpriseEvent?: boolean,
|
||||
notes?: string,
|
||||
reminders?: string[]
|
||||
id?: string,
|
||||
id?: string | number,
|
||||
attendees?: string[]
|
||||
}
|
||||
@ -1,39 +1,46 @@
|
||||
import { ProfileType } from "@/contexts/AuthContext";
|
||||
import {ProfileType} from "@/contexts/AuthContext";
|
||||
|
||||
export interface User {
|
||||
uid: string;
|
||||
email: string | null;
|
||||
uid: string;
|
||||
email: string | null;
|
||||
}
|
||||
|
||||
export interface UserProfile {
|
||||
userType: ProfileType;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
childrenIds?: string[];
|
||||
birthday?: Date;
|
||||
parentId?: string;
|
||||
contact?: string;
|
||||
email: string;
|
||||
password: string;
|
||||
familyId?: string;
|
||||
uid?: string;
|
||||
googleToken?: string;
|
||||
microsoftToken?: string;
|
||||
eventColor?: string
|
||||
userType: ProfileType;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
childrenIds?: string[];
|
||||
birthday?: Date;
|
||||
parentId?: string;
|
||||
contact?: string;
|
||||
email: string;
|
||||
password: string;
|
||||
familyId?: string;
|
||||
uid?: string;
|
||||
pfp?: string;
|
||||
googleToken?: string | null;
|
||||
microsoftToken?: string | null;
|
||||
appleToken?: string | null;
|
||||
eventColor?: string | null;
|
||||
googleMail?: string | null;
|
||||
outlookMail?: string | null;
|
||||
appleMail?: string | null;
|
||||
timeZone?: string | null;
|
||||
firstDayOfWeek?: string | null;
|
||||
}
|
||||
|
||||
export interface ParentProfile extends UserProfile {
|
||||
userType: ProfileType.PARENT;
|
||||
childrenIds: string[];
|
||||
userType: ProfileType.PARENT;
|
||||
childrenIds: string[];
|
||||
}
|
||||
|
||||
export interface ChildProfile extends UserProfile {
|
||||
userType: ProfileType.CHILD;
|
||||
birthday: Date;
|
||||
parentId: string;
|
||||
userType: ProfileType.CHILD;
|
||||
birthday: Date;
|
||||
parentId: string;
|
||||
}
|
||||
|
||||
export interface CaregiverProfile extends UserProfile {
|
||||
userType: ProfileType.CAREGIVER;
|
||||
contact: string;
|
||||
userType: ProfileType.CAREGIVER;
|
||||
contact: string;
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useAuthContext } from "@/contexts/AuthContext";
|
||||
import { useMutation, useQueryClient } from "react-query";
|
||||
import {useAuthContext} from "@/contexts/AuthContext";
|
||||
import {useMutation, useQueryClient} from "react-query";
|
||||
import firestore from "@react-native-firebase/firestore";
|
||||
import { EventData } from "@/hooks/firebase/types/eventData";
|
||||
import {EventData} from "@/hooks/firebase/types/eventData";
|
||||
|
||||
export const useCreateEvent = () => {
|
||||
const {user: currentUser, profileData} = useAuthContext()
|
||||
@ -11,7 +11,6 @@ export const useCreateEvent = () => {
|
||||
mutationKey: ["createEvent"],
|
||||
mutationFn: async (eventData: Partial<EventData>) => {
|
||||
try {
|
||||
console.log("CALLLLL")
|
||||
await firestore()
|
||||
.collection("Events")
|
||||
.add({...eventData, creatorId: currentUser?.uid, familyId: profileData?.familyId})
|
||||
@ -25,37 +24,41 @@ export const useCreateEvent = () => {
|
||||
})
|
||||
}
|
||||
|
||||
export const useCreateEventFromProvider = () => {
|
||||
const {user: currentUser} = useAuthContext()
|
||||
const queryClients = useQueryClient()
|
||||
export const useCreateEventsFromProvider = () => {
|
||||
const {user: currentUser} = useAuthContext();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["createEventFromProvider"],
|
||||
mutationFn: async (eventData: Partial<EventData>) => {
|
||||
mutationKey: ["createEventsFromProvider"],
|
||||
mutationFn: async (eventDataArray: Partial<EventData>[]) => {
|
||||
try {
|
||||
const snapshot = await firestore()
|
||||
.collection("Events")
|
||||
.where("id", "==", eventData.id)
|
||||
.get();
|
||||
for (const eventData of eventDataArray) {
|
||||
console.log("Processing EventData: ", eventData);
|
||||
|
||||
if (snapshot.empty) {
|
||||
await firestore()
|
||||
const snapshot = await firestore()
|
||||
.collection("Events")
|
||||
.add({...eventData, creatorId: currentUser?.uid})
|
||||
} else {
|
||||
console.log("ENTER HERE")
|
||||
const docId = snapshot.docs[0].id;
|
||||
await firestore()
|
||||
.collection("Events")
|
||||
.doc(docId)
|
||||
.update({...eventData, creatorId: currentUser?.uid});
|
||||
.where("id", "==", eventData.id)
|
||||
.get();
|
||||
|
||||
if (snapshot.empty) {
|
||||
await firestore()
|
||||
.collection("Events")
|
||||
.add({...eventData, creatorId: currentUser?.uid});
|
||||
} else {
|
||||
console.log("Event already exists, updating...");
|
||||
const docId = snapshot.docs[0].id;
|
||||
await firestore()
|
||||
.collection("Events")
|
||||
.doc(docId)
|
||||
.set({...eventData, creatorId: currentUser?.uid}, {merge: true});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
console.error("Error creating/updating events: ", e);
|
||||
}
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClients.invalidateQueries("events")
|
||||
queryClient.invalidateQueries("events");
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -2,43 +2,69 @@ import {useQuery} from "react-query";
|
||||
import firestore from "@react-native-firebase/firestore";
|
||||
import {useAuthContext} from "@/contexts/AuthContext";
|
||||
import {colorMap} from "@/contexts/SettingsContext";
|
||||
import {useAtomValue} from "jotai";
|
||||
import {isFamilyViewAtom} from "@/components/pages/calendar/atoms";
|
||||
|
||||
export const useGetEvents = (isFamilyView: boolean) => {
|
||||
const { user, profileData } = useAuthContext();
|
||||
export const useGetEvents = () => {
|
||||
const {user, profileData} = useAuthContext();
|
||||
const isFamilyView = useAtomValue(isFamilyViewAtom)
|
||||
|
||||
return useQuery({
|
||||
queryKey: ["events", user?.uid, isFamilyView],
|
||||
queryFn: async () => {
|
||||
const eventsQuery = firestore()
|
||||
.collection("Events")
|
||||
.where("creatorId", "==", user?.uid);
|
||||
const db = firestore();
|
||||
|
||||
const userId = user?.uid; // Assuming user is defined
|
||||
const familyId = profileData?.familyId; // Assuming profileData is defined
|
||||
let allEvents = [];
|
||||
|
||||
if (isFamilyView) {
|
||||
eventsQuery.where("familyID", "==", profileData?.familyId);
|
||||
const familyQuery = db.collection("Events").where("familyID", "==", familyId);
|
||||
const familySnapshot = await familyQuery.get();
|
||||
const familyEvents = familySnapshot.docs.map(doc => doc.data());
|
||||
|
||||
allEvents = [...familyEvents];
|
||||
} else {
|
||||
const creatorQuery = db.collection("Events").where("creatorId", "==", userId);
|
||||
const attendeeQuery = db.collection("Events").where("attendees", "array-contains", userId);
|
||||
|
||||
const [creatorSnapshot, attendeeSnapshot] = await Promise.all([
|
||||
creatorQuery.get(),
|
||||
attendeeQuery.get(),
|
||||
]);
|
||||
|
||||
const creatorEvents = creatorSnapshot.docs.map(doc => doc.data());
|
||||
const attendeeEvents = attendeeSnapshot.docs.map(doc => doc.data());
|
||||
|
||||
allEvents = [...creatorEvents, ...attendeeEvents];
|
||||
}
|
||||
|
||||
const snapshot = await eventsQuery.get();
|
||||
allEvents = allEvents.filter((event, index, self) =>
|
||||
index === self.findIndex(e => e.id === event.id)
|
||||
);
|
||||
|
||||
return await Promise.all(snapshot.docs.map(async (doc) => {
|
||||
const data = doc.data();
|
||||
return await Promise.all(
|
||||
allEvents.map(async (event) => {
|
||||
const profileSnapshot = await db
|
||||
.collection("Profiles")
|
||||
.doc(event.creatorId)
|
||||
.get();
|
||||
|
||||
const profileSnapshot = await firestore()
|
||||
.collection("Profiles")
|
||||
.doc(data.creatorId)
|
||||
.get();
|
||||
const profileData = profileSnapshot.data();
|
||||
const eventColor = profileData?.eventColor || colorMap.pink; // Default color if not found
|
||||
|
||||
const profileData = profileSnapshot.data();
|
||||
const eventColor: string = profileData?.eventColor || colorMap.pink // Default color if not found
|
||||
|
||||
return {
|
||||
id: doc.id,
|
||||
title: data.title,
|
||||
start: new Date(data.startDate.seconds * 1000),
|
||||
end: new Date(data.endDate.seconds * 1000),
|
||||
hideHours: data.allDay,
|
||||
eventColor: eventColor,
|
||||
};
|
||||
}));
|
||||
return {
|
||||
id: event.id,
|
||||
title: event.title,
|
||||
start: new Date(event.startDate.seconds * 1000),
|
||||
end: new Date(event.endDate.seconds * 1000),
|
||||
hideHours: event.allDay,
|
||||
eventColor: eventColor,
|
||||
};
|
||||
})
|
||||
);
|
||||
},
|
||||
staleTime: Infinity,
|
||||
cacheTime: Infinity
|
||||
});
|
||||
};
|
||||
|
||||
@ -3,6 +3,7 @@ import auth from "@react-native-firebase/auth";
|
||||
import { ProfileType } from "@/contexts/AuthContext";
|
||||
import { useSetUserData } from "./useSetUserData";
|
||||
import {uuidv4} from "@firebase/util";
|
||||
import * as Localization from "expo-localization";
|
||||
|
||||
export const useSignUp = () => {
|
||||
const { mutateAsync: setUserData } = useSetUserData();
|
||||
@ -30,6 +31,7 @@ export const useSignUp = () => {
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
familyId: uuidv4(),
|
||||
timeZone: Localization.getCalendars()[0].timeZone,
|
||||
},
|
||||
customUser: res.user,
|
||||
});
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
import {useAuthContext} from "@/contexts/AuthContext";
|
||||
import {useMutation, useQueryClient} from "react-query";
|
||||
import firestore from "@react-native-firebase/firestore";
|
||||
import {EventData} from "@/hooks/firebase/types/eventData";
|
||||
|
||||
export const useUpdateEvent = () => {
|
||||
const {user: currentUser} = useAuthContext()
|
||||
const queryClients = useQueryClient()
|
||||
|
||||
return useMutation({
|
||||
@ -13,7 +11,7 @@ export const useUpdateEvent = () => {
|
||||
try {
|
||||
await firestore()
|
||||
.collection("Events")
|
||||
.doc(eventData.id)
|
||||
.doc(`${eventData.id}`)
|
||||
.update(eventData);
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
||||
@ -1,33 +1,46 @@
|
||||
import {useAuthContext} from "@/contexts/AuthContext";
|
||||
import {useMutation, useQueryClient} from "react-query";
|
||||
import firestore from "@react-native-firebase/firestore";
|
||||
import {UserProfile} from "@/hooks/firebase/types/profileTypes";
|
||||
import {FirebaseAuthTypes} from "@react-native-firebase/auth";
|
||||
import {useMutation, useQueryClient} from "react-query";
|
||||
import {useAuthContext} from "@/contexts/AuthContext";
|
||||
import {UserProfile} from "@/hooks/firebase/types/profileTypes";
|
||||
|
||||
export const useUpdateUserData = () => {
|
||||
const {user: currentUser, refreshProfileData} = useAuthContext();
|
||||
const queryClient = useQueryClient()
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["updateUserData"],
|
||||
mutationFn: async ({newUserData, customUser}: {newUserData: Partial<UserProfile>, customUser?: FirebaseAuthTypes.User }) => {
|
||||
console.log("Mutation function called with data:", { newUserData, customUser });
|
||||
mutationFn: async ({
|
||||
newUserData,
|
||||
customUser,
|
||||
}: {
|
||||
newUserData: Partial<UserProfile>;
|
||||
customUser?: FirebaseAuthTypes.User;
|
||||
}) => {
|
||||
console.log("Mutation function called with data:", {newUserData, customUser});
|
||||
|
||||
const user = currentUser ?? customUser;
|
||||
|
||||
if (user) {
|
||||
console.log("Updating user data for UID:", user.uid);
|
||||
|
||||
try {
|
||||
console.log("New user data:", newUserData);
|
||||
const updatedUserData = Object.fromEntries(
|
||||
Object.entries(newUserData).map(([key, value]) =>
|
||||
[key, value === null ? firestore.FieldValue.delete() : value]
|
||||
)
|
||||
);
|
||||
|
||||
console.log("Updated user data with deletions:", updatedUserData);
|
||||
|
||||
await firestore()
|
||||
.collection("Profiles")
|
||||
.doc(user.uid)
|
||||
.update(newUserData);
|
||||
.update(updatedUserData);
|
||||
|
||||
console.log("User data updated successfully, fetching updated profile...");
|
||||
|
||||
await refreshProfileData()
|
||||
await refreshProfileData();
|
||||
|
||||
console.log("Profile data updated in context.");
|
||||
} catch (e) {
|
||||
@ -38,7 +51,7 @@ export const useUpdateUserData = () => {
|
||||
}
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries("events")
|
||||
}
|
||||
queryClient.invalidateQueries("events");
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
||||
32
hooks/useFetchAndSaveAppleEvents.ts
Normal file
32
hooks/useFetchAndSaveAppleEvents.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import {useMutation} from "react-query";
|
||||
import {useAuthContext} from "@/contexts/AuthContext";
|
||||
import {useCreateEventsFromProvider} from "@/hooks/firebase/useCreateEvent";
|
||||
import {fetchiPhoneCalendarEvents} from "@/calendar-integration/apple-calendar-utils";
|
||||
|
||||
export const useFetchAndSaveAppleEvents = () => {
|
||||
const {profileData} = useAuthContext();
|
||||
const {mutateAsync: createEventsFromProvider} = useCreateEventsFromProvider();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["fetchAndSaveAppleEvents"],
|
||||
mutationFn: async (token?: string, email?: string) => {
|
||||
const timeMin = new Date(new Date().setFullYear(new Date().getFullYear() - 1));
|
||||
const timeMax = new Date(new Date().setFullYear(new Date().getFullYear() + 5));
|
||||
try {
|
||||
const response = await fetchiPhoneCalendarEvents(
|
||||
profileData?.familyId!,
|
||||
email,
|
||||
timeMin,
|
||||
timeMax
|
||||
);
|
||||
|
||||
console.log(response);
|
||||
const items = response ?? [];
|
||||
await createEventsFromProvider(items);
|
||||
} catch (error) {
|
||||
console.error("Error fetching and saving Apple Calendar events: ", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
45
hooks/useFetchAndSaveGoogleEvents.ts
Normal file
45
hooks/useFetchAndSaveGoogleEvents.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import {useMutation} from "react-query";
|
||||
import {fetchGoogleCalendarEvents} from "@/calendar-integration/google-calendar-utils";
|
||||
import {useAuthContext} from "@/contexts/AuthContext";
|
||||
import {useCreateEventsFromProvider} from "@/hooks/firebase/useCreateEvent";
|
||||
|
||||
export const useFetchAndSaveGoogleEvents = () => {
|
||||
const {profileData} = useAuthContext();
|
||||
const {mutateAsync: createEventsFromProvider} = useCreateEventsFromProvider();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["fetchAndSaveGoogleEvents"],
|
||||
mutationFn: async (token?: string, email?: string) => {
|
||||
console.log("Fetching Google Calendar events...");
|
||||
const timeMin = new Date(new Date().setFullYear(new Date().getFullYear() - 1));
|
||||
const timeMax = new Date(new Date().setFullYear(new Date().getFullYear() + 5));
|
||||
|
||||
console.log("Token: ", token ?? profileData?.googleToken);
|
||||
|
||||
try {
|
||||
const response = await fetchGoogleCalendarEvents(
|
||||
token ?? profileData?.googleToken,
|
||||
email ?? profileData?.googleMail,
|
||||
profileData?.familyId,
|
||||
timeMin.toISOString().slice(0, -5) + "Z",
|
||||
timeMax.toISOString().slice(0, -5) + "Z"
|
||||
);
|
||||
|
||||
console.log("Google Calendar events fetched:", response);
|
||||
|
||||
const items = response?.map((item) => {
|
||||
if (item.allDay) {
|
||||
item.startDate = new Date(new Date(item.startDate).setHours(0, 0, 0, 0));
|
||||
item.endDate = item.startDate;
|
||||
}
|
||||
return item;
|
||||
}) || [];
|
||||
|
||||
await createEventsFromProvider(items);
|
||||
} catch (error) {
|
||||
console.error("Error fetching Google Calendar events:", error);
|
||||
throw error; // Ensure errors are propagated to the mutation
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
36
hooks/useFetchAndSaveOutlookEvents.ts
Normal file
36
hooks/useFetchAndSaveOutlookEvents.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { useMutation } from "react-query";
|
||||
import { useAuthContext } from "@/contexts/AuthContext";
|
||||
import { useCreateEventsFromProvider } from "@/hooks/firebase/useCreateEvent";
|
||||
import { fetchMicrosoftCalendarEvents } from "@/calendar-integration/microsoft-calendar-utils";
|
||||
|
||||
export const useFetchAndSaveOutlookEvents = () => {
|
||||
const { profileData } = useAuthContext();
|
||||
const { mutateAsync: createEventsFromProvider } = useCreateEventsFromProvider();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["fetchAndSaveOutlookEvents"],
|
||||
mutationFn: async (token?: string, email?: string) => {
|
||||
const timeMin = new Date(new Date().setFullYear(new Date().getFullYear() - 1));
|
||||
const timeMax = new Date(new Date().setFullYear(new Date().getFullYear() + 3));
|
||||
|
||||
console.log("Token: ", token ?? profileData?.microsoftToken);
|
||||
|
||||
try {
|
||||
const response = await fetchMicrosoftCalendarEvents(
|
||||
token ?? profileData?.microsoftToken,
|
||||
email ?? profileData?.outlookMail,
|
||||
profileData?.familyId,
|
||||
timeMin.toISOString().slice(0, -5) + "Z",
|
||||
timeMax.toISOString().slice(0, -5) + "Z"
|
||||
);
|
||||
|
||||
console.log(response);
|
||||
const items = response ?? [];
|
||||
await createEventsFromProvider(items);
|
||||
} catch (error) {
|
||||
console.error("Error fetching and saving Outlook events: ", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -14,6 +14,7 @@ install! 'cocoapods',
|
||||
prepare_react_native_project!
|
||||
|
||||
target 'cally' do
|
||||
pod 'IQKeyboardManagerSwift', :git => 'https://github.com/douglasjunior/IQKeyboardManager.git', :branch => 'react-native-keyboard-manager'
|
||||
use_expo_modules!
|
||||
config = use_native_modules!
|
||||
|
||||
|
||||
@ -1190,8 +1190,12 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- ExpoAppleAuthentication (6.4.2):
|
||||
- ExpoModulesCore
|
||||
- ExpoAsset (10.0.10):
|
||||
- ExpoModulesCore
|
||||
- ExpoCalendar (13.0.5):
|
||||
- ExpoModulesCore
|
||||
- ExpoCamera (15.0.16):
|
||||
- ExpoModulesCore
|
||||
- ZXingObjC/OneD
|
||||
@ -1210,6 +1214,8 @@ PODS:
|
||||
- ExpoModulesCore
|
||||
- ExpoKeepAwake (13.0.2):
|
||||
- ExpoModulesCore
|
||||
- ExpoLocalization (15.0.3):
|
||||
- ExpoModulesCore
|
||||
- ExpoModulesCore (1.12.24):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
@ -1489,6 +1495,7 @@ PODS:
|
||||
- hermes-engine (0.74.3):
|
||||
- hermes-engine/Pre-built (= 0.74.3)
|
||||
- hermes-engine/Pre-built (0.74.3)
|
||||
- IQKeyboardManagerSwift (6.5.16)
|
||||
- leveldb-library (1.22.5)
|
||||
- nanopb (2.30909.1):
|
||||
- nanopb/decode (= 2.30909.1)
|
||||
@ -2682,6 +2689,10 @@ PODS:
|
||||
- React-logger (= 0.74.3)
|
||||
- React-perflogger (= 0.74.3)
|
||||
- React-utils (= 0.74.3)
|
||||
- ReactNativeKeyboardManager (6.5.16-0):
|
||||
- IQKeyboardManagerSwift (= 6.5.16)
|
||||
- React-Core
|
||||
- React-RCTText
|
||||
- ReactNativeUiLib (4.2.0):
|
||||
- React
|
||||
- RecaptchaInterop (100.0.0)
|
||||
@ -2802,7 +2813,9 @@ DEPENDENCIES:
|
||||
- expo-dev-launcher (from `../node_modules/expo-dev-launcher`)
|
||||
- expo-dev-menu (from `../node_modules/expo-dev-menu`)
|
||||
- expo-dev-menu-interface (from `../node_modules/expo-dev-menu-interface/ios`)
|
||||
- ExpoAppleAuthentication (from `../node_modules/expo-apple-authentication/ios`)
|
||||
- ExpoAsset (from `../node_modules/expo-asset/ios`)
|
||||
- ExpoCalendar (from `../node_modules/expo-calendar/ios`)
|
||||
- ExpoCamera (from `../node_modules/expo-camera/ios`)
|
||||
- ExpoCrypto (from `../node_modules/expo-crypto/ios`)
|
||||
- ExpoDevice (from `../node_modules/expo-device/ios`)
|
||||
@ -2811,6 +2824,7 @@ DEPENDENCIES:
|
||||
- ExpoHead (from `../node_modules/expo-router/ios`)
|
||||
- ExpoImagePicker (from `../node_modules/expo-image-picker/ios`)
|
||||
- ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`)
|
||||
- ExpoLocalization (from `../node_modules/expo-localization/ios`)
|
||||
- ExpoModulesCore (from `../node_modules/expo-modules-core`)
|
||||
- ExpoSystemUI (from `../node_modules/expo-system-ui/ios`)
|
||||
- ExpoWebBrowser (from `../node_modules/expo-web-browser/ios`)
|
||||
@ -2822,6 +2836,7 @@ DEPENDENCIES:
|
||||
- fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`)
|
||||
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
|
||||
- hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
|
||||
- IQKeyboardManagerSwift (from `https://github.com/douglasjunior/IQKeyboardManager.git`, branch `react-native-keyboard-manager`)
|
||||
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
|
||||
- RCT-Folly/Fabric (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
|
||||
- RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`)
|
||||
@ -2874,6 +2889,7 @@ DEPENDENCIES:
|
||||
- React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`)
|
||||
- React-utils (from `../node_modules/react-native/ReactCommon/react/utils`)
|
||||
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
|
||||
- ReactNativeKeyboardManager (from `../node_modules/react-native-keyboard-manager`)
|
||||
- ReactNativeUiLib (from `../node_modules/react-native-ui-lib`)
|
||||
- "RNDateTimePicker (from `../node_modules/@react-native-community/datetimepicker`)"
|
||||
- "RNFBApp (from `../node_modules/@react-native-firebase/app`)"
|
||||
@ -2956,8 +2972,12 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/expo-dev-menu"
|
||||
expo-dev-menu-interface:
|
||||
:path: "../node_modules/expo-dev-menu-interface/ios"
|
||||
ExpoAppleAuthentication:
|
||||
:path: "../node_modules/expo-apple-authentication/ios"
|
||||
ExpoAsset:
|
||||
:path: "../node_modules/expo-asset/ios"
|
||||
ExpoCalendar:
|
||||
:path: "../node_modules/expo-calendar/ios"
|
||||
ExpoCamera:
|
||||
:path: "../node_modules/expo-camera/ios"
|
||||
ExpoCrypto:
|
||||
@ -2974,6 +2994,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/expo-image-picker/ios"
|
||||
ExpoKeepAwake:
|
||||
:path: "../node_modules/expo-keep-awake/ios"
|
||||
ExpoLocalization:
|
||||
:path: "../node_modules/expo-localization/ios"
|
||||
ExpoModulesCore:
|
||||
:path: "../node_modules/expo-modules-core"
|
||||
ExpoSystemUI:
|
||||
@ -2997,6 +3019,9 @@ EXTERNAL SOURCES:
|
||||
hermes-engine:
|
||||
:podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec"
|
||||
:tag: hermes-2024-06-28-RNv0.74.3-7bda0c267e76d11b68a585f84cfdd65000babf85
|
||||
IQKeyboardManagerSwift:
|
||||
:branch: react-native-keyboard-manager
|
||||
:git: https://github.com/douglasjunior/IQKeyboardManager.git
|
||||
RCT-Folly:
|
||||
:podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec"
|
||||
RCTDeprecation:
|
||||
@ -3097,6 +3122,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native/ReactCommon/react/utils"
|
||||
ReactCommon:
|
||||
:path: "../node_modules/react-native/ReactCommon"
|
||||
ReactNativeKeyboardManager:
|
||||
:path: "../node_modules/react-native-keyboard-manager"
|
||||
ReactNativeUiLib:
|
||||
:path: "../node_modules/react-native-ui-lib"
|
||||
RNDateTimePicker:
|
||||
@ -3122,6 +3149,11 @@ EXTERNAL SOURCES:
|
||||
Yoga:
|
||||
:path: "../node_modules/react-native/ReactCommon/yoga"
|
||||
|
||||
CHECKOUT OPTIONS:
|
||||
IQKeyboardManagerSwift:
|
||||
:commit: 718cbed77cdd5ecd8b779afe543ba5b2df45b40a
|
||||
:git: https://github.com/douglasjunior/IQKeyboardManager.git
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
abseil: d121da9ef7e2ff4cab7666e76c5a3e0915ae08c3
|
||||
AppAuth: 501c04eda8a8d11f179dbe8637b7a91bb7e5d2fa
|
||||
@ -3142,7 +3174,9 @@ SPEC CHECKSUMS:
|
||||
expo-dev-launcher: fe4f2c0a0aa627449eeaec5f9f11e04090f97c40
|
||||
expo-dev-menu: 5b14897ecce3a8cf9e9cf9109344c2c192a3766a
|
||||
expo-dev-menu-interface: be32c09f1e03833050f0ee290dcc86b3ad0e73e4
|
||||
ExpoAppleAuthentication: 265219fa0ba1110872079f55f56686b9737b0065
|
||||
ExpoAsset: 323700f291684f110fb55f0d4022a3362ea9f875
|
||||
ExpoCalendar: 135beb39ea3795f854a4ea287a49f74c9203ce51
|
||||
ExpoCamera: 929be541d1c1319fcf32f9f5d9df8b97804346b5
|
||||
ExpoCrypto: 156078f266bf28f80ecf5e2a9c3a0d6ffce07a1c
|
||||
ExpoDevice: fc94f0e42ecdfd897e7590f2874fc64dfa7e9b1c
|
||||
@ -3151,6 +3185,7 @@ SPEC CHECKSUMS:
|
||||
ExpoHead: fcb28a68ed4ba28f177394d2dfb8a0a8824cd103
|
||||
ExpoImagePicker: 12a420923383ae38dccb069847218f27a3b87816
|
||||
ExpoKeepAwake: 3b8815d9dd1d419ee474df004021c69fdd316d08
|
||||
ExpoLocalization: f04eeec2e35bed01ab61c72ee1768ec04d093d01
|
||||
ExpoModulesCore: db3e31e694684f08223d713e89f7648c6d3e04d0
|
||||
ExpoSystemUI: d4f065a016cae6721b324eb659cdee4d4cf0cb26
|
||||
ExpoWebBrowser: 7595ccac6938eb65b076385fd23d035db9ecdc8e
|
||||
@ -3183,6 +3218,7 @@ SPEC CHECKSUMS:
|
||||
gRPC-Core: eee4be35df218649fe66d721a05a7f27a28f069b
|
||||
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
|
||||
hermes-engine: 1f547997900dd0752dc0cc0ae6dd16173c49e09b
|
||||
IQKeyboardManagerSwift: 90ba81812fbbd6694924a95a271fa3affdf04a14
|
||||
leveldb-library: e8eadf9008a61f9e1dde3978c086d2b6d9b9dc28
|
||||
nanopb: d4d75c12cd1316f4a64e3c6963f879ecd4b5e0d5
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
@ -3238,6 +3274,7 @@ SPEC CHECKSUMS:
|
||||
React-runtimescheduler: e4ad653e1d2f5ff40ba047446cacde009694f0ed
|
||||
React-utils: 6f7ac39d9a0de447d4334bb25d144a28c0c5d8c9
|
||||
ReactCommon: 4a09c7d8a06e93c1e2e988a3b9f3db3d2449f2fc
|
||||
ReactNativeKeyboardManager: 704d89bde3cb1e0f432bc273a44eec96eab9d90f
|
||||
ReactNativeUiLib: deb877cd9b36cf5cad3c72b226bb330060681351
|
||||
RecaptchaInterop: 7d1a4a01a6b2cb1610a47ef3f85f0c411434cb21
|
||||
RNDateTimePicker: 40ffda97d071a98a10fdca4fa97e3977102ccd14
|
||||
@ -3255,6 +3292,6 @@ SPEC CHECKSUMS:
|
||||
Yoga: bd92064a0d558be92786820514d74fc4dddd1233
|
||||
ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5
|
||||
|
||||
PODFILE CHECKSUM: 50f618790da7cbbfd5c5e988b7f9370bd45d34a6
|
||||
PODFILE CHECKSUM: ae388457578eb44dbbdba1451a584b59f3bc21dd
|
||||
|
||||
COCOAPODS: 1.15.2
|
||||
|
||||
7
ios/ReactNativeKeyboardManager.swift
Normal file
7
ios/ReactNativeKeyboardManager.swift
Normal file
@ -0,0 +1,7 @@
|
||||
//
|
||||
// ReactNativeKeyboardManager.swift
|
||||
// cally
|
||||
//
|
||||
// Created by Milan Paunovic on 20.10.24..
|
||||
//
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
103D20271F044483964A389F /* cally-Bridging-Header.h */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.c.h; name = "cally-Bridging-Header.h"; path = "cally/cally-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
13B07F961A680F5B00A75B9A /* cally.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = cally.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
13B07F961A680F5B00A75B9A /* Cally.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Cally.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = cally/AppDelegate.h; sourceTree = "<group>"; };
|
||||
13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = cally/AppDelegate.mm; sourceTree = "<group>"; };
|
||||
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = cally/Images.xcassets; sourceTree = "<group>"; };
|
||||
@ -35,6 +35,7 @@
|
||||
BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; };
|
||||
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
|
||||
F20F68FCCB33056D70B2396B /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = cally/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
F3A90F152CC474F700DDA353 /* ReactNativeKeyboardManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactNativeKeyboardManager.swift; sourceTree = "<group>"; };
|
||||
F56C9EADA6FA4AEAA71245EB /* GoogleService-Info.plist */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "cally/GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||
FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-cally/ExpoModulesProvider.swift"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
@ -88,6 +89,7 @@
|
||||
83CBB9F61A601CBA00E9B192 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F3A90F152CC474F700DDA353 /* ReactNativeKeyboardManager.swift */,
|
||||
13B07FAE1A68108700A75B9A /* cally */,
|
||||
832341AE1AAA6A7D00B99B32 /* Libraries */,
|
||||
83CBBA001A601CBA00E9B192 /* Products */,
|
||||
@ -103,7 +105,7 @@
|
||||
83CBBA001A601CBA00E9B192 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
13B07F961A680F5B00A75B9A /* cally.app */,
|
||||
13B07F961A680F5B00A75B9A /* Cally.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@ -166,7 +168,7 @@
|
||||
);
|
||||
name = cally;
|
||||
productName = cally;
|
||||
productReference = 13B07F961A680F5B00A75B9A /* cally.app */;
|
||||
productReference = 13B07F961A680F5B00A75B9A /* Cally.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
@ -178,7 +180,9 @@
|
||||
LastUpgradeCheck = 1130;
|
||||
TargetAttributes = {
|
||||
13B07F861A680F5B00A75B9A = {
|
||||
DevelopmentTeam = MV9C3PHV87;
|
||||
LastSwiftMigration = 1250;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
@ -302,6 +306,7 @@
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/EXUpdates/EXUpdates.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ExpoDevice/ExpoDevice_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ExpoLocalization/ExpoLocalization_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ExpoSystemUI/ExpoSystemUI_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/FirebaseAuth/FirebaseAuth_Privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle",
|
||||
@ -314,6 +319,7 @@
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher/GTMSessionFetcher_Core_Privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/PromisesSwift/Promises_Privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/ReachabilitySwift.bundle",
|
||||
@ -337,6 +343,7 @@
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXUpdates.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoDevice_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoFileSystem_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoLocalization_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoSystemUI_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseAuth_Privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle",
|
||||
@ -349,6 +356,7 @@
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GTMSessionFetcher_Core_Privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/IQKeyboardManagerSwift.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}/ReachabilitySwift.bundle",
|
||||
@ -421,7 +429,10 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = cally/cally.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = MV9C3PHV87;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
@ -441,7 +452,7 @@
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.cally.app;
|
||||
PRODUCT_NAME = "CallyFamilyPlanner";
|
||||
PRODUCT_NAME = "Cally";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "cally/cally-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@ -457,7 +468,10 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = cally/cally.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = MV9C3PHV87;
|
||||
INFOPLIST_FILE = cally/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -472,7 +486,7 @@
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.cally.app;
|
||||
PRODUCT_NAME = "CallyFamilyPlanner";
|
||||
PRODUCT_NAME = "Cally";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "cally/cally-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||
BuildableName = "cally.app"
|
||||
BuildableName = "Cally.app"
|
||||
BlueprintName = "cally"
|
||||
ReferencedContainer = "container:cally.xcodeproj">
|
||||
</BuildableReference>
|
||||
@ -55,7 +55,7 @@
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||
BuildableName = "cally.app"
|
||||
BuildableName = "Cally.app"
|
||||
BlueprintName = "cally"
|
||||
ReferencedContainer = "container:cally.xcodeproj">
|
||||
</BuildableReference>
|
||||
@ -72,7 +72,7 @@
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||
BuildableName = "cally.app"
|
||||
BuildableName = "Cally.app"
|
||||
BlueprintName = "cally"
|
||||
ReferencedContainer = "container:cally.xcodeproj">
|
||||
</BuildableReference>
|
||||
|
||||
@ -4,10 +4,12 @@
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleAllowMixedLocalizations</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Cally - Family Planner</string>
|
||||
<string>Cally.</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@ -45,7 +47,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>28</string>
|
||||
<string>31</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
@ -55,12 +57,20 @@
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSCalendarsFullAccessUsageDescription</key>
|
||||
<string>The app needs to access your calendar.</string>
|
||||
<key>NSCalendarsUsageDescription</key>
|
||||
<string>The app needs to access your calendar.</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Allow $(PRODUCT_NAME) to access your camera</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Allow $(PRODUCT_NAME) to access your microphone</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>Allow $(PRODUCT_NAME) to access your photos</string>
|
||||
<key>NSRemindersFullAccessUsageDescription</key>
|
||||
<string>Allow $(PRODUCT_NAME) to access your reminders</string>
|
||||
<key>NSRemindersUsageDescription</key>
|
||||
<string>Allow $(PRODUCT_NAME) to access your reminders</string>
|
||||
<key>NSUserActivityTypes</key>
|
||||
<array>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||
@ -95,6 +105,12 @@
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>SplashScreen</string>
|
||||
|
||||
@ -4,5 +4,9 @@
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.applesignin</key>
|
||||
<array>
|
||||
<string>Default</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -44,9 +44,11 @@
|
||||
"debounce": "^2.1.1",
|
||||
"expo": "~51.0.24",
|
||||
"expo-app-loading": "^2.1.1",
|
||||
"expo-apple-authentication": "~6.4.2",
|
||||
"expo-auth-session": "^5.5.2",
|
||||
"expo-barcode-scanner": "~13.0.1",
|
||||
"expo-build-properties": "~0.12.4",
|
||||
"expo-calendar": "~13.0.5",
|
||||
"expo-camera": "~15.0.16",
|
||||
"expo-constants": "~16.0.2",
|
||||
"expo-dev-client": "~4.0.27",
|
||||
@ -54,6 +56,7 @@
|
||||
"expo-font": "~12.0.10",
|
||||
"expo-image-picker": "~15.0.7",
|
||||
"expo-linking": "~6.3.1",
|
||||
"expo-localization": "~15.0.3",
|
||||
"expo-notifications": "~0.28.18",
|
||||
"expo-router": "~3.5.20",
|
||||
"expo-splash-screen": "~0.27.5",
|
||||
@ -73,6 +76,7 @@
|
||||
"react-native-calendars": "^1.1306.0",
|
||||
"react-native-gesture-handler": "~2.16.1",
|
||||
"react-native-gifted-charts": "^1.4.41",
|
||||
"react-native-keyboard-manager": "^6.5.16-0",
|
||||
"react-native-linear-gradient": "^2.8.3",
|
||||
"react-native-onboarding-swiper": "^1.3.0",
|
||||
"react-native-qrcode-svg": "^6.3.2",
|
||||
@ -84,7 +88,9 @@
|
||||
"react-native-toast-message": "^2.2.1",
|
||||
"react-native-ui-lib": "^7.27.0",
|
||||
"react-native-web": "~0.19.10",
|
||||
"react-query": "^3.39.3"
|
||||
"react-query": "^3.39.3",
|
||||
"timezonecomplete": "^5.13.1",
|
||||
"tzdata": "^1.0.42"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
|
||||
42
plugins/withPodfile.js
Normal file
42
plugins/withPodfile.js
Normal file
@ -0,0 +1,42 @@
|
||||
const { withDangerousMod, withPlugins } = require('@expo/config-plugins');
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
async function readFile(path) {
|
||||
return fs.promises.readFile(path, 'utf8');
|
||||
}
|
||||
|
||||
async function saveFile(path, content) {
|
||||
return fs.promises.writeFile(path, content, 'utf8');
|
||||
}
|
||||
|
||||
module.exports = (config) =>
|
||||
withPlugins(config, [
|
||||
(config) => {
|
||||
return withDangerousMod(config, [
|
||||
'iOS',
|
||||
async (config) => {
|
||||
const file = path.join(config.modRequest.platformProjectRoot, 'Podfile');
|
||||
|
||||
/*
|
||||
* You need to remove the line before adding it.
|
||||
* If you don't do this and you run `expo prebuild` in a dirt project
|
||||
* your file will have the same line added twice
|
||||
*/
|
||||
const contents = (await readFile(file)).replace(
|
||||
/pod 'IQKeyboardManagerSwift', :git => 'https:\/\/github.com\/douglasjunior\/IQKeyboardManager.git', :branch => 'react-native-keyboard-manager'\n\n/g,
|
||||
'',
|
||||
);
|
||||
/*
|
||||
* Now re-adds the content
|
||||
*/
|
||||
await saveFile(
|
||||
file,
|
||||
`pod 'IQKeyboardManagerSwift', :git => 'https://github.com/douglasjunior/IQKeyboardManager.git', :branch => 'react-native-keyboard-manager'\n\n${contents}`,
|
||||
);
|
||||
return config;
|
||||
},
|
||||
]);
|
||||
},
|
||||
]);
|
||||
Reference in New Issue
Block a user