Compare commits
71 Commits
dp/calenda
...
matija_cha
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c6ec9eda4 | |||
| 0deac561cc | |||
| a5231e8547 | |||
| d00c89ec6c | |||
| e4eb67fba7 | |||
| 939b525a4b | |||
| 23f03eca2a | |||
| 7effcfe288 | |||
| 43d0d67044 | |||
| e2d18cf7d5 | |||
| dc05090f49 | |||
| 0f75be8d1e | |||
| f662852c68 | |||
| 656622fd93 | |||
| 4b01e18ac0 | |||
| c6db12a9b6 | |||
| 7aa7143d09 | |||
| 7d16da78d0 | |||
| a8717a9a42 | |||
| 55adf88e6a | |||
| a2ff00df70 | |||
| b37000ec4d | |||
| 6962bfc727 | |||
| 341a1bf249 | |||
| 0f12296ac6 | |||
| 515b5738bb | |||
| de854ecfd6 | |||
| 36329dc2f2 | |||
| f4be82587c | |||
| 1e506dd246 | |||
| 9123aab566 | |||
| ed0c654a55 | |||
| 621c7f1f50 | |||
| 5c6233a5fd | |||
| d97387767b | |||
| 4c021b67f6 | |||
| 28ad8f8986 | |||
| fd08cd84fc | |||
| b3e40ad909 | |||
| 6e1e665b93 | |||
| 3d4795c25d | |||
| 41ee6d0f3e | |||
| a8ab69b69f | |||
| 04a6103470 | |||
| 9c6cc16f16 | |||
| cd62837198 | |||
| 86231daba4 | |||
| 22e962a8b2 | |||
| e6758a014f | |||
| 9b94aa8e70 | |||
| a05de1b333 | |||
| 562da49806 | |||
| 7d9d41acfc | |||
| 454d2256db | |||
| f20a7dbdd8 | |||
| e1c03c840d | |||
| ca0b55c494 | |||
| 2c099740a2 | |||
| 1a5283be47 | |||
| fb3859e69b | |||
| 2b55c5db0c | |||
| 9be3e2849e | |||
| b74887cf3c | |||
| 4b07f1f36a | |||
| 5d70cdbeb8 | |||
| 74ac898f32 | |||
| 326b49a5b2 | |||
| a6fda307a1 | |||
| b82a6fcf8f | |||
| 10565ceb39 | |||
| fe6e0365a4 |
2
.github/workflows/ci-cd.yml
vendored
@ -34,7 +34,7 @@ jobs:
|
||||
token: ${{ secrets.EXPO_TOKEN }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Prebuild, Build and Submit
|
||||
run: npm run prebuild-build-submit-ios-cicd
|
||||
|
||||
@ -91,6 +91,9 @@ android {
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0.0"
|
||||
manifestPlaceholders = [
|
||||
appAuthRedirectScheme: "callyplanner"
|
||||
]
|
||||
}
|
||||
signingConfigs {
|
||||
debug {
|
||||
|
||||
@ -14,9 +14,14 @@
|
||||
</intent>
|
||||
</queries>
|
||||
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme">
|
||||
<meta-data android:name="expo.modules.updates.ENABLED" android:value="false"/>
|
||||
<meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" android:value="default"/>
|
||||
<meta-data android:name="com.google.firebase.messaging.default_notification_color" android:resource="@color/notification_icon_color"/>
|
||||
<meta-data android:name="expo.modules.notifications.default_notification_color" android:resource="@color/notification_icon_color"/>
|
||||
<meta-data android:name="expo.modules.updates.ENABLED" android:value="true"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_RUNTIME_VERSION" android:value="@string/expo_runtime_version"/>
|
||||
<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">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 77 KiB |
@ -3,4 +3,5 @@
|
||||
<color name="iconBackground">#ffffff</color>
|
||||
<color name="colorPrimary">#023c69</color>
|
||||
<color name="colorPrimaryDark">#ffffff</color>
|
||||
<color name="notification_icon_color">#ffffff</color>
|
||||
</resources>
|
||||
@ -1,6 +1,7 @@
|
||||
<resources>
|
||||
<string name="app_name">Kali - Family Planner</string>
|
||||
<string name="app_name">Cally - Family Planner</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>
|
||||
<string name="expo_runtime_version">1.0.0</string>
|
||||
</resources>
|
||||
@ -1,4 +1,4 @@
|
||||
rootProject.name = 'Kali - Family Planner'
|
||||
rootProject.name = 'Cally - Family Planner'
|
||||
|
||||
dependencyResolutionManagement {
|
||||
versionCatalogs {
|
||||
|
||||
24
app.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "Kali - Family Planner",
|
||||
"name": "Cally - Family Planner",
|
||||
"slug": "cally",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
@ -16,7 +16,7 @@
|
||||
"supportsTablet": true,
|
||||
"bundleIdentifier": "com.cally.app",
|
||||
"googleServicesFile": "./ios/GoogleService-Info.plist",
|
||||
"buildNumber": "14"
|
||||
"buildNumber": "28"
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
@ -24,7 +24,11 @@
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"package": "com.cally.app",
|
||||
"googleServicesFile": "./android/app/google-services.json"
|
||||
"googleServicesFile": "./android/app/google-services.json",
|
||||
"permissions": [
|
||||
"android.permission.CAMERA",
|
||||
"android.permission.RECORD_AUDIO"
|
||||
]
|
||||
},
|
||||
"web": {
|
||||
"bundler": "metro",
|
||||
@ -51,7 +55,15 @@
|
||||
"microphonePermission": "Allow $(PRODUCT_NAME) to access your microphone",
|
||||
"recordAudioAndroid": true
|
||||
}
|
||||
]
|
||||
],
|
||||
[
|
||||
"expo-notifications",
|
||||
{
|
||||
"color": "#ffffff",
|
||||
"defaultChannel": "default"
|
||||
}
|
||||
],
|
||||
"expo-font"
|
||||
],
|
||||
"experiments": {
|
||||
"typedRoutes": true
|
||||
@ -63,6 +75,10 @@
|
||||
"eas": {
|
||||
"projectId": "bdb8c57b-25bb-4d36-b3b8-5b09c5092f52"
|
||||
}
|
||||
},
|
||||
"runtimeVersion": "1.0.0",
|
||||
"updates": {
|
||||
"url": "https://u.expo.dev/bdb8c57b-25bb-4d36-b3b8-5b09c5092f52"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,6 +16,13 @@ import {
|
||||
MaterialCommunityIcons,
|
||||
Octicons,
|
||||
} from "@expo/vector-icons";
|
||||
import MenuIcon from "@/assets/svgs/MenuIcon";
|
||||
import { router } from "expo-router";
|
||||
import NavGroceryIcon from "@/assets/svgs/NavGroceryIcon";
|
||||
import NavToDosIcon from "@/assets/svgs/NavToDosIcon";
|
||||
import NavBrainDumpIcon from "@/assets/svgs/NavBrainDumpIcon";
|
||||
import NavCalendarIcon from "@/assets/svgs/NavCalendarIcon";
|
||||
import NavSettingsIcon from "@/assets/svgs/NavSettingsIcon";
|
||||
|
||||
export default function TabLayout() {
|
||||
const { mutateAsync: signOut } = useSignOut();
|
||||
@ -30,12 +37,13 @@ export default function TabLayout() {
|
||||
backgroundColor: "#f9f8f7",
|
||||
height: "100%",
|
||||
},
|
||||
drawerIcon: () => <MenuIcon />,
|
||||
}}
|
||||
drawerContent={(props) => {
|
||||
return (
|
||||
<DrawerContentScrollView {...props} style={{ height: "100%" }}>
|
||||
<View centerH centerV margin-30>
|
||||
<Text text50>Welcome to Kali</Text>
|
||||
<Text style={styles.title}>Welcome to Cally</Text>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
@ -49,26 +57,14 @@ export default function TabLayout() {
|
||||
color="rgb(7, 184, 199)"
|
||||
bgColor={"rgb(231, 248, 250)"}
|
||||
pressFunc={() => props.navigation.navigate("calendar")}
|
||||
icon={
|
||||
<Feather
|
||||
name="calendar"
|
||||
size={30}
|
||||
color="rgb(7, 184, 199)"
|
||||
/>
|
||||
}
|
||||
icon={<NavCalendarIcon />}
|
||||
/>
|
||||
<DrawerButton
|
||||
color="#50be0c"
|
||||
title={"Groceries"}
|
||||
bgColor={"#eef9e7"}
|
||||
pressFunc={() => props.navigation.navigate("grocery")}
|
||||
icon={
|
||||
<MaterialCommunityIcons
|
||||
name="food-apple-outline"
|
||||
size={30}
|
||||
color="#50be0c"
|
||||
/>
|
||||
}
|
||||
icon={<NavGroceryIcon />}
|
||||
/>
|
||||
</View>
|
||||
<View style={{ flex: 1 }}>
|
||||
@ -87,19 +83,17 @@ export default function TabLayout() {
|
||||
/>*/}
|
||||
<DrawerButton
|
||||
color="#8005eb"
|
||||
title={"To Dos"}
|
||||
title={"To Do's"}
|
||||
bgColor={"#f3e6fd"}
|
||||
pressFunc={() => props.navigation.navigate("todos")}
|
||||
icon={
|
||||
<AntDesign name="checkcircleo" size={30} color="#8005eb" />
|
||||
}
|
||||
icon={<NavToDosIcon />}
|
||||
/>
|
||||
<DrawerButton
|
||||
color="#e0ca03"
|
||||
title={"Brain Dump"}
|
||||
bgColor={"#fffacb"}
|
||||
pressFunc={() => props.navigation.navigate("brain_dump")}
|
||||
icon={<AntDesign name="book" size={30} color="#e0ca03" />}
|
||||
icon={<NavBrainDumpIcon />}
|
||||
/>
|
||||
{/*<DrawerItem label="Logout" onPress={() => signOut()} />*/}
|
||||
</View>
|
||||
@ -107,6 +101,7 @@ export default function TabLayout() {
|
||||
<Button
|
||||
onPress={() => props.navigation.navigate("settings")}
|
||||
label={"Manage Settings"}
|
||||
labelStyle={styles.label}
|
||||
iconSource={() => (
|
||||
<View
|
||||
backgroundColor="#ededed"
|
||||
@ -117,7 +112,7 @@ export default function TabLayout() {
|
||||
centerV
|
||||
centerH
|
||||
>
|
||||
<Octicons name="gear" size={30} color="#6c645b" />
|
||||
<NavSettingsIcon />
|
||||
</View>
|
||||
)}
|
||||
backgroundColor="white"
|
||||
@ -125,8 +120,8 @@ export default function TabLayout() {
|
||||
paddingV-30
|
||||
marginH-30
|
||||
marginB-10
|
||||
borderRadius={15}
|
||||
style={{ elevation: 1 }}
|
||||
borderRadius={18.55}
|
||||
style={{ elevation: 0 }}
|
||||
/>
|
||||
|
||||
<Button
|
||||
@ -134,13 +129,14 @@ export default function TabLayout() {
|
||||
marginH-30
|
||||
paddingV-15
|
||||
style={{
|
||||
marginTop: "47%",
|
||||
marginTop: "42%",
|
||||
backgroundColor: "transparent",
|
||||
borderWidth: 2,
|
||||
borderWidth: 1.3,
|
||||
borderColor: "#fd1775",
|
||||
}}
|
||||
label="Sign out of Kali"
|
||||
label="Sign out of Cally"
|
||||
color="#fd1775"
|
||||
labelStyle={styles.signOut}
|
||||
onPress={() => signOut()}
|
||||
/>
|
||||
</DrawerContentScrollView>
|
||||
@ -194,9 +190,19 @@ export default function TabLayout() {
|
||||
name="todos"
|
||||
options={{
|
||||
drawerLabel: "To-Do",
|
||||
title: "To-Do",
|
||||
title: "To-Do's",
|
||||
}}
|
||||
/>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
signOut: { fontFamily: "Poppins_500Medium", fontSize: 15 },
|
||||
label: { fontFamily: "Poppins_400Medium", fontSize: 15 },
|
||||
title: {
|
||||
fontSize: 26.13,
|
||||
fontFamily: 'Manrope_600SemiBold',
|
||||
color: "#262627"
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,160 +1,14 @@
|
||||
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 { useGetEvents } from "@/hooks/firebase/useGetEvents";
|
||||
import { useAuthContext } from "@/contexts/AuthContext";
|
||||
import HeaderTemplate from "@/components/shared/HeaderTemplate";
|
||||
import CalendarViewSwitch from "@/components/pages/calendar/CalendarViewSwitch";
|
||||
import {ManuallyAddEventModal} from "@/components/pages/calendar/ManuallyAddEventModal";
|
||||
|
||||
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 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";
|
||||
|
||||
export default function Screen() {
|
||||
const {profileData} = useAuthContext();
|
||||
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();
|
||||
|
||||
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 currentYear = selectedDate.getFullYear();
|
||||
const currentDay = selectedDate.getDate();
|
||||
const newMonthIndex = months.indexOf(month);
|
||||
|
||||
// Update the date with the new month while preserving the day and year
|
||||
const updatedDate = new Date(currentYear, newMonthIndex, currentDay);
|
||||
setSelectedDate(updatedDate);
|
||||
};
|
||||
|
||||
console.log({events});
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, height: "100%", padding: 10 }}>
|
||||
<HeaderTemplate
|
||||
message={"Let's get your week started!"}
|
||||
isWelcome={true}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={{ flex: 1, backgroundColor: "#fff", borderRadius: 30 }}
|
||||
ref={calendarContainerRef}
|
||||
onLayout={onLayout}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 20,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
backgroundColor: "white",
|
||||
marginBottom: 10,
|
||||
}}
|
||||
>
|
||||
<Picker
|
||||
value={months[selectedDate.getMonth()]} // Get the month from the date
|
||||
placeholder={"Select Month"}
|
||||
mode={PickerModes.SINGLE}
|
||||
onChange={(itemValue) => handleMonthChange(itemValue as string)}
|
||||
trailingAccessory={<MaterialIcons name={"keyboard-arrow-down"} />}
|
||||
>
|
||||
{months.map((month) => (
|
||||
<Picker.Item key={month} label={month} value={month} />
|
||||
))}
|
||||
</Picker>
|
||||
|
||||
<View>
|
||||
<SegmentedControl
|
||||
segments={[
|
||||
{label: "D"},
|
||||
{label: "W"},
|
||||
{label: "M"}]}
|
||||
backgroundColor="#ececec"
|
||||
inactiveColor="#919191"
|
||||
activeBackgroundColor="#ea156c"
|
||||
activeColor="white"
|
||||
outlineColor="white"
|
||||
outlineWidth={3}
|
||||
style={{ backgroundColor: "green" }}
|
||||
segmentLabelStyle={styles.segmentslblStyle}
|
||||
onChangeIndex={handleSegmentChange}
|
||||
initialIndex={mode === "day" ? 0 : mode === "week" ? 1 : 2}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{calendarHeight > 0 && (
|
||||
<Calendar
|
||||
bodyContainerStyle={styles.calHeader}
|
||||
mode={mode}
|
||||
events={events ?? []}
|
||||
height={calendarHeight}
|
||||
activeDate={selectedDate}
|
||||
onPressCell={setSelectedNewEndDate}
|
||||
onSwipeEnd={(newDate) => {
|
||||
console.log(newDate);
|
||||
setSelectedDate(newDate);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<CalendarViewSwitch />
|
||||
<AddEventDialog/>
|
||||
<ManuallyAddEventModal key={`${selectedNewEventDate}`} initialDate={selectedNewEventDate} show={!!selectedNewEventDate} close={() => setSelectedNewEndDate(undefined)}/>
|
||||
</View>
|
||||
<SettingsContextProvider>
|
||||
<CalendarProvider>
|
||||
<CalendarPage />
|
||||
</CalendarProvider>
|
||||
</SettingsContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
segmentslblStyle: {
|
||||
fontSize: 14,
|
||||
},
|
||||
calHeader: {
|
||||
borderWidth: 0,
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,15 +1,11 @@
|
||||
import SettingsPage from "@/components/pages/settings/SettingsPage";
|
||||
import { AuthContextProvider } from "@/contexts/AuthContext";
|
||||
import { SettingsContextProvider } from "@/contexts/SettingsContext";
|
||||
import {SettingsContextProvider} from "@/contexts/SettingsContext";
|
||||
import React from "react";
|
||||
import { View } from "react-native-ui-lib";
|
||||
|
||||
export default function Screen() {
|
||||
return (
|
||||
<AuthContextProvider>
|
||||
<SettingsContextProvider>
|
||||
<SettingsPage />
|
||||
</SettingsContextProvider>
|
||||
</AuthContextProvider>
|
||||
);
|
||||
return (
|
||||
<SettingsContextProvider>
|
||||
<SettingsPage/>
|
||||
</SettingsContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
305
app/_layout.tsx
@ -1,58 +1,275 @@
|
||||
import {DarkTheme, DefaultTheme, ThemeProvider} from '@react-navigation/native';
|
||||
import {useFonts} from 'expo-font';
|
||||
import {Stack} from 'expo-router';
|
||||
import * as SplashScreen from 'expo-splash-screen';
|
||||
import {useEffect} from 'react';
|
||||
import 'react-native-reanimated';
|
||||
|
||||
import {useColorScheme} from '@/hooks/useColorScheme';
|
||||
import {AuthContextProvider} from "@/contexts/AuthContext";
|
||||
|
||||
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 firestore from "@react-native-firebase/firestore";
|
||||
import auth from "@react-native-firebase/auth";
|
||||
import {QueryClient, QueryClientProvider} from "react-query";
|
||||
import {Toast} from "react-native-ui-lib";
|
||||
import firestore from "@react-native-firebase/firestore";
|
||||
|
||||
// Prevent the splash screen from auto-hiding before asset loading is complete.
|
||||
SplashScreen.preventAutoHideAsync();
|
||||
|
||||
const queryClient = new QueryClient()
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
if (__DEV__) {
|
||||
functions().useEmulator("localhost", 5001);
|
||||
firestore().useEmulator("localhost", 5471);
|
||||
auth().useEmulator("http://localhost:9099");
|
||||
}
|
||||
|
||||
// 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({
|
||||
SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
|
||||
});
|
||||
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();
|
||||
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, 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;
|
||||
);
|
||||
}
|
||||
}, [loaded]);
|
||||
|
||||
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 (!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>
|
||||
);
|
||||
}
|
||||
|
||||
BIN
assets/fonts/Manrope-VariableFont_wght.ttf
Normal file
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 82 KiB |
BIN
assets/images/child-picture.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
assets/images/child1-picture.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
BIN
assets/images/profile-picture.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 21 KiB |
20
assets/svgs/AddImageIcon.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import * as React from "react"
|
||||
import Svg, { Path, SvgProps } from "react-native-svg"
|
||||
const AddImageIcon: React.FC<SvgProps> = (props) => (
|
||||
<Svg
|
||||
width={props.width || 28}
|
||||
height={props.width || 28}
|
||||
viewBox="0 0 28 28"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
stroke={props.color || "#FD1775"}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.936}
|
||||
d="M16.833 20.433 14.34 17.96c-1.146-1.137-1.719-1.705-2.379-1.918a2.903 2.903 0 0 0-1.785 0c-.66.213-1.233.781-2.379 1.918L1.99 23.808m14.843-3.375.496-.492c1.17-1.16 1.755-1.74 2.426-1.952a2.903 2.903 0 0 1 1.811.02c.666.228 1.238.821 2.381 2.008l1.214 1.232m-8.328-.816 5.743 5.844M1.99 23.807c.044.379.121.676.255.939.279.546.723.99 1.27 1.269.62.316 1.434.316 3.06.316h13.94c.901 0 1.553 0 2.061-.054M1.99 23.807c-.061-.515-.061-1.183-.061-2.122V7.745c0-1.626 0-2.439.316-3.06.279-.547.723-.99 1.27-1.27.62-.316 1.434-.316 3.06-.316h5.518m10.483 23.178c.408-.043.723-.121 1-.262.546-.279.99-.723 1.268-1.27.317-.62.317-1.434.317-3.06v-5.518m-2.904-5.808V6.003m0 0V1.647m0 4.356h4.356m-4.356 0H17.9"
|
||||
/>
|
||||
</Svg>
|
||||
)
|
||||
export default AddImageIcon
|
||||
19
assets/svgs/AddPersonIcon.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import * as React from "react"
|
||||
import Svg, { SvgProps, Path } from "react-native-svg"
|
||||
const AddPersonIcon = (props: SvgProps) => (
|
||||
<Svg
|
||||
width={props. width || 23}
|
||||
height={props. width || 23}
|
||||
viewBox="0 0 23 23"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
fill={props.color || "#8A8A8A"}
|
||||
fillRule="evenodd"
|
||||
d="M22.089 19.5a1 1 0 0 1-1 .998h-1v1.05a1 1 0 0 1-2 0v-1.05h-1a1 1 0 0 1-1-.998 1 1 0 0 1 1-.998h1v-.945a1 1 0 0 1 2 0v.945h1a1 1 0 0 1 1 .998Zm-11.377-9.045a7.844 7.844 0 0 0-.654-.033c-.203 0-.402.014-.601.03a2.997 2.997 0 0 1-2.368-2.924 3 3 0 0 1 6 0 2.999 2.999 0 0 1-2.377 2.927Zm-8.605 7.05c-.014.005-.004 0-.018.006V3.53c0-.55.448-.99 1-.99h6.847a4.994 4.994 0 0 0-4.847 4.984c0 1.441.622 2.73 1.602 3.642-2.478 1.147-4.27 3.516-4.584 6.338ZM14.992 6.52a4.955 4.955 0 0 0-4.837-3.98h6.934a1 1 0 0 1 1 .997v8.979a1 1 0 0 0 2 0V2.54a1.997 1.997 0 0 0-2-1.995h-16c-1.105 0-2 .893-2 1.995v15.962c0 1.102.895 1.996 2 1.996h10a1 1 0 0 0 1-.998 1 1 0 0 0-1-.998H4.064s-.006-.065-.006-.106c0-6.084 7.386-6.888 9.24-5.168 1.074.995 2.465-.59 1.387-1.354a7.933 7.933 0 0 0-1.221-.712c1.214-1.115 1.889-2.785 1.528-4.641Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</Svg>
|
||||
)
|
||||
export default AddPersonIcon
|
||||
15
assets/svgs/AppleIcon.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import * as React from "react";
|
||||
import Svg, { Path, SvgProps } from "react-native-svg";
|
||||
|
||||
const AppleIcon: React.FC<SvgProps> = (props) => (
|
||||
<Svg
|
||||
width={30}
|
||||
height={35}
|
||||
viewBox="5 3 49 53"
|
||||
{...props}
|
||||
>
|
||||
<Path d="M44.527 34.75c-1.078 2.395-1.597 3.465-2.984 5.578-1.941 2.953-4.68 6.64-8.063 6.664-3.011.028-3.789-1.965-7.878-1.93-4.086.02-4.938 1.97-7.954 1.938-3.386-.031-5.976-3.352-7.918-6.3-5.43-8.27-6.003-17.966-2.648-23.122 2.375-3.656 6.129-5.805 9.656-5.805 3.594 0 5.852 1.973 8.82 1.973 2.883 0 4.637-1.976 8.794-1.976 3.14 0 6.46 1.71 8.836 4.664-7.766 4.257-6.504 15.347 1.34 18.316ZM31.195 8.469c1.512-1.942 2.66-4.68 2.242-7.469-2.464.168-5.347 1.742-7.03 3.781-1.528 1.86-2.794 4.617-2.302 7.285 2.692.086 5.477-1.52 7.09-3.597Z" />
|
||||
</Svg>
|
||||
);
|
||||
|
||||
export default AppleIcon;
|
||||
21
assets/svgs/BinIcon.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import * as React from "react"
|
||||
import Svg, { Path } from "react-native-svg"
|
||||
|
||||
interface BinIconProps extends React.SVGProps<SVGSVGElement> {}
|
||||
const BinIcon: React.FC<BinIconProps> = (props) => (
|
||||
<Svg
|
||||
width={19}
|
||||
height={21}
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
stroke="#B7B7B7"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.7}
|
||||
d="m15.568 4.122-.834 12.5c-.072 1.095-.11 1.642-.345 2.057-.209.366-.523.66-.901.843-.43.208-.979.208-2.076.208H7.237c-1.097 0-1.646 0-2.076-.208a2.081 2.081 0 0 1-.9-.843c-.237-.415-.274-.962-.347-2.057l-.833-12.5M1 4.122h16.649m-4.162 0-.282-.845c-.273-.819-.41-1.228-.662-1.53a2.08 2.08 0 0 0-.835-.603C11.34 1 10.909 1 10.046 1H8.603c-.863 0-1.295 0-1.662.144a2.081 2.081 0 0 0-.835.602c-.253.303-.39.712-.662 1.53l-.282.846m6.244 4.162v7.284M7.243 8.284v7.284"
|
||||
/>
|
||||
</Svg>
|
||||
)
|
||||
export default BinIcon
|
||||
19
assets/svgs/CalendarIcon.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import * as React from "react"
|
||||
import Svg, { Path, SvgProps } from "react-native-svg"
|
||||
const CalendarIcon: React.FC<SvgProps> = (props) => (
|
||||
<Svg
|
||||
width={props.width || 21}
|
||||
height={props.height || 21}
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
stroke={props.color || "#FD1775"}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.455}
|
||||
d="M1.413 7.359h18.294m-4.065 4.067-10.164-.002m3.388 4.066H5.478m0-14.23v2.033m10.164-2.032v2.032M4.665 19.555h11.79c1.138 0 1.708 0 2.142-.222.383-.194.694-.505.889-.888.221-.435.221-1.004.221-2.142V6.546c0-1.139 0-1.708-.221-2.143a2.032 2.032 0 0 0-.889-.888c-.434-.222-1.004-.222-2.142-.222H4.665c-1.138 0-1.707 0-2.142.222a2.033 2.033 0 0 0-.888.888c-.222.435-.222 1.004-.222 2.143v9.757c0 1.138 0 1.707.222 2.142.195.383.505.694.888.888.435.222 1.004.222 2.142.222Z"
|
||||
/>
|
||||
</Svg>
|
||||
)
|
||||
export default CalendarIcon
|
||||
34
assets/svgs/CameraIcon.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import * as React from "react";
|
||||
import Svg, { Path, SvgProps } from "react-native-svg";
|
||||
|
||||
interface CameraIconProps extends SvgProps {
|
||||
color?: string;
|
||||
}
|
||||
|
||||
const CameraIcon: React.FC<CameraIconProps> = (props) => (
|
||||
<Svg
|
||||
width={23}
|
||||
height={21}
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
stroke={props.color || "#919191"}
|
||||
strokeWidth={1.583}
|
||||
d="M11.5 14.722a3.167 3.167 0 1 0 0-6.333 3.167 3.167 0 0 0 0 6.333Z"
|
||||
/>
|
||||
<Path
|
||||
stroke={props.color || "#919191"}
|
||||
strokeWidth={1.583}
|
||||
d="M9.154 20h4.691c3.295 0 4.942 0 6.126-.776a4.651 4.651 0 0 0 1.294-1.271c.79-1.162.79-2.78.79-6.014 0-3.234 0-4.851-.79-6.013a4.65 4.65 0 0 0-1.294-1.271c-.76-.499-1.713-.677-3.17-.74-.696 0-1.294-.518-1.43-1.188A2.179 2.179 0 0 0 13.223 1H9.775c-1.043 0-1.94.723-2.145 1.727-.137.67-.736 1.187-1.431 1.187-1.457.064-2.41.242-3.17.741a4.65 4.65 0 0 0-1.294 1.27c-.79 1.163-.79 2.78-.79 6.014 0 3.235 0 4.852.79 6.014a4.65 4.65 0 0 0 1.294 1.27C4.213 20 5.86 20 9.154 20Z"
|
||||
/>
|
||||
<Path
|
||||
stroke={props.color || "#919191"}
|
||||
strokeLinecap="round"
|
||||
strokeWidth={1.583}
|
||||
d="M18.889 8.389h-1.056"
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
|
||||
export default CameraIcon;
|
||||
28
assets/svgs/ClockIcon.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import * as React from "react";
|
||||
import Svg, { Path, SvgProps } from "react-native-svg";
|
||||
|
||||
interface ClockIconProps extends SvgProps {}
|
||||
|
||||
const ClockIcon: React.FC<ClockIconProps> = (props) => (
|
||||
<Svg
|
||||
width={22}
|
||||
height={25}
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
stroke="#919191"
|
||||
strokeWidth={1.667}
|
||||
d="M11 23.611c5.523 0 10-4.477 10-10s-4.477-10-10-10-10 4.477-10 10 4.477 10 10 10Z"
|
||||
/>
|
||||
<Path
|
||||
stroke="#919191"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.667}
|
||||
d="M11 9.167v4.444l2.778 2.778M1.556 4.167 6 1.389M20.444 4.167 16 1.389"
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
|
||||
export default ClockIcon;
|
||||
19
assets/svgs/CloseXIcon.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import * as React from "react"
|
||||
import Svg, { Path, SvgProps } from "react-native-svg"
|
||||
const CloseXIcon: React.FC<SvgProps> = (props) => (
|
||||
<Svg
|
||||
width={15}
|
||||
height={15}
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
stroke="#AAA"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.394}
|
||||
d="m1.573 1.543 12.544 12.544M1.573 14.087 14.117 1.543"
|
||||
/>
|
||||
</Svg>
|
||||
)
|
||||
export default CloseXIcon
|
||||
20
assets/svgs/DropModalIcon.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import * as React from "react";
|
||||
import Svg, { Path, SvgProps } from "react-native-svg";
|
||||
|
||||
const DropModalIcon: React.FC<SvgProps> = (props) => (
|
||||
<Svg
|
||||
width={30}
|
||||
height={10}
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
fill="#DADCE0"
|
||||
fillRule="evenodd"
|
||||
d="M16.751 9.342c-.975.5-2.557.5-3.532 0L.731 2.308C-.244 1.808-.244 1 .731.5 1.707 0 3.288 0 4.264.5l10.721 6.13L25.707.5c.975-.5 2.556-.5 3.531 0 .976.5.976 1.309 0 1.808L16.752 9.342Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
|
||||
export default DropModalIcon;
|
||||
21
assets/svgs/FireworksOrangeIcon.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import * as React from "react"
|
||||
import Svg, { G, Path, Defs, ClipPath, SvgProps } from "react-native-svg"
|
||||
|
||||
const FireworksOrangeIcon: React.FC<SvgProps> = (props) => (
|
||||
<Svg
|
||||
width={props.width || 29}
|
||||
height={props.height || 28}
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<G fill={props.color || "#F90"} clipPath="url(#a)">
|
||||
<Path d="M14.573 4.229c.527 0 .885-1.15 1.022-2.31C15.765.468 15.152 0 14.573 0c-.58 0-1.193.467-1.023 1.92.137 1.16.495 2.309 1.023 2.309ZM14.573 22.987c-.528 0-.886 1.15-1.023 2.31-.17 1.452.443 1.92 1.023 1.92.58 0 1.193-.468 1.022-1.92-.137-1.16-.496-2.31-1.022-2.31ZM9.85 5.473c.456-.262.192-1.438-.27-2.51-.578-1.343-1.343-1.44-1.845-1.151-.503.29-.799 1.001.074 2.174.7.935 1.583 1.751 2.04 1.487ZM19.296 21.743c-.456.261-.193 1.438.268 2.51.58 1.343 1.343 1.44 1.846 1.15.502-.289.8-1.001-.074-2.173-.698-.935-1.583-1.751-2.04-1.487ZM4.95 6.845c-1.172-.873-1.884-.576-2.174-.074-.29.503-.192 1.267 1.15 1.846 1.073.461 2.249.724 2.512.269.263-.457-.554-1.343-1.489-2.041ZM25.217 18.6c-1.07-.463-2.247-.724-2.51-.27-.263.458.554 1.343 1.489 2.042 1.172.872 1.883.575 2.172.073.29-.502.193-1.266-1.15-1.845ZM5.193 13.607c0-.526-1.15-.884-2.309-1.021-1.452-.171-1.918.443-1.918 1.021 0 .581.466 1.194 1.918 1.024 1.159-.137 2.31-.496 2.31-1.024ZM26.261 12.585c-1.158.138-2.308.496-2.308 1.023 0 .527 1.15.886 2.308 1.022 1.453.172 1.92-.442 1.92-1.022 0-.58-.467-1.193-1.92-1.023ZM3.927 18.6c-1.343.579-1.44 1.343-1.15 1.845.29.502 1 .8 2.172-.073.935-.699 1.751-1.584 1.488-2.041-.262-.455-1.438-.194-2.51.269ZM25.217 8.617c1.344-.58 1.442-1.343 1.15-1.846-.288-.502-.999-.799-2.171.074-.935.698-1.752 1.584-1.489 2.04.263.456 1.44.194 2.51-.268ZM7.81 23.23c-.874 1.172-.578 1.884-.075 2.174.503.29 1.267.192 1.845-1.15.462-1.073.726-2.25.27-2.511-.457-.264-1.342.552-2.04 1.487ZM21.336 3.986c.874-1.173.578-1.884.074-2.174-.502-.29-1.267-.192-1.846 1.151-.46 1.072-.724 2.248-.268 2.51.456.264 1.342-.552 2.04-1.487ZM13.464 9.362c.398-.106.435-1.04.304-1.938-.164-1.125-.718-1.353-1.153-1.235-.436.114-.803.589-.383 1.645.337.844.838 1.636 1.232 1.528ZM15.682 17.852c-.394.107-.434 1.044-.303 1.941.164 1.125.718 1.353 1.154 1.234.435-.116.802-.59.382-1.644-.336-.844-.837-1.636-1.233-1.53ZM9.14 8.194c-.316.32-.398.91.496 1.617.71.56 1.542.996 1.829.707.29-.29-.144-1.12-.704-1.831-.705-.893-1.3-.811-1.62-.493ZM20.002 19.023c.319-.319.4-.912-.492-1.618-.712-.56-1.54-.994-1.83-.706-.289.29.145 1.12.707 1.831.704.892 1.297.811 1.615.493ZM8.38 12.79c.898.13 1.834.093 1.938-.304.107-.396-.685-.897-1.528-1.233-1.055-.42-1.527-.052-1.645.383-.117.434.11.99 1.234 1.154ZM20.767 14.427c-.898-.13-1.832-.092-1.94.303-.106.396.686.896 1.529 1.232 1.056.42 1.53.053 1.646-.382.118-.436-.11-.99-1.235-1.153ZM8.8 15.95c.842-.336 1.635-.838 1.528-1.235-.105-.393-1.042-.432-1.94-.3-1.124.162-1.352.717-1.234 1.151.116.436.59.803 1.646.385ZM20.346 11.266c-.843.338-1.634.838-1.528 1.233.106.396 1.041.434 1.94.304 1.125-.164 1.352-.72 1.235-1.153-.117-.437-.59-.804-1.647-.384ZM9.649 17.422c-.89.703-.81 1.3-.49 1.617.319.317.912.399 1.616-.493.56-.71.996-1.54.707-1.83-.291-.29-1.12.144-1.833.706ZM19.496 9.796c.892-.705.81-1.3.494-1.617-.319-.32-.914-.4-1.617.493-.563.712-.997 1.542-.707 1.83.289.29 1.118-.146 1.83-.706ZM13.45 17.862c-.394-.106-.896.685-1.232 1.529-.42 1.056-.055 1.53.383 1.645.435.118.99-.11 1.153-1.234.131-.898.091-1.834-.304-1.94ZM16.545 6.18c-.434-.119-.99.11-1.152 1.234-.131.899-.093 1.833.302 1.94.395.107.897-.686 1.233-1.528.42-1.056.055-1.529-.383-1.647ZM12.37 13.607a2.204 2.204 0 1 0 4.407.001 2.204 2.204 0 0 0-4.408 0Z" />
|
||||
</G>
|
||||
<Defs>
|
||||
<ClipPath id="a">
|
||||
<Path fill={props.color || "#fff"} d="M.965 0H28.18v27.216H.965z" />
|
||||
</ClipPath>
|
||||
</Defs>
|
||||
</Svg>
|
||||
)
|
||||
export default FireworksOrangeIcon
|
||||
23
assets/svgs/GiftIcon.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import * as React from "react";
|
||||
import Svg, { Path, SvgProps } from "react-native-svg";
|
||||
const GiftIcon: React.FC<SvgProps> = (props) => (
|
||||
<Svg width={props.width || 14} height={props.height || 14} fill="none" {...props}>
|
||||
<Path
|
||||
fill={props.color || "#46A80A"}
|
||||
d="M6.09 12.581V8.834H1.872v-.937H6.09V4.149H1.873v8.432H6.09Zm.936 0h4.216V4.15H7.026v3.748h4.216v.937H7.026v3.747ZM.936 3.212H12.18v9.838a.469.469 0 0 1-.468.468H1.405a.468.468 0 0 1-.468-.468V3.212Z"
|
||||
/>
|
||||
<Path
|
||||
fill={props.color || "#46A80A"}
|
||||
d="M.468 3.212h12.18c.312 0 .468.157.468.469 0 .312-.156.468-.468.468H.468C.156 4.15 0 3.993 0 3.681c0-.312.156-.469.468-.469Z"
|
||||
/>
|
||||
<Path
|
||||
fill={props.color || "#46A80A"}
|
||||
d="M5.153 3.213a.937.937 0 1 0 0-1.874.937.937 0 0 0 0 1.874Zm0 .936a1.874 1.874 0 1 1 0-3.747 1.874 1.874 0 0 1 0 3.747Z"
|
||||
/>
|
||||
<Path
|
||||
fill={props.color || "#46A80A"}
|
||||
d="M7.964 3.213a.937.937 0 1 0 0-1.874.937.937 0 0 0 0 1.874Zm0 .936a1.874 1.874 0 1 1 0-3.747 1.874 1.874 0 0 1 0 3.747Z"
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
export default GiftIcon;
|
||||
30
assets/svgs/GoogleIcon.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import * as React from "react";
|
||||
import Svg, { Path, SvgProps } from "react-native-svg";
|
||||
|
||||
const GoogleIcon: React.FC<SvgProps> = (props) => (
|
||||
<Svg
|
||||
width={30}
|
||||
height={30}
|
||||
viewBox="0 0 48 48"
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
fill="#FFC107"
|
||||
d="M43.611 20.083H42V20H24v8h11.303c-1.649 4.657-6.08 8-11.303 8-6.627 0-12-5.373-12-12s5.373-12 12-12c3.059 0 5.842 1.154 7.961 3.039l5.657-5.657C34.046 6.053 29.268 4 24 4 12.955 4 4 12.955 4 24s8.955 20 20 20 20-8.955 20-20c0-1.341-.138-2.65-.389-3.917z"
|
||||
/>
|
||||
<Path
|
||||
fill="#FF3D00"
|
||||
d="m6.306 14.691 6.571 4.819C14.655 15.108 18.961 12 24 12c3.059 0 5.842 1.154 7.961 3.039l5.657-5.657C34.046 6.053 29.268 4 24 4 16.318 4 9.656 8.337 6.306 14.691z"
|
||||
/>
|
||||
<Path
|
||||
fill="#4CAF50"
|
||||
d="M24 44c5.166 0 9.86-1.977 13.409-5.192l-6.19-5.238A11.91 11.91 0 0 1 24 36c-5.202 0-9.619-3.317-11.283-7.946l-6.522 5.025C9.505 39.556 16.227 44 24 44z"
|
||||
/>
|
||||
<Path
|
||||
fill="#1976D2"
|
||||
d="M43.611 20.083H42V20H24v8h11.303a12.04 12.04 0 0 1-4.087 5.571l.003-.002 6.19 5.238C36.971 39.205 44 34 44 24c0-1.341-.138-2.65-.389-3.917z"
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
|
||||
export default GoogleIcon;
|
||||
23
assets/svgs/LockIcon.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import * as React from "react";
|
||||
import Svg, { Path, SvgProps } from "react-native-svg";
|
||||
|
||||
interface LockIconProps extends SvgProps {}
|
||||
|
||||
const LockIcon: React.FC<LockIconProps> = (props) => (
|
||||
<Svg
|
||||
width={22}
|
||||
height={25}
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
stroke="#919191"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M11 15.688v2.5M4.75 10.1c.59-.037 1.316-.037 2.25-.037h8c.934 0 1.66 0 2.25.037m-12.5 0c-.735.044-1.257.145-1.702.372a3.75 3.75 0 0 0-1.64 1.639C1 12.913 1 13.963 1 16.064v1.75c0 2.1 0 3.15.409 3.952a3.75 3.75 0 0 0 1.639 1.639c.802.409 1.852.409 3.952.409h8c2.1 0 3.15 0 3.953-.41a3.75 3.75 0 0 0 1.638-1.638c.409-.802.409-1.852.409-3.952v-1.75c0-2.1 0-3.15-.409-3.953a3.75 3.75 0 0 0-1.639-1.639c-.445-.227-.967-.328-1.702-.372m-12.5 0V7.563a6.25 6.25 0 1 1 12.5 0V10.1"
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
|
||||
export default LockIcon;
|
||||
26
assets/svgs/MenuIcon.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import * as React from "react";
|
||||
import Svg, { Path, SvgProps } from "react-native-svg";
|
||||
|
||||
interface MenuIconProps extends SvgProps {
|
||||
color?: string;
|
||||
}
|
||||
|
||||
const MenuIcon: React.FC<MenuIconProps> = (props) => (
|
||||
<Svg
|
||||
width={24}
|
||||
height={16}
|
||||
viewBox="0 0 24 16"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
stroke={props.color || "#919191"}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2.222}
|
||||
d="M2 8h15.556M2 1.333h20M2 14.667h11.111"
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
|
||||
export default MenuIcon;
|
||||
36
assets/svgs/NavBrainDumpIcon.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import * as React from "react"
|
||||
import Svg, { Path, SvgProps } from "react-native-svg"
|
||||
const NavBrainDumpIcon: React.FC<SvgProps> = (props) => (
|
||||
<Svg
|
||||
width={22}
|
||||
height={28}
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
stroke="#F90"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit={10}
|
||||
strokeWidth={1.509}
|
||||
d="M21.1.859H3.994C2.284.859.976 2.167.976 3.877c0 1.71 1.308 3.019 3.018 3.019H21.1V27.02"
|
||||
/>
|
||||
<Path
|
||||
stroke="#F90"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit={10}
|
||||
strokeWidth={1.509}
|
||||
d="M21.1 27.021H3.994c-1.71 0-3.018-1.308-3.018-3.018V3.878M21.097 3.878H3.991"
|
||||
/>
|
||||
<Path
|
||||
stroke="#F90"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit={10}
|
||||
strokeWidth={1.509}
|
||||
d="M6.007 6.897v12.075l3.019-1.006 3.018 1.006V6.897"
|
||||
/>
|
||||
</Svg>
|
||||
)
|
||||
export default NavBrainDumpIcon
|
||||
20
assets/svgs/NavCalendarIcon.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import * as React from "react"
|
||||
import Svg, { Path, SvgProps } from "react-native-svg"
|
||||
const NavCalendarIcon: React.FC<SvgProps> = (props) => (
|
||||
<Svg
|
||||
width={28}
|
||||
height={28}
|
||||
viewBox="0 0 28 28"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
stroke={props.color || "#07B8C7"}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2.124}
|
||||
d="M1.825 10.075h25.043m-5.565 5.567L7.39 15.64m4.638 5.566H7.39m0-19.478V4.51m13.913-2.782V4.51M6.277 26.77h16.139c1.558 0 2.337 0 2.933-.303.523-.267.949-.692 1.216-1.216.303-.595.303-1.375.303-2.933V8.962c0-1.558 0-2.337-.303-2.933a2.782 2.782 0 0 0-1.216-1.216c-.596-.303-1.375-.303-2.933-.303H6.277c-1.558 0-2.337 0-2.933.303-.523.267-.949.693-1.216 1.216-.303.596-.303 1.375-.303 2.933v13.356c0 1.558 0 2.338.303 2.933.267.523.693.95 1.216 1.216.596.303 1.375.303 2.933.303Z"
|
||||
/>
|
||||
</Svg>
|
||||
)
|
||||
export default NavCalendarIcon
|
||||
20
assets/svgs/NavGroceryIcon.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import * as React from "react";
|
||||
import Svg, { Path, SvgProps } from "react-native-svg";
|
||||
const NavGroceryIcon: React.FC<SvgProps> = (props) => (
|
||||
<Svg
|
||||
width={29}
|
||||
height={32}
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
fill="#50BE0C"
|
||||
d="M27.8 12c-.888-2.27-2.662-4.14-4.983-4.956-2.518-.883-5.143-.382-7.636.307-.047-1.362.068-2.728.262-4.076.11-.761.634-2.335-.668-2.389-1.081-.045-1.084 1.402-1.19 2.14-.203 1.416-.323 2.85-.284 4.281-.599-.1-1.19-.322-1.787-.446a13.485 13.485 0 0 0-2.704-.3c-1.752 0-3.48.465-4.922 1.479C1.09 10.01 0 13.446.046 16.747c.052 3.575 1.263 6.99 3.3 9.905.899 1.288 1.932 2.522 3.21 3.451 1.241.903 2.628 1.325 4.133 1.54.64.093 1.29.096 1.927-.009.557-.09 1.078-.325 1.63-.419.417-.07 1.005.258 1.408.348a6.297 6.297 0 0 0 2.096.107c2.563-.292 4.594-1.449 6.28-3.387 3.71-4.264 5.921-10.791 3.77-16.284Zm-1.69 8.743c-.474 1.663-1.225 3.182-2.195 4.608-.86 1.263-1.855 2.514-3.131 3.377-1.172.79-2.76 1.223-4.173 1.093-.719-.065-1.373-.41-2.087-.478-.8-.076-1.538.352-2.318.459-3.064.416-5.485-1.665-7.146-3.972-2.04-2.831-3.249-6.214-3.134-9.732.106-3.223 1.643-6.55 4.998-7.432 1.678-.44 3.446-.188 5.099.238.715.183 1.454.48 2.196.52.572.066 1.2-.196 1.74-.344 3.233-.89 6.873-1.105 9.124 1.855 2.08 2.732 1.925 6.666 1.027 9.808Z"
|
||||
/>
|
||||
<Path
|
||||
fill="#50BE0C"
|
||||
d="M12 17.966c-.778.285-1.977.71-2.432 1.454.11-.183.221-.363-.002.002-.222.365-.113.186-.001.003-.668 1.099.272 2.53 1.547 2.347 1.258-.18 1.378-1.419 1.364-2.443a8.822 8.822 0 0 0-.095-1.282c-.043-.264-.157-.164-.382-.08ZM18.096 18.917c-.435-.304-.919-.542-1.405-.752-.118-.05-.698-.375-.832-.297.018-.01.036-.021 0 0-.036.023-.02.012-.001.001-.11.07-.096.609-.104.716a9.003 9.003 0 0 0-.01 1.534c.1 1.117 1.049 2.082 2.228 1.517 1.165-.558 1.095-2.04.124-2.719ZM12.223 3.815C12.343.28 8.347-.271 6.922.432c.371 4.075 3.48 4.164 5.301 3.383Z"
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
export default NavGroceryIcon;
|
||||
28
assets/svgs/NavSettingsIcon.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import * as React from "react";
|
||||
import Svg, { Path, SvgProps } from "react-native-svg";
|
||||
const NavSettingsIcon: React.FC<SvgProps> = (props) => (
|
||||
<Svg
|
||||
width={26}
|
||||
height={28}
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
stroke="#6C645B"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit={10}
|
||||
strokeWidth={1.69}
|
||||
d="M12.972 17.893a3.887 3.887 0 1 0 0-7.774 3.887 3.887 0 0 0 0 7.774Z"
|
||||
/>
|
||||
<Path
|
||||
stroke="#6C645B"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit={10}
|
||||
strokeWidth={1.69}
|
||||
d="m24.398 8.531-.972-1.683a1.943 1.943 0 0 0-2.655-.711l-.511.295c-1.944 1.121-4.373-.28-4.373-2.525v-.59c0-1.074-.87-1.944-1.944-1.944H12c-1.073 0-1.943.87-1.943 1.943v.591c0 2.244-2.43 3.647-4.373 2.525l-.511-.295a1.943 1.943 0 0 0-2.655.711l-.972 1.683a1.943 1.943 0 0 0 .711 2.655l.511.296c1.944 1.122 1.944 3.927 0 5.049l-.51.295a1.943 1.943 0 0 0-.712 2.655l.972 1.684a1.943 1.943 0 0 0 2.655.71l.51-.294c1.944-1.123 4.374.28 4.374 2.524v.591c0 1.074.87 1.944 1.944 1.944h1.943c1.074 0 1.944-.87 1.944-1.944v-.59c0-2.245 2.43-3.648 4.373-2.526l.511.296c.93.536 2.119.219 2.655-.711l.972-1.684a1.943 1.943 0 0 0-.712-2.655l-.51-.295c-1.944-1.122-1.944-3.927 0-5.05l.51-.295a1.942 1.942 0 0 0 .712-2.655Z"
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
export default NavSettingsIcon;
|
||||
19
assets/svgs/NavToDosIcon.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import * as React from "react"
|
||||
import Svg, { Path, SvgProps } from "react-native-svg"
|
||||
const NavToDosIcon: React.FC<SvgProps> = (props) => (
|
||||
<Svg
|
||||
width={props.width || 30}
|
||||
height={props.width || 30}
|
||||
viewBox="0 0 30 30"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
fill={props.color || "#8005EB"}
|
||||
stroke={props.color || "#8005EB"}
|
||||
strokeWidth={0.5}
|
||||
d="M15.038 29.649c-7.953 0-14.4-6.447-14.4-14.4s6.447-14.4 14.4-14.4 14.4 6.447 14.4 14.4-6.447 14.4-14.4 14.4Zm0-1.44c7.157 0 12.96-5.802 12.96-12.96 0-7.158-5.803-12.96-12.96-12.96-7.158 0-12.96 5.802-12.96 12.96 0 7.158 5.802 12.96 12.96 12.96Zm4.53-17.07a.72.72 0 1 1 1.019 1.019l-7.2 7.2a.72.72 0 0 1-1.018 0l-2.88-2.88a.72.72 0 1 1 1.018-1.018l2.37 2.37 6.692-6.69Z"
|
||||
/>
|
||||
</Svg>
|
||||
)
|
||||
export default NavToDosIcon
|
||||
119
assets/svgs/OutlookIcon.tsx
Normal file
@ -0,0 +1,119 @@
|
||||
import * as React from "react";
|
||||
import Svg, { Path, LinearGradient, Stop, SvgProps } from "react-native-svg";
|
||||
|
||||
const OutlookIcon: React.FC<SvgProps> = (props) => (
|
||||
<Svg
|
||||
width={34}
|
||||
height={34}
|
||||
viewBox="0 0 48 48"
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
fill="#103262"
|
||||
d="m43.255 23.547-6.81-3.967v11.594H44v-6.331a1.5 1.5 0 0 0-.745-1.296z"
|
||||
/>
|
||||
<Path fill="#0084d7" d="M13 10h10v9H13v-9z" />
|
||||
<Path fill="#33afec" d="M23 10h10v9H23v-9z" />
|
||||
<Path fill="#54daff" d="M33 10h10v9H33v-9z" />
|
||||
<Path fill="#027ad4" d="M23 19h10v9H23v-9z" />
|
||||
<Path fill="#0553a4" d="M23 28h10v9H23v-9z" />
|
||||
<Path fill="#25a2e5" d="M33 19h10v9H33v-9z" />
|
||||
<Path fill="#0262b8" d="M33 28h10v9H33v-9z" />
|
||||
<Path d="M13 37h30V24.238l-14.01 8-15.99-8z" opacity={0.019} />
|
||||
<Path d="M13 37h30V24.476l-14.01 8-15.99-8z" opacity={0.038} />
|
||||
<Path d="M13 37h30V24.714l-14.01 8-15.99-8z" opacity={0.057} />
|
||||
<Path d="M13 37h30V24.952l-14.01 8-15.99-8z" opacity={0.076} />
|
||||
<Path d="M13 37h30V25.19l-14.01 8-15.99-8z" opacity={0.095} />
|
||||
<Path d="M13 37h30V25.429l-14.01 8-15.99-8z" opacity={0.114} />
|
||||
<Path d="M13 37h30V25.667l-14.01 8-15.99-8z" opacity={0.133} />
|
||||
<Path d="M13 37h30V25.905l-14.01 8-15.99-8z" opacity={0.152} />
|
||||
<Path d="M13 37h30V26.143l-14.01 8-15.99-8z" opacity={0.171} />
|
||||
<Path d="M13 37h30V26.381l-14.01 8-15.99-8z" opacity={0.191} />
|
||||
<Path d="M13 37h30V26.619l-14.01 8-15.99-8z" opacity={0.209} />
|
||||
<Path d="M13 37h30V26.857l-14.01 8-15.99-8z" opacity={0.229} />
|
||||
<Path d="M13 37h30v-9.905l-14.01 8-15.99-8z" opacity={0.248} />
|
||||
<Path d="M13 37h30v-9.667l-14.01 8-15.99-8z" opacity={0.267} />
|
||||
<Path d="M13 37h30v-9.429l-14.01 8-15.99-8z" opacity={0.286} />
|
||||
<Path d="M13 37h30v-9.19l-14.01 8-15.99-8z" opacity={0.305} />
|
||||
<Path d="M13 37h30v-8.952l-14.01 8-15.99-8z" opacity={0.324} />
|
||||
<Path d="M13 37h30v-8.714l-14.01 8-15.99-8z" opacity={0.343} />
|
||||
<Path d="M13 37h30v-8.476l-14.01 8-15.99-8z" opacity={0.362} />
|
||||
<Path d="M13 37h30v-8.238l-14.01 8-15.99-8z" opacity={0.381} />
|
||||
<Path d="M13 37h30v-8l-14.01 8L13 29z" opacity={0.4} />
|
||||
<LinearGradient
|
||||
id="a"
|
||||
x1={38.925}
|
||||
x2={32.286}
|
||||
y1={24.557}
|
||||
y2={36.024}
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<Stop offset={0} stopColor="#31abec" />
|
||||
<Stop offset={1} stopColor="#1582d5" />
|
||||
</LinearGradient>
|
||||
<Path
|
||||
fill="url(#a)"
|
||||
d="M15.441 42h26.563a1.996 1.996 0 0 0 2-1.994C44.007 35.485 44 24.843 44 24.843s-.007.222-1.751 1.212-27.505 15.511-27.505 15.511.234.434.697.434z"
|
||||
/>
|
||||
<LinearGradient
|
||||
id="b"
|
||||
x1={13.665}
|
||||
x2={41.285}
|
||||
y1={6.992}
|
||||
y2={9.074}
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<Stop offset={0.042} stopColor="#076db4" />
|
||||
<Stop offset={0.85} stopColor="#0461af" />
|
||||
</LinearGradient>
|
||||
<Path fill="url(#b)" d="M43 10H13V8a2 2 0 0 1 2-2h26a2 2 0 0 1 2 2v2z" />
|
||||
<LinearGradient
|
||||
id="c"
|
||||
x1={28.153}
|
||||
x2={23.638}
|
||||
y1={33.218}
|
||||
y2={41.1}
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<Stop offset={0} stopColor="#33acee" />
|
||||
<Stop offset={1} stopColor="#1b8edf" />
|
||||
</LinearGradient>
|
||||
<Path
|
||||
fill="url(#c)"
|
||||
d="M13 25v15a2 2 0 0 0 2 2H42.004a1.98 1.98 0 0 0 1.221-.425L13 25z"
|
||||
/>
|
||||
<Path
|
||||
d="M21.319 13H13v24h8.319A3.68 3.68 0 0 0 25 33.319V16.681A3.68 3.68 0 0 0 21.319 13z"
|
||||
opacity={0.05}
|
||||
/>
|
||||
<Path
|
||||
d="M21.213 36H13V13.333h8.213a3.12 3.12 0 0 1 3.121 3.121v16.425A3.122 3.122 0 0 1 21.213 36z"
|
||||
opacity={0.07}
|
||||
/>
|
||||
<Path
|
||||
d="M21.106 35H13V13.667h8.106a2.56 2.56 0 0 1 2.56 2.56V32.44a2.56 2.56 0 0 1-2.56 2.56z"
|
||||
opacity={0.09}
|
||||
/>
|
||||
<LinearGradient
|
||||
id="d"
|
||||
x1={3.53}
|
||||
x2={22.41}
|
||||
y1={14.53}
|
||||
y2={33.41}
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<Stop offset={0} stopColor="#1784d8" />
|
||||
<Stop offset={1} stopColor="#0864c5" />
|
||||
</LinearGradient>
|
||||
<Path
|
||||
fill="url(#d)"
|
||||
d="M21 34H5a2 2 0 0 1-2-2V16a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v16a2 2 0 0 1-2 2z"
|
||||
/>
|
||||
<Path
|
||||
fill="#fff"
|
||||
d="M13 18.691c-3.111 0-4.985 2.377-4.985 5.309S9.882 29.309 13 29.309c3.119 0 4.985-2.377 4.985-5.308 0-2.933-1.874-5.31-4.985-5.31zm0 8.826c-1.765 0-2.82-1.574-2.82-3.516s1.06-3.516 2.82-3.516 2.821 1.575 2.821 3.516-1.057 3.516-2.821 3.516z"
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
|
||||
export default OutlookIcon;
|
||||
20
assets/svgs/PenIcon.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import * as React from "react";
|
||||
import Svg, { Path } from "react-native-svg";
|
||||
|
||||
interface PenIconProps extends React.SVGProps<SVGSVGElement> {}
|
||||
|
||||
const PenIcon: React.FC<PenIconProps> = (props) => (
|
||||
<Svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={17}
|
||||
height={19}
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
fill="#919191"
|
||||
d="M9.46 4.489a.907.907 0 1 0-1.416-1.135l1.417 1.135Zm-7.844 8.337.687.592a.835.835 0 0 0 .021-.025l-.708-.567Zm-.207.505-.906-.059v.017l.906.042Zm-.17 3.658-.906-.042c-.002.045 0 .09.004.135l.903-.093Zm.953.831.03.907c.06-.002.12-.01.18-.024l-.21-.883Zm3.63-.86.21.883.013-.003-.223-.88Zm.466-.295.7.578.008-.01-.708-.568Zm8.016-8.546a.908.908 0 0 0-1.416-1.136l1.416 1.136ZM8.048 3.353A.908.908 0 0 0 9.464 4.49L8.048 3.353Zm2.63-1.828.707.568a.942.942 0 0 0 .035-.046l-.742-.522Zm1.97-.387.581-.697a.915.915 0 0 0-.09-.067l-.49.764Zm2.707 2.253.643-.64a.941.941 0 0 0-.062-.057l-.58.697Zm-.01 1.982-.636-.648a.92.92 0 0 0-.072.08l.707.568Zm-2.457 1.61a.907.907 0 1 0 1.416 1.136l-1.416-1.136ZM9.654 3.787a.907.907 0 1 0-1.795.269l1.795-.27Zm4.064 4.663a.908.908 0 0 0-.244-1.798l.244 1.798ZM8.044 3.354.908 12.258l1.416 1.135L9.461 4.49 8.044 3.354ZM.93 12.233c-.251.291-.4.656-.426 1.04l1.812.117c-.001.01-.005.02-.012.028L.93 12.233Zm-.427 1.056-.169 3.658 1.813.084.17-3.658-1.814-.084Zm-.165 3.793a1.834 1.834 0 0 0 1.884 1.645l-.059-1.814h-.007a.02.02 0 0 1-.006-.004.02.02 0 0 1-.005-.006.018.018 0 0 1-.002-.007l-1.805.186ZM2.4 18.703l3.63-.86-.419-1.766-3.63.86.42 1.766Zm3.644-.863c.37-.094.7-.303.943-.598l-1.4-1.155a.021.021 0 0 1 .01-.006l.447 1.759Zm.95-.607 7.309-9.114-1.416-1.136-7.308 9.114 1.416 1.136ZM9.465 4.489l1.921-2.396L9.97.958 8.048 3.353 9.464 4.49Zm1.956-2.442a.544.544 0 0 1 .739-.145l.98-1.528a2.36 2.36 0 0 0-3.204.63l1.485 1.043Zm.648-.211 2.707 2.253 1.161-1.395L13.229.44l-1.16 1.395Zm2.645 2.196a.487.487 0 0 1 .143.347l1.815.01a2.303 2.303 0 0 0-.673-1.639l-1.285 1.282Zm.143.347c-.001.13-.054.255-.147.346l1.27 1.296c.44-.43.688-1.017.692-1.632l-1.816-.01Zm-.22.426-1.748 2.178 1.416 1.136 1.748-2.178-1.415-1.136Zm-6.777-.75a5.215 5.215 0 0 0 5.86 4.395l-.245-1.798a3.4 3.4 0 0 1-3.82-2.865l-1.795.269Z"
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
export default PenIcon;
|
||||
23
assets/svgs/PrivacyPolicyIcon.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import * as React from "react"
|
||||
import Svg, { Path, SvgProps } from "react-native-svg"
|
||||
const PrivacyPolicyIcon: React.FC<SvgProps> = (props) => (
|
||||
<Svg
|
||||
width={props.width ||20}
|
||||
height={props.height ||22}
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
stroke={props.color || "#6C645B"}
|
||||
strokeWidth={1.2}
|
||||
d="M1.413 8.656c0-3.7 0-5.55 1.15-6.7 1.15-1.15 3-1.15 6.7-1.15h1.962c3.7 0 5.55 0 6.7 1.15 1.149 1.15 1.149 3 1.149 6.7v3.925c0 3.7 0 5.55-1.15 6.7-1.15 1.149-3 1.149-6.7 1.149H9.263c-3.7 0-5.55 0-6.7-1.15-1.149-1.15-1.149-3-1.149-6.7V8.657Z"
|
||||
/>
|
||||
<Path
|
||||
stroke={props.color || "#6C645B"}
|
||||
strokeLinecap="round"
|
||||
strokeWidth={1.2}
|
||||
d="M6.324 10.617h7.85M6.324 6.694h7.85M6.324 14.541h4.906"
|
||||
/>
|
||||
</Svg>
|
||||
)
|
||||
export default PrivacyPolicyIcon
|
||||
27
assets/svgs/ProfileIcon.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import * as React from "react"
|
||||
import Svg, { Path, SvgProps } from "react-native-svg"
|
||||
const ProfileIcon: React.FC<SvgProps> = (props) => (
|
||||
<Svg
|
||||
width={22}
|
||||
height={22}
|
||||
viewBox="0 0 21 21"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
stroke={props.color || "red"}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.414}
|
||||
d="M10.956 11.538a.91.91 0 0 0-.226 0 3.083 3.083 0 0 1-2.98-3.084 3.089 3.089 0 0 1 3.093-3.093 3.09 3.09 0 0 1 .113 6.176ZM17.199 17.762a9.368 9.368 0 0 1-6.356 2.47 9.368 9.368 0 0 1-6.356-2.47c.095-.886.66-1.754 1.67-2.433 2.583-1.716 6.808-1.716 9.373 0 1.009.68 1.575 1.547 1.669 2.433Z"
|
||||
/>
|
||||
<Path
|
||||
stroke={props.color || "red"}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.414}
|
||||
d="M10.843 20.233a9.43 9.43 0 1 0 0-18.86 9.43 9.43 0 0 0 0 18.86Z"
|
||||
/>
|
||||
</Svg>
|
||||
)
|
||||
export default ProfileIcon
|
||||
21
assets/svgs/RemindersIcon.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import * as React from "react";
|
||||
import Svg, { SvgProps, Path } from "react-native-svg";
|
||||
const RemindersIcon = (props: SvgProps) => (
|
||||
<Svg
|
||||
width={props.width || 22}
|
||||
height={props.width || 22}
|
||||
viewBox="0 0 22 22"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
fill={props.color || "#919191"}
|
||||
fillRule="evenodd"
|
||||
stroke={props.color || "#919191"}
|
||||
strokeWidth={0.4}
|
||||
d="M3.974 3.96C7.964-.023 14.45.02 18.464 4.034c4.016 4.016 4.057 10.506.067 14.495-3.99 3.99-10.48 3.95-14.495-.067a10.315 10.315 0 0 1-2.94-8.705.788.788 0 1 1 1.561.214 8.738 8.738 0 0 0 2.494 7.376c3.41 3.411 8.902 3.43 12.265.067C20.78 14.05 20.76 8.56 17.35 5.148c-3.41-3.41-8.897-3.43-12.26-.072l.786.004a.788.788 0 1 1-.008 1.576L3.19 6.643a.788.788 0 0 1-.785-.784l-.013-2.677a.788.788 0 1 1 1.577-.007l.004.786Zm7.276 2.293c.435 0 .788.353.788.789v3.878l2.398 2.398a.788.788 0 1 1-1.115 1.115l-2.86-2.86V7.043c0-.436.354-.789.789-.789Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
export default RemindersIcon;
|
||||
@ -1,4 +1,5 @@
|
||||
export async function fetchGoogleCalendarEvents(token, 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}`,
|
||||
{
|
||||
@ -9,6 +10,7 @@ export async function fetchGoogleCalendarEvents(token, startDate, endDate) {
|
||||
);
|
||||
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
const googleEvents = [];
|
||||
data.items?.forEach((item) => {
|
||||
let isAllDay = false;
|
||||
|
||||
101
components/pages/brain_dump/AddBrainDump.tsx
Normal file
@ -0,0 +1,101 @@
|
||||
import { View, Text, Button, TextField } from "react-native-ui-lib";
|
||||
import React, { useEffect, useState } 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 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,
|
||||
}: {
|
||||
addBrainDumpProps: IAddBrainDumpProps;
|
||||
}) => {
|
||||
const { addBrainDump } = useBrainDumpContext();
|
||||
const [dumpTitle, setDumpTitle] = useState<string>("");
|
||||
const [dumpDesc, setDumpDesc] = useState<string>("");
|
||||
const { width, height } = Dimensions.get("screen");
|
||||
|
||||
useEffect(() => {
|
||||
setDumpDesc("");
|
||||
setDumpTitle("");
|
||||
}, [addBrainDumpProps.isVisible]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
bottom={true}
|
||||
height={"90%"}
|
||||
width={width}
|
||||
panDirection={PanningDirectionsEnum.DOWN}
|
||||
onDismiss={() => addBrainDumpProps.setIsVisible(false)}
|
||||
containerStyle={{
|
||||
borderTopRightRadius: 15,
|
||||
borderTopLeftRadius: 15,
|
||||
backgroundColor: "white",
|
||||
padding: 0,
|
||||
paddingTop: 3,
|
||||
margin: 0,
|
||||
}}
|
||||
visible={addBrainDumpProps.isVisible}
|
||||
>
|
||||
<View row spread style={styles.topBtns} marginB-20>
|
||||
<Button
|
||||
color="#05a8b6"
|
||||
label="Cancel"
|
||||
style={styles.topBtn}
|
||||
onPress={() => {
|
||||
addBrainDumpProps.setIsVisible(false);
|
||||
}}
|
||||
/>
|
||||
<DropModalIcon
|
||||
style={{ marginTop: 15 }}
|
||||
onPress={() => addBrainDumpProps.setIsVisible(false)}
|
||||
/>
|
||||
<Button
|
||||
color="#05a8b6"
|
||||
label="Save"
|
||||
style={styles.topBtn}
|
||||
onPress={() => {
|
||||
addBrainDump({ id: 99, title: dumpTitle, description: dumpDesc });
|
||||
addBrainDumpProps.setIsVisible(false);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View marginH-20>
|
||||
<TextField
|
||||
value={dumpTitle}
|
||||
placeholder="Set Title"
|
||||
text60R
|
||||
onChangeText={(text) => {
|
||||
setDumpTitle(text);
|
||||
}}
|
||||
/>
|
||||
<View height={2} backgroundColor="#b3b3b3" width={"100%"} marginB-20 />
|
||||
<TextField
|
||||
value={dumpDesc}
|
||||
placeholder="Write Description"
|
||||
text70
|
||||
onChangeText={(text) => {
|
||||
setDumpDesc(text);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
topBtns: {},
|
||||
topBtn: {
|
||||
backgroundColor: "white",
|
||||
color: "#05a8b6",
|
||||
},
|
||||
});
|
||||
|
||||
export default AddBrainDump;
|
||||
@ -1,59 +1,120 @@
|
||||
import { ScrollView } from "react-native";
|
||||
import { Dimensions, ScrollView } from "react-native";
|
||||
import React, { useState } from "react";
|
||||
import { View, Text } from "react-native-ui-lib";
|
||||
import { View, Text, Button } from "react-native-ui-lib";
|
||||
import DumpList from "./DumpList";
|
||||
import HeaderTemplate from "@/components/shared/HeaderTemplate";
|
||||
import { TextField } from "react-native-ui-lib";
|
||||
import { StyleSheet } from "react-native";
|
||||
import { Feather } from "@expo/vector-icons";
|
||||
import { Feather, MaterialIcons } from "@expo/vector-icons";
|
||||
import { TextInput } from "react-native-gesture-handler";
|
||||
import AddBrainDump from "./AddBrainDump";
|
||||
import LinearGradient from "react-native-linear-gradient";
|
||||
|
||||
const BrainDumpPage = () => {
|
||||
const [searchText, setSearchText] = useState<string>("");
|
||||
const [isAddVisible, setIsAddVisible] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<View>
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
>
|
||||
<View marginH-25>
|
||||
<HeaderTemplate
|
||||
message={"Welcome to your notes!"}
|
||||
isWelcome={false}
|
||||
/>
|
||||
<View>
|
||||
<View style={styles.searchField} centerV>
|
||||
<TextField
|
||||
value={searchText}
|
||||
onChangeText={(value) => {
|
||||
setSearchText(value);
|
||||
}}
|
||||
leadingAccessory={
|
||||
<Feather
|
||||
name="search"
|
||||
size={24}
|
||||
color="#9b9b9b"
|
||||
style={{ paddingRight: 10 }}
|
||||
/>
|
||||
}
|
||||
placeholder="Search notes..."
|
||||
/>
|
||||
<View height={"100%"}>
|
||||
<View>
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
>
|
||||
<View marginH-25>
|
||||
<HeaderTemplate
|
||||
message={"Welcome to your notes!"}
|
||||
isWelcome={false}
|
||||
children={
|
||||
<Text
|
||||
style={{ fontFamily: "Manrope_400Regular", fontSize: 14 }}
|
||||
>
|
||||
Drop your notes on-the-go here, and{"\n"}organize them later.
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
<View>
|
||||
<View style={styles.searchField} centerV>
|
||||
<TextField
|
||||
value={searchText}
|
||||
onChangeText={(value) => {
|
||||
setSearchText(value);
|
||||
}}
|
||||
leadingAccessory={
|
||||
<Feather
|
||||
name="search"
|
||||
size={24}
|
||||
color="#9b9b9b"
|
||||
style={{ paddingRight: 10 }}
|
||||
/>
|
||||
}
|
||||
style={{
|
||||
fontFamily: "Manrope_500Medium",
|
||||
fontSize: 15,
|
||||
}}
|
||||
placeholder="Search notes..."
|
||||
/>
|
||||
</View>
|
||||
<DumpList searchText={searchText} />
|
||||
</View>
|
||||
<DumpList searchText={searchText} />
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</ScrollView>
|
||||
</View>
|
||||
<LinearGradient
|
||||
colors={["#f2f2f2", "transparent"]}
|
||||
start={{ x: 0.5, y: 1 }}
|
||||
end={{ x: 0.5, y: 0 }}
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
height: 90,
|
||||
width: Dimensions.get("screen").width,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
style={{
|
||||
height: 40,
|
||||
position: "relative",
|
||||
marginLeft: "auto",
|
||||
width: 20,
|
||||
right: 20,
|
||||
bottom: -10,
|
||||
borderRadius: 30,
|
||||
backgroundColor: "#fd1775",
|
||||
}}
|
||||
color="white"
|
||||
enableShadow
|
||||
onPress={() => {
|
||||
setIsAddVisible(true);
|
||||
}}
|
||||
>
|
||||
<View row centerV centerH>
|
||||
<MaterialIcons name="add" size={22} color={"white"} />
|
||||
<Text
|
||||
white
|
||||
style={{ fontSize: 16, fontFamily: "Manrope_600SemiBold" }}
|
||||
>
|
||||
New
|
||||
</Text>
|
||||
</View>
|
||||
</Button>
|
||||
</LinearGradient>
|
||||
<AddBrainDump
|
||||
addBrainDumpProps={{
|
||||
isVisible: isAddVisible,
|
||||
setIsVisible: setIsAddVisible,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
searchField: {
|
||||
borderWidth: 1,
|
||||
borderWidth: 0.7,
|
||||
borderColor: "#9b9b9b",
|
||||
borderRadius: 18,
|
||||
height: 48,
|
||||
borderRadius: 15,
|
||||
height: 42,
|
||||
paddingLeft: 10,
|
||||
marginVertical: 20,
|
||||
},
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import { View, Text } from "react-native-ui-lib";
|
||||
import React, { useState } from "react";
|
||||
import { IBrainDump } from "@/contexts/DumpContext";
|
||||
import { TouchableOpacity, TouchableWithoutFeedback } from "react-native-gesture-handler";
|
||||
import {
|
||||
TouchableOpacity,
|
||||
TouchableWithoutFeedback,
|
||||
} from "react-native-gesture-handler";
|
||||
import MoveBrainDump from "./MoveBrainDump";
|
||||
|
||||
const BrainDumpItem = (props: { item: IBrainDump }) => {
|
||||
@ -13,16 +16,34 @@ const BrainDumpItem = (props: { item: IBrainDump }) => {
|
||||
<View
|
||||
backgroundColor="white"
|
||||
marginV-5
|
||||
padding-15
|
||||
style={{ borderRadius: 20, elevation: 2 }}
|
||||
paddingH-13
|
||||
paddingV-10
|
||||
style={{ borderRadius: 15, elevation: 2 }}
|
||||
>
|
||||
<Text text70BL marginB-8>
|
||||
<Text
|
||||
text70B
|
||||
style={{ fontSize: 15, fontFamily: "Manrope_600SemiBold" }}
|
||||
marginB-8
|
||||
>
|
||||
{props.item.title}
|
||||
</Text>
|
||||
<Text text70>{props.item.description}</Text>
|
||||
<Text
|
||||
text70
|
||||
style={{
|
||||
fontSize: 13,
|
||||
fontFamily: "Manrope_400Regular",
|
||||
color: "#5c5c5c",
|
||||
}}
|
||||
>
|
||||
{props.item.description}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
<MoveBrainDump item={props.item} isVisible={isVisible} setIsVisible={setIsVisible} />
|
||||
<MoveBrainDump
|
||||
item={props.item}
|
||||
isVisible={isVisible}
|
||||
setIsVisible={setIsVisible}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,24 +1,33 @@
|
||||
import { View } from 'react-native-ui-lib';
|
||||
import React from 'react';
|
||||
import { useBrainDumpContext } from '@/contexts/DumpContext';
|
||||
import { FlatList } from 'react-native';
|
||||
import BrainDumpItem from './DumpItem';
|
||||
import { View } from "react-native-ui-lib";
|
||||
import React from "react";
|
||||
import { useBrainDumpContext } from "@/contexts/DumpContext";
|
||||
import { FlatList } from "react-native";
|
||||
import BrainDumpItem from "./DumpItem";
|
||||
import LinearGradient from "react-native-linear-gradient";
|
||||
|
||||
const DumpList = (props: { searchText: string }) => {
|
||||
const { brainDumps } = useBrainDumpContext();
|
||||
|
||||
const filteredBrainDumps = props.searchText.trim() === ""
|
||||
? brainDumps
|
||||
: brainDumps.filter((item) =>
|
||||
item.title.toLowerCase().includes(props.searchText.toLowerCase())
|
||||
);
|
||||
const filteredBrainDumps =
|
||||
props.searchText.trim() === ""
|
||||
? brainDumps
|
||||
: brainDumps.filter(
|
||||
(item) =>
|
||||
item.title.toLowerCase().includes(props.searchText.toLowerCase()) ||
|
||||
item.description
|
||||
.toLowerCase()
|
||||
.includes(props.searchText.toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View marginB-70>
|
||||
<FlatList
|
||||
style={{ zIndex: -1 }}
|
||||
data={filteredBrainDumps}
|
||||
keyExtractor={(item) => item.title}
|
||||
renderItem={({ item }) => <BrainDumpItem key={item.title} item={item} />}
|
||||
renderItem={({ item }) => (
|
||||
<BrainDumpItem key={item.title} item={item} />
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
@ -1,50 +1,67 @@
|
||||
import React, { useState } from "react";
|
||||
import { Button, Dialog, View, Text, TextField } from "react-native-ui-lib";
|
||||
import { StyleSheet } from "react-native";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
View,
|
||||
Text,
|
||||
TextField,
|
||||
TouchableOpacity,
|
||||
} from "react-native-ui-lib";
|
||||
import { Dimensions, StyleSheet } from "react-native";
|
||||
import { PanningDirectionsEnum } from "react-native-ui-lib/src/incubator/panView";
|
||||
import { IBrainDump } from "@/contexts/DumpContext";
|
||||
import { Entypo, EvilIcons, Feather, Octicons } from "@expo/vector-icons";
|
||||
import { TouchableOpacity } from "react-native-gesture-handler";
|
||||
import { IBrainDump, useBrainDumpContext } from "@/contexts/DumpContext";
|
||||
import PenIcon from "@/assets/svgs/PenIcon";
|
||||
import BinIcon from "@/assets/svgs/BinIcon";
|
||||
import DropModalIcon from "@/assets/svgs/DropModalIcon";
|
||||
import CloseXIcon from "@/assets/svgs/CloseXIcon";
|
||||
import NavCalendarIcon from "@/assets/svgs/NavCalendarIcon";
|
||||
import NavToDosIcon from "@/assets/svgs/NavToDosIcon";
|
||||
import RemindersIcon from "@/assets/svgs/RemindersIcon";
|
||||
import MenuIcon from "@/assets/svgs/MenuIcon";
|
||||
|
||||
const MoveBrainDump = (props: {
|
||||
item: IBrainDump;
|
||||
isVisible: boolean;
|
||||
setIsVisible: (value: boolean) => void;
|
||||
}) => {
|
||||
const { updateBrainDumpItem, deleteBrainDump } = useBrainDumpContext();
|
||||
const [description, setDescription] = useState<string>(
|
||||
props.item.description
|
||||
);
|
||||
const { width } = Dimensions.get("screen");
|
||||
|
||||
useEffect(() => {
|
||||
updateBrainDumpItem(props.item.id, { description: description });
|
||||
}, [description]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
bottom={true}
|
||||
height={"90%"}
|
||||
width={width}
|
||||
panDirection={PanningDirectionsEnum.DOWN}
|
||||
onDismiss={() => props.setIsVisible(false)}
|
||||
containerStyle={{
|
||||
borderRadius: 10,
|
||||
borderRadius: 15,
|
||||
backgroundColor: "white",
|
||||
width: "100%",
|
||||
alignSelf: "stretch",
|
||||
padding: 0,
|
||||
padding: 10,
|
||||
paddingTop: 3,
|
||||
margin: 0,
|
||||
}}
|
||||
visible={props.isVisible}
|
||||
>
|
||||
<View row spread paddingH-10 paddingV-15>
|
||||
<View center paddingT-8>
|
||||
<TouchableOpacity onPress={() => props.setIsVisible(false)}>
|
||||
<DropModalIcon />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View row spread paddingH-10 paddingB-15>
|
||||
<Button
|
||||
color="#05a8b6"
|
||||
style={styles.topBtn}
|
||||
iconSource={() => <EvilIcons name="close" size={30} color="black" />}
|
||||
onPress={() => {
|
||||
props.setIsVisible(false);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
style={styles.topBtn}
|
||||
iconSource={() => (
|
||||
<Feather name="chevron-down" size={24} color="black" />
|
||||
)}
|
||||
iconSource={() => <CloseXIcon />}
|
||||
onPress={() => {
|
||||
props.setIsVisible(false);
|
||||
}}
|
||||
@ -52,34 +69,42 @@ const MoveBrainDump = (props: {
|
||||
<View row>
|
||||
<Button
|
||||
style={styles.topBtn}
|
||||
iconSource={() => (
|
||||
<Octicons name="pencil" size={24} color="#919191" />
|
||||
)}
|
||||
onPress={() => {}}
|
||||
marginR-10
|
||||
iconSource={() => <PenIcon />}
|
||||
onPress={() => {
|
||||
console.log("selview");
|
||||
props.setIsVisible(false);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
style={styles.topBtn}
|
||||
iconSource={() => (
|
||||
<EvilIcons name="trash" size={30} color="#919191" />
|
||||
)}
|
||||
onPress={() => {}}
|
||||
marginL-5
|
||||
iconSource={() => <BinIcon />}
|
||||
onPress={() => {
|
||||
deleteBrainDump(props.item.id);
|
||||
props.setIsVisible(false);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View centerH>
|
||||
<Text text60R>{props.item.title} </Text>
|
||||
<Text style={styles.title}>{props.item.title} </Text>
|
||||
</View>
|
||||
<View style={styles.divider} />
|
||||
<View row marginH-20>
|
||||
<Entypo
|
||||
name="text"
|
||||
size={24}
|
||||
color="black"
|
||||
style={{ marginBottom: "auto" }}
|
||||
/>
|
||||
<View row gap-5>
|
||||
<View paddingT-8 marginR-5>
|
||||
<MenuIcon width={20} height={12} />
|
||||
</View>
|
||||
<TextField
|
||||
textAlignVertical="top"
|
||||
multiline
|
||||
fieldStyle={{
|
||||
width: "94%",
|
||||
}}
|
||||
style={{
|
||||
fontFamily: "Manrope_400Regular",
|
||||
fontSize: 14,
|
||||
}}
|
||||
placeholder="Add description"
|
||||
numberOfLines={3}
|
||||
value={description}
|
||||
@ -89,6 +114,38 @@ const MoveBrainDump = (props: {
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.divider} />
|
||||
<View gap-20>
|
||||
<TouchableOpacity>
|
||||
<View row centerV>
|
||||
<NavToDosIcon
|
||||
width={22}
|
||||
color={"#919191"}
|
||||
style={styles.optionsIcon}
|
||||
/>
|
||||
<Text style={styles.optionsReg}>Move to</Text>
|
||||
<Text style={styles.optionsBold}> my to do's</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity>
|
||||
<View row centerV>
|
||||
<NavCalendarIcon
|
||||
width={22}
|
||||
height={22}
|
||||
color={"#919191"}
|
||||
style={styles.optionsIcon}
|
||||
/>
|
||||
<Text style={styles.optionsReg}>Move to</Text>
|
||||
<Text style={styles.optionsBold}> my calendar</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity>
|
||||
<View row centerV>
|
||||
<RemindersIcon width={22} style={styles.optionsIcon} />
|
||||
<Text style={styles.optionsReg}>Move to</Text>
|
||||
<Text style={styles.optionsBold}> my reminders</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@ -113,12 +170,28 @@ const styles = StyleSheet.create({
|
||||
topBtn: {
|
||||
backgroundColor: "white",
|
||||
color: "#05a8b6",
|
||||
marginTop: -3,
|
||||
},
|
||||
rotateSwitch: {
|
||||
marginLeft: 35,
|
||||
marginBottom: 10,
|
||||
marginTop: 25,
|
||||
},
|
||||
optionsReg: {
|
||||
fontSize: 16,
|
||||
fontFamily: "PlusJakartaSans_400Regular",
|
||||
},
|
||||
optionsBold: {
|
||||
fontSize: 16,
|
||||
fontFamily: "PlusJakartaSans_600SemiBold",
|
||||
},
|
||||
optionsIcon: {
|
||||
marginRight: 10,
|
||||
},
|
||||
title: {
|
||||
fontSize: 22,
|
||||
fontFamily: "Manrope_500Medium",
|
||||
},
|
||||
});
|
||||
|
||||
export default MoveBrainDump;
|
||||
|
||||
@ -14,12 +14,14 @@ import {
|
||||
Text,
|
||||
View,
|
||||
} from "react-native-ui-lib";
|
||||
import { TouchableOpacity } from "react-native";
|
||||
import { StyleSheet, TouchableOpacity } from "react-native";
|
||||
import { ManuallyAddEventModal } from "@/components/pages/calendar/ManuallyAddEventModal";
|
||||
import AddChoreDialog from "../todos/AddChoreDialog";
|
||||
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";
|
||||
|
||||
export const AddEventDialog = () => {
|
||||
const [show, setShow] = useState(false);
|
||||
@ -37,127 +39,150 @@ export const AddEventDialog = () => {
|
||||
const handleScanImageDialog = () => {
|
||||
setShow(false);
|
||||
setTimeout(() => {
|
||||
setShowUploadDialog(true);
|
||||
setShowUploadDialog(true);
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
<ToDosContextProvider>
|
||||
<>
|
||||
<Button
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 20,
|
||||
right: 20,
|
||||
height: 40,
|
||||
borderRadius: 30,
|
||||
backgroundColor: "#fd1775",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
centerV
|
||||
color="white"
|
||||
enableShadow
|
||||
iconSource={() => (
|
||||
<MaterialIcons name="add" size={22} color={"white"} />
|
||||
)}
|
||||
onPress={() => setShow(true)}
|
||||
label="New"
|
||||
text60R
|
||||
/>
|
||||
|
||||
<Dialog
|
||||
visible={show}
|
||||
onDismiss={() => setShow(false)}
|
||||
panDirection={PanningProvider.Directions.DOWN}
|
||||
center
|
||||
>
|
||||
<Card
|
||||
<>
|
||||
<Button
|
||||
style={{
|
||||
paddingHorizontal: 40,
|
||||
paddingTop: 40,
|
||||
paddingBottom: 20,
|
||||
justifyContent: "center",
|
||||
position: "absolute",
|
||||
bottom: 20,
|
||||
right: 20,
|
||||
height: 40,
|
||||
borderRadius: 30,
|
||||
backgroundColor: "#fd1775",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
color="white"
|
||||
enableShadow
|
||||
onPress={() => setShow(true)}
|
||||
>
|
||||
<Text text50R>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"
|
||||
onPress={handleScanImageDialog}
|
||||
iconSource={() => (
|
||||
<Feather
|
||||
name="camera"
|
||||
size={21}
|
||||
style={{ marginRight: 7 }}
|
||||
color="white"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
style={{
|
||||
marginBottom: 10,
|
||||
backgroundColor: "#e28800",
|
||||
justifyContent: "center",
|
||||
width: "100%",
|
||||
paddingVertical: 13,
|
||||
}}
|
||||
label="Create Event"
|
||||
onPress={handleOpenManualInputModal}
|
||||
iconSource={() => (
|
||||
<MaterialCommunityIcons
|
||||
name="calendar-text-outline"
|
||||
size={22}
|
||||
style={{ marginRight: 5 }}
|
||||
color="white"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
style={{
|
||||
marginBottom: 10,
|
||||
backgroundColor: "#05a8b6",
|
||||
justifyContent: "center",
|
||||
width: "100%",
|
||||
paddingVertical: 13,
|
||||
}}
|
||||
label="Add To Do"
|
||||
onPress={() => setChoreDialogVisible(true)}
|
||||
iconSource={() => (
|
||||
<AntDesign
|
||||
name="checkcircleo"
|
||||
size={20}
|
||||
style={{ marginRight: 7 }}
|
||||
color="white"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<View row centerV centerH>
|
||||
<MaterialIcons name="add" size={22} color={"white"} />
|
||||
<Text white style={{ fontSize: 16, fontFamily: 'Manrope_600SemiBold' }}>
|
||||
New
|
||||
</Text>
|
||||
</View>
|
||||
</Button>
|
||||
|
||||
<TouchableOpacity onPress={() => setShow(false)}>
|
||||
<Text style={{ marginTop: 20, color: "#999999" }} 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} />
|
||||
</>
|
||||
<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} />
|
||||
)}
|
||||
/>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
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 },
|
||||
});
|
||||
|
||||
204
components/pages/calendar/CalendarPage.tsx
Normal file
@ -0,0 +1,204 @@
|
||||
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 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",
|
||||
];
|
||||
|
||||
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}
|
||||
>
|
||||
<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()]} // 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}
|
||||
/>
|
||||
</View>
|
||||
</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 : {}}
|
||||
/>
|
||||
)}
|
||||
</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>
|
||||
);
|
||||
}
|
||||
@ -3,8 +3,10 @@ import React, { useState } from "react";
|
||||
import { MaterialIcons } from "@expo/vector-icons";
|
||||
import { StyleSheet } from "react-native";
|
||||
|
||||
const CalendarViewSwitch = () => {
|
||||
const [show, setShow] = useState<boolean>(false);
|
||||
interface ICalendarViewProps {
|
||||
viewSwitch: (value: boolean) => void;
|
||||
}
|
||||
const CalendarViewSwitch = (calendarViewProps: ICalendarViewProps) => {
|
||||
const [calView, setCalView] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
@ -29,7 +31,12 @@ const CalendarViewSwitch = () => {
|
||||
}}
|
||||
centerV
|
||||
>
|
||||
<TouchableOpacity onPress={() => setCalView(true)}>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
setCalView(true);
|
||||
calendarViewProps.viewSwitch(true);
|
||||
}}
|
||||
>
|
||||
<View
|
||||
centerV
|
||||
centerH
|
||||
@ -37,13 +44,18 @@ const CalendarViewSwitch = () => {
|
||||
paddingH-15
|
||||
style={calView ? styles.switchBtnActive : styles.switchBtn}
|
||||
>
|
||||
<Text color={calView ? "white" : "#a1a1a1"} text70R>
|
||||
<Text color={calView ? "white" : "#a1a1a1"} style={styles.switchTxt}>
|
||||
Family View
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity onPress={() => setCalView(false)}>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
setCalView(false);
|
||||
calendarViewProps.viewSwitch(false);
|
||||
}}
|
||||
>
|
||||
<View
|
||||
centerV
|
||||
centerH
|
||||
@ -51,7 +63,7 @@ const CalendarViewSwitch = () => {
|
||||
paddingH-15
|
||||
style={!calView ? styles.switchBtnActive : styles.switchBtn}
|
||||
>
|
||||
<Text color={!calView ? "white" : "#a1a1a1"} text70R>
|
||||
<Text color={!calView ? "white" : "#a1a1a1"} style={styles.switchTxt}>
|
||||
My View
|
||||
</Text>
|
||||
</View>
|
||||
@ -71,4 +83,8 @@ const styles = StyleSheet.create({
|
||||
backgroundColor: "white",
|
||||
borderRadius: 50,
|
||||
},
|
||||
switchTxt:{
|
||||
fontSize: 16,
|
||||
fontFamily: 'Manrope_600SemiBold'
|
||||
}
|
||||
});
|
||||
|
||||
312
components/pages/calendar/EditEventDialog.tsx
Normal file
@ -0,0 +1,312 @@
|
||||
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 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";
|
||||
|
||||
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();
|
||||
|
||||
useEffect(() => {
|
||||
setEvent(editEventProps.event);
|
||||
}, [editEventProps.isVisible]);
|
||||
|
||||
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);
|
||||
}}
|
||||
/>
|
||||
</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 }));
|
||||
}}
|
||||
/>
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
@ -1,331 +1,488 @@
|
||||
import {
|
||||
Avatar,
|
||||
Colors,
|
||||
DateTimePicker,
|
||||
LoaderScreen,
|
||||
Modal,
|
||||
Picker,
|
||||
Switch,
|
||||
Text,
|
||||
TextField,
|
||||
View,
|
||||
Avatar,
|
||||
Button,
|
||||
ButtonSize,
|
||||
Colors,
|
||||
DateTimePicker,
|
||||
LoaderScreen,
|
||||
Modal,
|
||||
Picker,
|
||||
Switch,
|
||||
Text,
|
||||
TextField,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from "react-native-ui-lib";
|
||||
import {ScrollView, TouchableOpacity} from "react-native-gesture-handler";
|
||||
import {useSafeAreaInsets} from "react-native-safe-area-context";
|
||||
import {useState} from "react";
|
||||
import {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} 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,
|
||||
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 DropModalIcon from "@/assets/svgs/DropModalIcon";
|
||||
import { CalendarEvent, useCalendarContext } from "@/contexts/CalendarContext";
|
||||
import { repeatOptions } from "@/contexts/ToDosContext";
|
||||
import { ImageBackground, 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";
|
||||
|
||||
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
|
||||
show,
|
||||
close,
|
||||
initialDate,
|
||||
}: {
|
||||
show: boolean;
|
||||
close: () => void;
|
||||
initialDate?: Date;
|
||||
}) => {
|
||||
const {user} = useAuthContext();
|
||||
const insets = useSafeAreaInsets();
|
||||
const { addEvent } = useCalendarContext();
|
||||
const { user } = useAuthContext();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const [title, setTitle] = useState<string>("");
|
||||
const [title, setTitle] = useState<string>("");
|
||||
|
||||
const [isAllDay, setIsAllDay] = useState(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) : new Date();
|
||||
date.setSeconds(0, 0);
|
||||
return date;
|
||||
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 [startDate, setStartDate] = useState(initialDate ?? new Date());
|
||||
const [endDate, setEndDate] = useState(initialDate ?? new Date());
|
||||
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 [repeatInterval, setRepeatInterval] = useState<PickerMultiValue>([]);
|
||||
const handleSave = async () => {
|
||||
let finalStartDate: Date;
|
||||
let finalEndDate: Date;
|
||||
|
||||
const {mutateAsync: createEvent, isLoading, isError} = useCreateEvent();
|
||||
|
||||
const formatDateTime = (date: Date) => {
|
||||
return 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);
|
||||
finalStartDate.setHours(0, 0, 0, 0);
|
||||
|
||||
finalEndDate = new Date(startDate);
|
||||
finalEndDate.setHours(23, 59, 59, 999);
|
||||
} else {
|
||||
finalStartDate = combineDateAndTime(startDate, startTime);
|
||||
finalEndDate = combineDateAndTime(endDate, endTime);
|
||||
}
|
||||
|
||||
const eventData: Partial<EventData> = {
|
||||
title,
|
||||
startDate: finalStartDate,
|
||||
endDate: finalEndDate,
|
||||
repeatDays: repeatInterval.map((x) => x.toString()),
|
||||
allDay: isAllDay,
|
||||
};
|
||||
|
||||
await createEvent(eventData);
|
||||
|
||||
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 (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 eventData: Partial<EventData> = {
|
||||
title: title,
|
||||
startDate: finalStartDate,
|
||||
endDate: finalEndDate,
|
||||
allDay: isAllDay,
|
||||
};
|
||||
|
||||
await createEvent(eventData);
|
||||
|
||||
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}
|
||||
>
|
||||
<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,
|
||||
}}
|
||||
>
|
||||
<TouchableOpacity onPress={close}>
|
||||
<Text style={{color: "#007bff"}}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
<Text style={{fontWeight: "bold", fontSize: 16}}>Add event</Text>
|
||||
<TouchableOpacity onPress={handleSave}>
|
||||
<Text style={{color: "#007bff"}}>Save</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<ScrollView
|
||||
contentContainerStyle={{paddingHorizontal: 16, paddingTop: 10}}
|
||||
>
|
||||
<View style={{marginVertical: 10}}>
|
||||
<TextField
|
||||
placeholder={"Title"}
|
||||
floatingPlaceholder
|
||||
value={title}
|
||||
onChangeText={setTitle}
|
||||
showCharCounter
|
||||
maxLength={200}
|
||||
fieldStyle={{
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: "black",
|
||||
borderStyle: "solid",
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: 20,
|
||||
}}
|
||||
>
|
||||
<View style={{flexDirection: "row", alignItems: "center"}}>
|
||||
<MaterialIcons name="schedule" size={24} color="gray"/>
|
||||
<Text style={{marginLeft: 10}}>All-day</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={isAllDay}
|
||||
onValueChange={(value) => setIsAllDay(value)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: 20,
|
||||
}}
|
||||
>
|
||||
<DateTimePicker
|
||||
mode="date"
|
||||
dateFormatter={formatDateTime}
|
||||
value={startDate}
|
||||
onChange={setStartDate}
|
||||
display="default"
|
||||
/>
|
||||
{!isAllDay && (
|
||||
<DateTimePicker
|
||||
mode="time"
|
||||
value={startTime}
|
||||
onChange={setStartTime}
|
||||
display="spinner"
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{!isAllDay && (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: 20,
|
||||
}}
|
||||
>
|
||||
<DateTimePicker
|
||||
mode="date"
|
||||
dateFormatter={formatDateTime}
|
||||
value={endDate}
|
||||
onChange={setEndDate}
|
||||
display="default"
|
||||
/>
|
||||
<DateTimePicker
|
||||
mode="time"
|
||||
value={endTime}
|
||||
onChange={setEndTime}
|
||||
minuteInterval={1}
|
||||
display="spinner"
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginBottom: 20,
|
||||
}}
|
||||
>
|
||||
<MaterialIcons name="repeat" size={24} color="gray"/>
|
||||
<Picker
|
||||
value={repeatInterval}
|
||||
//@ts-ignore
|
||||
onChange={(items: PickerMultiValue) => setRepeatInterval(items)}
|
||||
placeholder="Doest not repeat"
|
||||
style={{marginLeft: 10, flex: 1}}
|
||||
mode={Picker.modes.MULTI}
|
||||
getLabel={getRepeatLabel}
|
||||
>
|
||||
{daysOfWeek.map((option) => (
|
||||
<Picker.Item
|
||||
key={option.value}
|
||||
label={option.label}
|
||||
value={option.value}
|
||||
/>
|
||||
))}
|
||||
</Picker>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginBottom: 20,
|
||||
}}
|
||||
>
|
||||
<MaterialIcons name="person-add" size={24} color="gray"/>
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
marginLeft: 10,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
<Avatar size={40} backgroundColor={Colors.yellow10}/>
|
||||
<View style={{marginLeft: 10}}>
|
||||
<Text>Other</Text>
|
||||
<Text style={{color: "gray"}}>{user?.email}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
</Modal>
|
||||
<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,
|
||||
}}
|
||||
>
|
||||
<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"
|
||||
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>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
@ -11,6 +11,8 @@ import { Dialog, PanningProvider, Card } from "react-native-ui-lib";
|
||||
import { StyleSheet } from "react-native";
|
||||
import { Feather, MaterialIcons } from "@expo/vector-icons";
|
||||
import * as ImagePicker from "expo-image-picker";
|
||||
import AddImageIcon from "@/assets/svgs/AddImageIcon";
|
||||
import CameraIcon from "@/assets/svgs/CameraIcon";
|
||||
|
||||
interface IUploadDialogProps {
|
||||
show: boolean;
|
||||
@ -51,19 +53,9 @@ const UploadImageDialog = (uploadDialogProps: IUploadDialogProps) => {
|
||||
panDirection={PanningProvider.Directions.DOWN}
|
||||
center
|
||||
>
|
||||
<Card
|
||||
style={{
|
||||
paddingHorizontal: 40,
|
||||
paddingTop: 20,
|
||||
paddingBottom: 10,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Card style={styles.modalCard}>
|
||||
<View centerH>
|
||||
<Text text60 marginB-20>
|
||||
Upload an Image
|
||||
</Text>
|
||||
<Text style={styles.modalTitle}>Upload an Image</Text>
|
||||
{!selectedImage && (
|
||||
<TouchableOpacity onPress={handleImagePick}>
|
||||
<View
|
||||
@ -73,12 +65,8 @@ const UploadImageDialog = (uploadDialogProps: IUploadDialogProps) => {
|
||||
gap-8
|
||||
marginB-20
|
||||
>
|
||||
<MaterialIcons
|
||||
name="add-photo-alternate"
|
||||
size={30}
|
||||
color="#fd1775"
|
||||
/>
|
||||
<Text color="#fd1775" text70>
|
||||
<AddImageIcon />
|
||||
<Text style={styles.uploadTxt}>
|
||||
Click here to upload an image
|
||||
</Text>
|
||||
</View>
|
||||
@ -102,38 +90,31 @@ const UploadImageDialog = (uploadDialogProps: IUploadDialogProps) => {
|
||||
setImageTitle("");
|
||||
}}
|
||||
>
|
||||
<Feather
|
||||
name="trash"
|
||||
size={22}
|
||||
color="#919191"
|
||||
/>
|
||||
<Feather name="trash" size={22} color="#919191" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<Button
|
||||
style={{
|
||||
marginBottom: 10,
|
||||
marginTop: 20,
|
||||
marginTop: 35,
|
||||
backgroundColor: "#ea156c",
|
||||
justifyContent: "center",
|
||||
paddingVertical: 13,
|
||||
paddingVertical: 15,
|
||||
width: 285,
|
||||
alignItems: "center",
|
||||
}}
|
||||
label="Upload Image"
|
||||
onPress={() => {}}
|
||||
labelStyle={styles.btnLbl}
|
||||
iconSource={() => (
|
||||
<Feather
|
||||
name="camera"
|
||||
size={21}
|
||||
style={{ marginRight: 7 }}
|
||||
color="white"
|
||||
/>
|
||||
<CameraIcon color="white" style={{marginRight: 10}}/>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<TouchableOpacity onPress={() => uploadDialogProps.setShow(false)}>
|
||||
<Text text80 color="#999999">
|
||||
<Text style={styles.bottomText}>
|
||||
Go back
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
@ -148,6 +129,7 @@ export default UploadImageDialog;
|
||||
const styles = StyleSheet.create({
|
||||
uploadImgBox: {
|
||||
backgroundColor: "#ffe8f2",
|
||||
padding: 35,
|
||||
width: "100%",
|
||||
aspectRatio: 1.8,
|
||||
borderRadius: 20,
|
||||
@ -155,10 +137,20 @@ const styles = StyleSheet.create({
|
||||
borderColor: "#fd1775",
|
||||
borderStyle: "dashed",
|
||||
},
|
||||
btnLbl: {
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 15,
|
||||
},
|
||||
uploadTxt: {
|
||||
color: "#b11d5a",
|
||||
fontSize: 15,
|
||||
fontFamily: "PlusJakartaSans_400Regular",
|
||||
marginTop: 12
|
||||
},
|
||||
selectedImage: {
|
||||
width: 60,
|
||||
width: 38.69,
|
||||
aspectRatio: 1,
|
||||
borderRadius: 10,
|
||||
borderRadius: 5,
|
||||
},
|
||||
imageContainer: {
|
||||
alignItems: "center",
|
||||
@ -166,13 +158,33 @@ const styles = StyleSheet.create({
|
||||
borderWidth: 1,
|
||||
borderColor: "#d9d9d9",
|
||||
padding: 10,
|
||||
borderRadius: 13,
|
||||
borderRadius: 10,
|
||||
},
|
||||
imageInfo: {
|
||||
marginLeft: 10,
|
||||
},
|
||||
imageTitle: {
|
||||
fontSize: 16,
|
||||
color: "#333",
|
||||
fontSize: 15,
|
||||
color: "#262627",
|
||||
fontFamily: "PlusJakartaSans_400Regular"
|
||||
},
|
||||
modalCard: {
|
||||
paddingHorizontal: 25,
|
||||
paddingTop: 30,
|
||||
paddingBottom: 17,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
borderRadius: 20,
|
||||
},
|
||||
modalTitle: {
|
||||
fontSize: 22,
|
||||
fontFamily: "Manrope_600SemiBold",
|
||||
marginBottom: 20,
|
||||
},
|
||||
bottomText: {
|
||||
marginTop: 20,
|
||||
color: "#999999",
|
||||
fontSize: 13.53,
|
||||
fontFamily: "Poppins_500Medium",
|
||||
},
|
||||
});
|
||||
|
||||
@ -45,8 +45,11 @@ const AddGroceryItem = () => {
|
||||
text70L
|
||||
iconSource={() => <FontAwesome6 name="add" size={18} color="white" />}
|
||||
style={styles.finishShopBtn}
|
||||
labelStyle={styles.addBtnLbl}
|
||||
enableShadow
|
||||
onPress={() => {setIsAddingGrocery(true)}}
|
||||
onPress={() => {
|
||||
setIsAddingGrocery(true);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
@ -89,4 +92,5 @@ const styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
marginHorizontal: 3,
|
||||
},
|
||||
addBtnLbl: { fontFamily: "Manrope_500Medium", fontSize: 17, marginLeft: 5 },
|
||||
});
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { StyleSheet } from "react-native";
|
||||
import React, { useState } from "react";
|
||||
import React from "react";
|
||||
import {
|
||||
Dialog,
|
||||
Text,
|
||||
@ -11,9 +11,9 @@ import {
|
||||
} from "react-native-ui-lib";
|
||||
import {
|
||||
GroceryFrequency,
|
||||
IGrocery,
|
||||
useGroceryContext,
|
||||
} from "@/contexts/GroceryContext";
|
||||
import { IGrocery } from "@/hooks/firebase/types/groceryData";
|
||||
interface EditGroceryFrequencyProps {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
@ -42,7 +42,7 @@ const EditGroceryFrequency = (props: EditGroceryFrequencyProps) => {
|
||||
<Switch
|
||||
value={props.item.recurring}
|
||||
onValueChange={(value) =>
|
||||
updateGroceryItem(props.item.id, { recurring: value })
|
||||
updateGroceryItem({id: props.item.id, recurring: value})
|
||||
}
|
||||
onColor={"lime"}
|
||||
/>
|
||||
@ -56,8 +56,9 @@ const EditGroceryFrequency = (props: EditGroceryFrequencyProps) => {
|
||||
const selectedFrequency =
|
||||
GroceryFrequency[item as keyof typeof GroceryFrequency];
|
||||
if (selectedFrequency) {
|
||||
updateGroceryItem(props.item.id, {
|
||||
frequency: selectedFrequency,
|
||||
updateGroceryItem({
|
||||
id: props.item.id,
|
||||
frequency: selectedFrequency,
|
||||
});
|
||||
} else {
|
||||
console.error("Invalid frequency selected");
|
||||
|
||||
@ -1,60 +1,72 @@
|
||||
import { View, Text } from "react-native";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { TextField } from "react-native-ui-lib";
|
||||
import {
|
||||
GroceryCategory,
|
||||
IGrocery,
|
||||
useGroceryContext,
|
||||
} from "@/contexts/GroceryContext";
|
||||
import { TouchableWithoutFeedback } from "react-native-gesture-handler";
|
||||
import {Text, View} from "react-native";
|
||||
import React, {useEffect, useRef} from "react";
|
||||
import {TextField, TextFieldRef} from "react-native-ui-lib";
|
||||
import {GroceryCategory, useGroceryContext,} from "@/contexts/GroceryContext";
|
||||
|
||||
interface IEditGrocery {
|
||||
id?: number;
|
||||
title: string;
|
||||
setTitle: (value: string) => void;
|
||||
setCategory?: (category: GroceryCategory) => void;
|
||||
category: GroceryCategory;
|
||||
setSubmit?: (value: boolean) => void;
|
||||
updateCategory?: (id: number, changes: Partial<IGrocery>) => void;
|
||||
closeEdit?: (value: boolean) => void;
|
||||
id?: string;
|
||||
title: string;
|
||||
category: GroceryCategory;
|
||||
setTitle: (value: string) => void;
|
||||
setCategory?: (category: GroceryCategory) => void;
|
||||
setSubmit?: (value: boolean) => void;
|
||||
closeEdit?: (value: boolean) => void;
|
||||
handleEditSubmit?: Function
|
||||
}
|
||||
|
||||
const EditGroceryItem = ({ editGrocery }: { editGrocery: IEditGrocery }) => {
|
||||
const { fuzzyMatchGroceryCategory } = useGroceryContext();
|
||||
const EditGroceryItem = ({editGrocery}: { editGrocery: IEditGrocery }) => {
|
||||
const {fuzzyMatchGroceryCategory} = useGroceryContext();
|
||||
const inputRef = useRef<TextFieldRef>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (editGrocery.setCategory)
|
||||
editGrocery.setCategory(fuzzyMatchGroceryCategory(editGrocery.title));
|
||||
}, [editGrocery.title]);
|
||||
useEffect(() => {
|
||||
if (editGrocery.setCategory)
|
||||
editGrocery.setCategory(fuzzyMatchGroceryCategory(editGrocery.title));
|
||||
}, [editGrocery.title]);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: "white",
|
||||
width: "100%",
|
||||
borderRadius: 25,
|
||||
padding: 15,
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
placeholder="Grocery"
|
||||
value={editGrocery.title}
|
||||
onChangeText={(value) => {
|
||||
editGrocery.setTitle(value);
|
||||
}}
|
||||
onSubmitEditing={() => {
|
||||
if (editGrocery.setSubmit) editGrocery.setSubmit(true);
|
||||
if (editGrocery.closeEdit) editGrocery.closeEdit(false);
|
||||
if (editGrocery.updateCategory && editGrocery.id)
|
||||
editGrocery.updateCategory(editGrocery.id, {
|
||||
category: fuzzyMatchGroceryCategory(editGrocery.title), title: editGrocery.title
|
||||
});
|
||||
}}
|
||||
maxLength={25}
|
||||
/>
|
||||
<Text>{editGrocery.category}</Text>
|
||||
</View>
|
||||
);
|
||||
useEffect(() => {
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus(); // Focus on the TextField
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: "white",
|
||||
width: "100%",
|
||||
borderRadius: 25,
|
||||
padding: 15,
|
||||
marginTop: 10
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
text70T
|
||||
style={{fontWeight: "400"}}
|
||||
ref={inputRef}
|
||||
placeholder="Grocery"
|
||||
value={editGrocery.title}
|
||||
onChangeText={(value) => {
|
||||
editGrocery.setTitle(value);
|
||||
}}
|
||||
onSubmitEditing={() => {
|
||||
if (editGrocery.setSubmit) {
|
||||
editGrocery.setSubmit(true);
|
||||
}
|
||||
if (editGrocery.setCategory) {
|
||||
editGrocery.setCategory(fuzzyMatchGroceryCategory(editGrocery.title));
|
||||
}
|
||||
if (editGrocery.handleEditSubmit) {
|
||||
editGrocery.handleEditSubmit({id: editGrocery.id, title: editGrocery.title, category: editGrocery.category});
|
||||
}
|
||||
if (editGrocery.closeEdit) {
|
||||
editGrocery.closeEdit(false);
|
||||
}
|
||||
}}
|
||||
maxLength={25}
|
||||
/>
|
||||
<Text>{editGrocery.category}</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditGroceryItem;
|
||||
|
||||
@ -1,66 +1,68 @@
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
Button,
|
||||
TouchableOpacity,
|
||||
Checkbox,
|
||||
ButtonSize,
|
||||
} from "react-native-ui-lib";
|
||||
import { Checkbox, Text, TouchableOpacity, View } from "react-native-ui-lib";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ProfileType, useAuthContext } from "@/contexts/AuthContext";
|
||||
import { MaterialCommunityIcons, AntDesign } from "@expo/vector-icons";
|
||||
import { ListItem } from "react-native-ui-lib";
|
||||
import {
|
||||
GroceryCategory,
|
||||
IGrocery,
|
||||
useGroceryContext,
|
||||
} from "@/contexts/GroceryContext";
|
||||
import { AntDesign } from "@expo/vector-icons";
|
||||
import { GroceryCategory, useGroceryContext } from "@/contexts/GroceryContext";
|
||||
import EditGroceryFrequency from "./EditGroceryFrequency";
|
||||
import EditGroceryItem from "./EditGroceryItem";
|
||||
import { StyleSheet } from "react-native";
|
||||
import { ImageBackground, StyleSheet } from "react-native";
|
||||
import { IGrocery } from "@/hooks/firebase/types/groceryData";
|
||||
import firestore from "@react-native-firebase/firestore";
|
||||
import { UserProfile } from "@/hooks/firebase/types/profileTypes";
|
||||
|
||||
const GroceryItem = ({
|
||||
item,
|
||||
handleItemApproved,
|
||||
}: {
|
||||
item: IGrocery;
|
||||
handleItemApproved: (id: number, changes: Partial<IGrocery>) => void;
|
||||
handleItemApproved: (id: string, changes: Partial<IGrocery>) => void;
|
||||
}) => {
|
||||
const { updateGroceryItem, groceries } = useGroceryContext();
|
||||
const { updateGroceryItem } = useGroceryContext();
|
||||
|
||||
const { profileType } = useAuthContext();
|
||||
const [openFreqEdit, setOpenFreqEdit] = useState<boolean>(false);
|
||||
const [isEditingTitle, setIsEditingTitle] = useState<boolean>(false);
|
||||
const [newTitle, setNewTitle] = useState<string>("");
|
||||
const [category, setCategory] = useState<GroceryCategory>(
|
||||
GroceryCategory.None
|
||||
);
|
||||
const [itemCreator, setItemCreator] = useState<UserProfile>(null);
|
||||
|
||||
const handleTitleChange = (newTitle: string) => {
|
||||
updateGroceryItem(item.id, { title: newTitle });
|
||||
updateGroceryItem({ id: item?.id, title: newTitle });
|
||||
};
|
||||
|
||||
const handleCategoryChange = (newCategory: GroceryCategory) => {
|
||||
updateGroceryItem(item.id, { category: newCategory });
|
||||
updateGroceryItem({ id: item?.id, category: newCategory });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setNewTitle(item.title);
|
||||
|
||||
getItemCreator(item?.creatorId);
|
||||
}, []);
|
||||
|
||||
const getItemCreator = async (uid: string | undefined) => {
|
||||
if (uid) {
|
||||
const documentSnapshot = await firestore()
|
||||
.collection("Profiles")
|
||||
.doc(uid)
|
||||
.get();
|
||||
|
||||
if (documentSnapshot.exists) {
|
||||
setItemCreator(documentSnapshot.data() as UserProfile);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View
|
||||
key={item.id}
|
||||
style={{ borderRadius: 18, marginVertical: 5 }}
|
||||
style={{ borderRadius: 17, marginVertical: 5 }}
|
||||
backgroundColor="white"
|
||||
centerV
|
||||
padding-0
|
||||
paddingH-13
|
||||
paddingV-10
|
||||
>
|
||||
<ListItem
|
||||
onPress={() => {
|
||||
setOpenFreqEdit(true);
|
||||
}}
|
||||
>
|
||||
<View row spread>
|
||||
<EditGroceryFrequency
|
||||
visible={openFreqEdit}
|
||||
key={item.id}
|
||||
@ -69,90 +71,80 @@ const GroceryItem = ({
|
||||
setOpenFreqEdit(false);
|
||||
}}
|
||||
/>
|
||||
<ListItem.Part left containerStyle={{ flex: 1, paddingStart: 20 }}>
|
||||
{!isEditingTitle ? (
|
||||
<View>
|
||||
<TouchableOpacity onPress={() => setIsEditingTitle(true)}>
|
||||
<Text text70BL>{item.title}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
) : (
|
||||
<EditGroceryItem
|
||||
{!isEditingTitle ? (
|
||||
<View>
|
||||
<TouchableOpacity onPress={() => setIsEditingTitle(true)}>
|
||||
<Text text70T black style={styles.title}>
|
||||
{item.title}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
) : (
|
||||
<EditGroceryItem
|
||||
editGrocery={{
|
||||
id: item.id,
|
||||
title: newTitle,
|
||||
setTitle: setNewTitle,
|
||||
category: category,
|
||||
updateCategory: updateGroceryItem,
|
||||
closeEdit: setIsEditingTitle,
|
||||
setTitle: setNewTitle,
|
||||
setCategory: setCategory,
|
||||
closeEdit: setIsEditingTitle,
|
||||
handleEditSubmit: updateGroceryItem
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!item.approved ? (
|
||||
<View row centerV marginB-10>
|
||||
<AntDesign
|
||||
name="check"
|
||||
size={24}
|
||||
style={{
|
||||
color: item.approved ? "green" : "#aaaaaa",
|
||||
marginRight: 15,
|
||||
}}
|
||||
onPress={() => {
|
||||
handleItemApproved(item.id, { approved: true });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ListItem.Part>
|
||||
<ListItem.Part right containerStyle={{ paddingEnd: item.approved ? 20 : 5 }}>
|
||||
{!item.approved ? (
|
||||
<View row >
|
||||
<Button
|
||||
padding-0
|
||||
children={
|
||||
<AntDesign
|
||||
name="check"
|
||||
size={24}
|
||||
style={{
|
||||
color: item.approved ? "green" : "#aaaaaa",
|
||||
}}
|
||||
/>
|
||||
}
|
||||
backgroundColor="transparent"
|
||||
size={Button.sizes.small}
|
||||
onPress={() => {
|
||||
handleItemApproved(item.id, { approved: true });
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
padding-0
|
||||
children={
|
||||
<AntDesign
|
||||
name="close"
|
||||
size={24}
|
||||
style={{ color: item.approved ? "#aaaaaa" : "red" }}
|
||||
/>
|
||||
}
|
||||
backgroundColor="transparent"
|
||||
size={Button.sizes.small}
|
||||
onPress={() => {
|
||||
handleItemApproved(item.id, { approved: false });
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<Checkbox
|
||||
value={item.bought}
|
||||
style={styles.checkbox}
|
||||
onValueChange={() =>
|
||||
updateGroceryItem(item.id, { bought: !item.bought })
|
||||
}
|
||||
<AntDesign
|
||||
name="close"
|
||||
size={24}
|
||||
style={{ color: item.approved ? "#aaaaaa" : "red" }}
|
||||
onPress={() => {
|
||||
handleItemApproved(item.id, { approved: false });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ListItem.Part>
|
||||
</ListItem>
|
||||
</View>
|
||||
) : (
|
||||
<Checkbox
|
||||
value={item.bought}
|
||||
containerStyle={styles.checkbox}
|
||||
hitSlop={20}
|
||||
onValueChange={() =>
|
||||
updateGroceryItem({ id: item.id, bought: !item.bought })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
{!item.approved && (
|
||||
<View>
|
||||
<View centerH>
|
||||
<View height={1} backgroundColor="#e7e7e7" width={"90%"} />
|
||||
<View height={0.7} backgroundColor="#e7e7e7" width={"98%"} />
|
||||
</View>
|
||||
<View paddingL-10 paddingV-15 flexS row centerV>
|
||||
<View
|
||||
<View paddingL-0 paddingT-12 flexS row centerV>
|
||||
<ImageBackground
|
||||
style={{
|
||||
width: 25,
|
||||
width: 22.36,
|
||||
aspectRatio: 1,
|
||||
borderRadius: 50,
|
||||
backgroundColor: "red",
|
||||
marginHorizontal: 10,
|
||||
marginRight: 10,
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
></View>
|
||||
<Text color="#858585" text70>Requested by Austin</Text>
|
||||
source={require('../../../assets/images/child-picture.png')}
|
||||
/>
|
||||
<Text color="#858585" style={styles.authorTxt}>
|
||||
Requested by {itemCreator?.firstName}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
@ -161,12 +153,19 @@ const GroceryItem = ({
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
checkbox:{
|
||||
authorTxt: { fontFamily: "Manrope_500Medium", fontSize: 12 },
|
||||
checkbox: {
|
||||
borderRadius: 50,
|
||||
borderWidth: 1,
|
||||
borderWidth: 0.7,
|
||||
color: "#bfbfbf",
|
||||
borderColor: "#bfbfbf",
|
||||
}
|
||||
})
|
||||
width: 24.64,
|
||||
aspectRatio: 1,
|
||||
},
|
||||
title: {
|
||||
fontFamily: "Manrope_500Medium",
|
||||
fontSize: 15,
|
||||
},
|
||||
});
|
||||
|
||||
export default GroceryItem;
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
import { FlatList } from "react-native";
|
||||
import { FlatList, StyleSheet } from "react-native";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { View, Text, ListItem, Button, TextField } from "react-native-ui-lib";
|
||||
import { Button, Text, TouchableOpacity, View } from "react-native-ui-lib";
|
||||
import GroceryItem from "./GroceryItem";
|
||||
import {
|
||||
IGrocery,
|
||||
GroceryCategory,
|
||||
GroceryFrequency,
|
||||
useGroceryContext,
|
||||
} from "@/contexts/GroceryContext";
|
||||
import HeaderTemplate from "@/components/shared/HeaderTemplate";
|
||||
import CategoryDropdown from "./CategoryDropdown";
|
||||
import { MaterialIcons } from "@expo/vector-icons";
|
||||
import { AntDesign, MaterialIcons } from "@expo/vector-icons";
|
||||
import EditGroceryItem from "./EditGroceryItem";
|
||||
import { ProfileType, useAuthContext } from "@/contexts/AuthContext";
|
||||
import { IGrocery } from "@/hooks/firebase/types/groceryData";
|
||||
import AddPersonIcon from "@/assets/svgs/AddPersonIcon";
|
||||
|
||||
const GroceryList = () => {
|
||||
const {
|
||||
@ -24,10 +24,10 @@ const GroceryList = () => {
|
||||
} = useGroceryContext();
|
||||
const { profileData } = useAuthContext();
|
||||
const [approvedGroceries, setapprovedGroceries] = useState<IGrocery[]>(
|
||||
groceries.filter((item) => item.approved === true)
|
||||
groceries?.filter((item) => item.approved === true)
|
||||
);
|
||||
const [pendingGroceries, setPendingGroceries] = useState<IGrocery[]>(
|
||||
groceries.filter((item) => item.approved !== true)
|
||||
groceries?.filter((item) => item.approved !== true)
|
||||
);
|
||||
const [category, setCategory] = useState<GroceryCategory>(
|
||||
GroceryCategory.Bakery
|
||||
@ -35,8 +35,11 @@ const GroceryList = () => {
|
||||
const [title, setTitle] = useState<string>("");
|
||||
const [submit, setSubmitted] = useState<boolean>(false);
|
||||
|
||||
const [pendingVisible, setPendingVisible] = useState<boolean>(true);
|
||||
const [approvedVisible, setApprovedVisible] = useState<boolean>(true);
|
||||
|
||||
// Group approved groceries by category
|
||||
const approvedGroceriesByCategory = approvedGroceries.reduce(
|
||||
const approvedGroceriesByCategory = approvedGroceries?.reduce(
|
||||
(groups: any, item: IGrocery) => {
|
||||
const category = item.category || "Uncategorized";
|
||||
if (!groups[category]) {
|
||||
@ -52,10 +55,10 @@ const GroceryList = () => {
|
||||
if (submit) {
|
||||
if (title?.length > 2 && title?.length <= 25) {
|
||||
addGrocery({
|
||||
id: 0,
|
||||
id: "",
|
||||
title: title,
|
||||
category: category,
|
||||
approved: profileData?.userType === ProfileType.PARENT ? true : false,
|
||||
approved: profileData?.userType === ProfileType.PARENT,
|
||||
recurring: false,
|
||||
frequency: GroceryFrequency.Never,
|
||||
bought: false,
|
||||
@ -73,8 +76,8 @@ const GroceryList = () => {
|
||||
}, [category]);
|
||||
|
||||
useEffect(() => {
|
||||
setapprovedGroceries(groceries.filter((item) => item.approved === true));
|
||||
setPendingGroceries(groceries.filter((item) => item.approved !== true));
|
||||
setapprovedGroceries(groceries?.filter((item) => item.approved === true));
|
||||
setPendingGroceries(groceries?.filter((item) => item.approved !== true));
|
||||
}, [groceries]);
|
||||
|
||||
return (
|
||||
@ -83,20 +86,23 @@ const GroceryList = () => {
|
||||
message={"Welcome to your grocery list"}
|
||||
isWelcome={false}
|
||||
>
|
||||
<View row spread gap-5>
|
||||
<View row centerV>
|
||||
<View
|
||||
backgroundColor="#e2eed8"
|
||||
padding-8
|
||||
paddingH-15
|
||||
paddingV-8
|
||||
marginR-5
|
||||
centerV
|
||||
style={{ borderRadius: 50 }}
|
||||
>
|
||||
<Text text70BL color="#46a80a">
|
||||
{approvedGroceries.length} list{" "}
|
||||
{approvedGroceries.length === 1 ? (
|
||||
<Text text70BL color="#46a80a">
|
||||
<Text text70BL color="#46a80a" style={styles.counterText}>
|
||||
{approvedGroceries?.length} list{" "}
|
||||
{approvedGroceries?.length === 1 ? (
|
||||
<Text text70BL color="#46a80a" style={styles.counterText}>
|
||||
item
|
||||
</Text>
|
||||
) : (
|
||||
<Text text70BL color="#46a80a">
|
||||
<Text text70BL color="#46a80a" style={styles.counterText}>
|
||||
items
|
||||
</Text>
|
||||
)}
|
||||
@ -105,65 +111,118 @@ const GroceryList = () => {
|
||||
<View
|
||||
backgroundColor="#faead2"
|
||||
padding-8
|
||||
paddingH-12
|
||||
marginR-15
|
||||
style={{ borderRadius: 50 }}
|
||||
>
|
||||
<Text text70BL color="#e28800">
|
||||
{pendingGroceries.length} pending
|
||||
<Text text70 style={styles.counterText} color="#e28800">
|
||||
{pendingGroceries?.length} pending
|
||||
</Text>
|
||||
</View>
|
||||
<Button
|
||||
backgroundColor="transparent"
|
||||
paddingH-10
|
||||
iconSource={() => (
|
||||
<MaterialIcons name="person-add-alt" size={24} color="gray" />
|
||||
)}
|
||||
/>
|
||||
<TouchableOpacity>
|
||||
<AddPersonIcon width={24}/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</HeaderTemplate>
|
||||
|
||||
{/* Pending Approval Section */}
|
||||
<View row spread marginT-40 marginB-20 centerV>
|
||||
<Text text70BL>Pending Approval</Text>
|
||||
<View row spread marginT-40 marginB-10 centerV>
|
||||
<View row centerV>
|
||||
<Text style={styles.subHeader}>Pending Approval</Text>
|
||||
{pendingVisible && (
|
||||
<AntDesign
|
||||
name="down"
|
||||
size={17}
|
||||
style={styles.dropIcon}
|
||||
color="#9f9f9f"
|
||||
onPress={() => {
|
||||
setPendingVisible(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!pendingVisible && (
|
||||
<AntDesign
|
||||
name="right"
|
||||
size={15}
|
||||
style={styles.dropIcon}
|
||||
color="#9f9f9f"
|
||||
onPress={() => {
|
||||
setPendingVisible(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<View
|
||||
centerV
|
||||
style={{
|
||||
aspectRatio: 1,
|
||||
width: 40,
|
||||
width: 35,
|
||||
backgroundColor: "#faead2",
|
||||
borderRadius: 50,
|
||||
}}
|
||||
>
|
||||
<Text text60L center color="#e28800">
|
||||
{pendingGroceries.length.toString()}
|
||||
<Text style={styles.counterNr} center color="#e28800">
|
||||
{pendingGroceries?.length.toString()}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
{pendingGroceries.length > 0 ? (
|
||||
<FlatList
|
||||
data={pendingGroceries}
|
||||
renderItem={({ item }) => (
|
||||
<GroceryItem item={item} handleItemApproved={updateGroceryItem} />
|
||||
{pendingGroceries?.length > 0
|
||||
? pendingVisible && (
|
||||
<FlatList
|
||||
data={pendingGroceries}
|
||||
renderItem={({ item }) => (
|
||||
<GroceryItem
|
||||
item={item}
|
||||
handleItemApproved={(id, changes) =>
|
||||
updateGroceryItem({ ...changes, id: id })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
keyExtractor={(item) => item.id.toString()}
|
||||
/>
|
||||
)
|
||||
: pendingVisible && (
|
||||
<Text style={styles.noItemTxt}>No items pending approval.</Text>
|
||||
)}
|
||||
keyExtractor={(item) => item.id.toString()}
|
||||
/>
|
||||
) : (
|
||||
<Text>No items pending approval.</Text>
|
||||
)}
|
||||
|
||||
{/* Approved Section */}
|
||||
<View row spread marginT-40 marginB-20 centerV>
|
||||
<Text text70BL>Shopping List</Text>
|
||||
<View row spread marginT-40 marginB-0 centerV>
|
||||
<View row centerV>
|
||||
<Text style={styles.subHeader}>Shopping List</Text>
|
||||
{approvedVisible && (
|
||||
<AntDesign
|
||||
name="down"
|
||||
size={17}
|
||||
style={styles.dropIcon}
|
||||
color="#9f9f9f"
|
||||
onPress={() => {
|
||||
setApprovedVisible(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!approvedVisible && (
|
||||
<AntDesign
|
||||
name="right"
|
||||
size={15}
|
||||
style={styles.dropIcon}
|
||||
color="#9f9f9f"
|
||||
onPress={() => {
|
||||
setApprovedVisible(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<View
|
||||
centerV
|
||||
style={{
|
||||
aspectRatio: 1,
|
||||
width: 40,
|
||||
width: 35,
|
||||
backgroundColor: "#e2eed8",
|
||||
borderRadius: 50,
|
||||
}}
|
||||
>
|
||||
<Text text60L center color="#46a80a">
|
||||
{approvedGroceries.length.toString()}
|
||||
<Text style={styles.counterNr} center color="#46a80a">
|
||||
{approvedGroceries?.length.toString()}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
@ -180,38 +239,60 @@ const GroceryList = () => {
|
||||
)}
|
||||
|
||||
{/* Render Approved Groceries Grouped by Category */}
|
||||
{approvedGroceries.length > 0 ? (
|
||||
<FlatList
|
||||
data={Object.keys(approvedGroceriesByCategory)}
|
||||
renderItem={({ item: category }) => (
|
||||
<View key={category}>
|
||||
{/* Render Category Header */}
|
||||
<Text
|
||||
text70M
|
||||
style={{ marginVertical: 10, paddingHorizontal: 15 }}
|
||||
color="#666"
|
||||
>
|
||||
{category}
|
||||
</Text>
|
||||
{/* Render Grocery Items for this Category */}
|
||||
{approvedGroceriesByCategory[category].map(
|
||||
(grocery: IGrocery) => (
|
||||
<GroceryItem
|
||||
key={grocery.id}
|
||||
item={grocery}
|
||||
handleItemApproved={updateGroceryItem}
|
||||
/>
|
||||
)
|
||||
{approvedGroceries?.length > 0
|
||||
? approvedVisible && (
|
||||
<FlatList
|
||||
data={Object.keys(approvedGroceriesByCategory)}
|
||||
renderItem={({ item: category }) => (
|
||||
<View key={category}>
|
||||
{/* Render Category Header */}
|
||||
<Text text80M style={{ marginTop: 10 }} color="#666">
|
||||
{category}
|
||||
</Text>
|
||||
{/* Render Grocery Items for this Category */}
|
||||
{approvedGroceriesByCategory[category].map(
|
||||
(grocery: IGrocery) => (
|
||||
<GroceryItem
|
||||
key={grocery.id}
|
||||
item={grocery}
|
||||
handleItemApproved={(id, changes) =>
|
||||
updateGroceryItem({ ...changes, id: id })
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
keyExtractor={(category) => category}
|
||||
/>
|
||||
)
|
||||
: approvedVisible && (
|
||||
<Text style={styles.noItemTxt}>No approved items.</Text>
|
||||
)}
|
||||
keyExtractor={(category) => category}
|
||||
/>
|
||||
) : (
|
||||
<Text>No approved items.</Text>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
dropIcon: {
|
||||
marginHorizontal: 10,
|
||||
},
|
||||
noItemTxt: {
|
||||
fontFamily: "Manrope_400Regular",
|
||||
fontSize: 14,
|
||||
},
|
||||
counterText: {
|
||||
fontSize: 14,
|
||||
fontFamily: "PlusJakartaSans_600SemiBold",
|
||||
},
|
||||
subHeader: {
|
||||
fontSize: 15,
|
||||
fontFamily: "Manrope_700Bold",
|
||||
},
|
||||
counterNr: {
|
||||
fontFamily: "PlusJakartaSans_600SemiBold",
|
||||
fontSize: 14
|
||||
}
|
||||
});
|
||||
|
||||
export default GroceryList;
|
||||
|
||||
@ -1,20 +1,36 @@
|
||||
import { Text, ScrollView } from "react-native";
|
||||
import { View } from "react-native-ui-lib";
|
||||
import React from "react";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import AddGroceryItem from "./AddGroceryItem";
|
||||
import GroceryList from "./GroceryList";
|
||||
import { useGroceryContext } from "@/contexts/GroceryContext";
|
||||
|
||||
const GroceryWrapper = () => {
|
||||
const { isAddingGrocery } = useGroceryContext();
|
||||
const scrollViewRef = useRef<ScrollView>(null); // Reference to the ScrollView
|
||||
|
||||
useEffect(() => {
|
||||
if (isAddingGrocery && scrollViewRef.current) {
|
||||
scrollViewRef.current.scrollTo({
|
||||
y: 400, // Adjust this value to scroll a bit down (100 is an example)
|
||||
animated: true,
|
||||
});
|
||||
}
|
||||
}, [isAddingGrocery]);
|
||||
|
||||
return (
|
||||
<View height={"100%"}>
|
||||
<View height={'90%'}>
|
||||
<ScrollView>
|
||||
<GroceryList />
|
||||
<View height={"100%"} paddingT-15 paddingH-15>
|
||||
<View height={"100%"}>
|
||||
<ScrollView
|
||||
ref={scrollViewRef} // Assign the ref to the ScrollView
|
||||
automaticallyAdjustKeyboardInsets={true}
|
||||
>
|
||||
<View marginB-70>
|
||||
<GroceryList />
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
{!isAddingGrocery && <AddGroceryItem />}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@ -31,7 +31,7 @@ const SignUpPage = ({setTab}: { setTab: React.Dispatch<React.SetStateAction<"re
|
||||
return (
|
||||
<View padding-10 height={"100%"} flexG>
|
||||
<Text text30 center>
|
||||
Get started with Kali
|
||||
Get started with Cally
|
||||
</Text>
|
||||
<Text center>Please enter your details.</Text>
|
||||
<TextField
|
||||
|
||||
@ -28,21 +28,28 @@ const OnboardingFlow = () => {
|
||||
width={10}
|
||||
/>
|
||||
),
|
||||
title: <Text text30>Welcome to Kali</Text>,
|
||||
title: <Text text30>Welcome to Cally</Text>,
|
||||
subtitle: (
|
||||
<View paddingB-250 marginH-20 spread>
|
||||
<Text text50R>Lightening Mental Loads, One Family at a Time</Text>
|
||||
<Button
|
||||
label="Continue"
|
||||
style={{ backgroundColor: "#fd1775" }}
|
||||
onPress={() => onboardingRef.current.goToPage(1, true)}
|
||||
onPress={() => onboardingRef?.current?.goToPage(1, true)}
|
||||
/>
|
||||
</View>
|
||||
),
|
||||
},
|
||||
{
|
||||
backgroundColor: "#f9f8f7",
|
||||
title: <Text>Get started with Kali</Text>,
|
||||
title: <Text>Get started with Cally</Text>,
|
||||
image: (
|
||||
<Image
|
||||
source={require("../../../assets/images/splash-clock.png")}
|
||||
height={10}
|
||||
width={10}
|
||||
/>
|
||||
),
|
||||
subtitle: (
|
||||
<View
|
||||
style={{
|
||||
|
||||
@ -1,371 +1,433 @@
|
||||
import {AntDesign, Ionicons} from "@expo/vector-icons";
|
||||
import React, {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 {GoogleSignin} from "@react-native-google-signin/google-signin";
|
||||
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 debounce from "debounce";
|
||||
import AppleIcon from "@/assets/svgs/AppleIcon";
|
||||
import GoogleIcon from "@/assets/svgs/GoogleIcon";
|
||||
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";
|
||||
|
||||
GoogleSignin.configure({
|
||||
webClientId:
|
||||
"406146460310-hjadmfa1gg4ptaouira5rkhu0djlo5ut.apps.googleusercontent.com",
|
||||
scopes: ["profile", "email"], // Note: add calendar scope
|
||||
});
|
||||
|
||||
const GoogleLogin = async () => {
|
||||
return await GoogleSignin.signIn();
|
||||
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",
|
||||
],
|
||||
};
|
||||
|
||||
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", // 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",
|
||||
};
|
||||
|
||||
const CalendarSettingsPage = (props: {
|
||||
setSelectedPage: (page: number) => void;
|
||||
setSelectedPage: (page: number) => void;
|
||||
}) => {
|
||||
const [selectedColor, setSelectedColor] = useState<string>(colorMap.pink);
|
||||
const [startDate, setStartDate] = useState<boolean>(true);
|
||||
const {profileData} = useAuthContext();
|
||||
const [startDate, setStartDate] = useState<boolean>(false);
|
||||
const { profileData } = useAuthContext();
|
||||
|
||||
const {mutateAsync: createEventFromProvider} = useCreateEventFromProvider();
|
||||
const {mutateAsync: updateUserData} = useUpdateUserData();
|
||||
const [selectedColor, setSelectedColor] = useState<string>(
|
||||
profileData?.eventColor ?? colorMap.pink
|
||||
);
|
||||
const [previousSelectedColor, setPreviousSelectedColor] = useState<string>(
|
||||
profileData?.eventColor ?? colorMap.pink
|
||||
);
|
||||
|
||||
const fetchAndSaveGoogleEvents = () => {
|
||||
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 { mutateAsync: createEventFromProvider } = useCreateEventFromProvider();
|
||||
const { mutateAsync: updateUserData } = useUpdateUserData();
|
||||
|
||||
fetchGoogleCalendarEvents(
|
||||
profileData?.googleToken,
|
||||
timeMin.toISOString().slice(0, -5) + "Z",
|
||||
timeMax.toISOString().slice(0, -5) + "Z",
|
||||
).then((response) => {
|
||||
response?.forEach((item) => saveData(item));
|
||||
});
|
||||
};
|
||||
WebBrowser.maybeCompleteAuthSession();
|
||||
const [request, response, promptAsync] = Google.useAuthRequest(googleConfig);
|
||||
|
||||
async function saveData(item: any) {
|
||||
await createEventFromProvider(item);
|
||||
}
|
||||
useEffect(() => {
|
||||
signInWithGoogle();
|
||||
}, [response]);
|
||||
|
||||
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
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
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 handleGoogleLogin = async () => {
|
||||
try {
|
||||
const response = await GoogleLogin();
|
||||
if (response) {
|
||||
const googleUserData = response.data;
|
||||
let idToken = googleUserData?.idToken;
|
||||
|
||||
if (idToken) {
|
||||
await updateUserData({newUserData: {googleToken: idToken}});
|
||||
}
|
||||
}
|
||||
} catch (apiError) {
|
||||
console.log(apiError || "Something went wrong");
|
||||
}
|
||||
};
|
||||
|
||||
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")}`,
|
||||
});
|
||||
|
||||
|
||||
console.log("Token response status:", tokenResponse.status);
|
||||
|
||||
if (!tokenResponse.ok) {
|
||||
console.error("Token exchange failed:", await tokenResponse.text());
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
<View marginH-30>
|
||||
<TouchableOpacity onPress={() => props.setSelectedPage(0)}>
|
||||
<View row marginT-20 marginB-35 centerV>
|
||||
<Ionicons name="chevron-back" size={22} color="#979797"/>
|
||||
<Text text70 color="#979797">
|
||||
Return to main settings
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<Text text60R>Calendar settings</Text>
|
||||
<View style={styles.card}>
|
||||
<Text text70 marginB-14>
|
||||
Event Color Preference
|
||||
</Text>
|
||||
<View row spread>
|
||||
<TouchableOpacity onPress={() => setSelectedColor(colorMap.pink)}>
|
||||
<View style={styles.colorBox} backgroundColor={colorMap.pink}>
|
||||
{selectedColor == colorMap.pink && (
|
||||
<AntDesign name="check" size={30} color="white"/>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={() => setSelectedColor(colorMap.orange)}>
|
||||
<View style={styles.colorBox} backgroundColor={colorMap.orange}>
|
||||
{selectedColor == colorMap.orange && (
|
||||
<AntDesign name="check" size={30} color="white"/>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={() => setSelectedColor(colorMap.green)}>
|
||||
<View style={styles.colorBox} backgroundColor={colorMap.green}>
|
||||
{selectedColor == colorMap.green && (
|
||||
<AntDesign name="check" size={30} color="white"/>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={() => setSelectedColor(colorMap.teal)}>
|
||||
<View style={styles.colorBox} backgroundColor={colorMap.teal}>
|
||||
{selectedColor == colorMap.teal && (
|
||||
<AntDesign name="check" size={30} color="white"/>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={() => setSelectedColor(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 text70>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>
|
||||
<View style={styles.card}>
|
||||
<Text text70>Add Calendar</Text>
|
||||
<View style={{marginTop: 20}}>
|
||||
<Button
|
||||
label={"Connect Google"}
|
||||
iconSource={() => (
|
||||
<View
|
||||
backgroundColor="#ededed"
|
||||
width={40}
|
||||
height={40}
|
||||
style={{borderRadius: 50}}
|
||||
marginR-10
|
||||
centerV
|
||||
centerH
|
||||
>
|
||||
<Ionicons name="logo-google" size={22} color="#979797"/>
|
||||
</View>
|
||||
)}
|
||||
backgroundColor="white"
|
||||
color="#464039"
|
||||
borderRadius={15}
|
||||
onPress={handleGoogleLogin}
|
||||
/>
|
||||
<Button
|
||||
label={"Connect Microsoft"}
|
||||
iconSource={() => (
|
||||
<View
|
||||
backgroundColor="#ededed"
|
||||
width={40}
|
||||
height={40}
|
||||
style={{borderRadius: 50}}
|
||||
marginR-10
|
||||
centerV
|
||||
centerH
|
||||
>
|
||||
<Ionicons name="logo-microsoft" size={22} color="#979797"/>
|
||||
</View>
|
||||
)}
|
||||
backgroundColor="white"
|
||||
color="#464039"
|
||||
borderRadius={15}
|
||||
onPress={handleMicrosoftSignIn}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.card}>
|
||||
<Text text70>Calendars</Text>
|
||||
<View style={{marginTop: 20}}>
|
||||
<Button
|
||||
label={"Sync Outlook"}
|
||||
iconSource={() => (
|
||||
<View
|
||||
backgroundColor="#ededed"
|
||||
width={40}
|
||||
height={40}
|
||||
style={{borderRadius: 50}}
|
||||
marginR-10
|
||||
centerV
|
||||
centerH
|
||||
>
|
||||
<Ionicons name="logo-microsoft" size={22} color="#979797"/>
|
||||
</View>
|
||||
)}
|
||||
backgroundColor="white"
|
||||
color="#464039"
|
||||
borderRadius={15}
|
||||
onPress={fetchAndSaveMicrosoftEvents}
|
||||
/>
|
||||
{profileData?.googleToken !== undefined && (
|
||||
<Button
|
||||
label={"Sync Google"}
|
||||
iconSource={() => (
|
||||
<View
|
||||
backgroundColor="#ededed"
|
||||
width={40}
|
||||
height={40}
|
||||
style={{borderRadius: 50}}
|
||||
marginR-10
|
||||
centerV
|
||||
centerH
|
||||
>
|
||||
<Ionicons name="logo-google" size={22} color="#979797"/>
|
||||
</View>
|
||||
)}
|
||||
backgroundColor="white"
|
||||
color="#464039"
|
||||
borderRadius={15}
|
||||
onPress={fetchAndSaveGoogleEvents}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
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)
|
||||
);
|
||||
|
||||
fetchGoogleCalendarEvents(
|
||||
profileData?.googleToken,
|
||||
timeMin.toISOString().slice(0, -5) + "Z",
|
||||
timeMax.toISOString().slice(0, -5) + "Z"
|
||||
).then((response) => {
|
||||
response?.forEach((item) => saveData(item));
|
||||
});
|
||||
};
|
||||
|
||||
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
|
||||
)
|
||||
);
|
||||
|
||||
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 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 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"
|
||||
)}`,
|
||||
});
|
||||
|
||||
console.log("Token response status:", tokenResponse.status);
|
||||
|
||||
if (!tokenResponse.ok) {
|
||||
console.error("Token exchange failed:", await tokenResponse.text());
|
||||
return;
|
||||
}
|
||||
|
||||
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 />
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
<Button
|
||||
onPress={fetchAndSaveMicrosoftEvents}
|
||||
label="Sync Outlook"
|
||||
labelStyle={styles.addCalLbl}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<OutlookIcon />
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
backBtn: {
|
||||
backgroundColor: "red",
|
||||
marginLeft: -2,
|
||||
justifyContent: "flex-start",
|
||||
},
|
||||
card: {
|
||||
backgroundColor: "white",
|
||||
width: "100%",
|
||||
padding: 20,
|
||||
paddingBottom: 30,
|
||||
marginTop: 20,
|
||||
borderRadius: 20,
|
||||
},
|
||||
colorBox: {
|
||||
aspectRatio: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: 50,
|
||||
borderRadius: 12,
|
||||
},
|
||||
checkbox: {
|
||||
borderRadius: 50,
|
||||
},
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
export default CalendarSettingsPage;
|
||||
|
||||
@ -10,20 +10,28 @@ const ChoreRewardSettings = (props: {
|
||||
}) => {
|
||||
return (
|
||||
<ToDosContextProvider>
|
||||
<View marginT-10>
|
||||
<View marginT-10 marginH-20>
|
||||
<ScrollView>
|
||||
<TouchableOpacity onPress={() => props.setSelectedPage(0)}>
|
||||
<View row marginT-20 marginL-20 marginB-35 centerV>
|
||||
<Ionicons name="chevron-back" size={22} color="#979797" />
|
||||
<Text text70 color="#979797">
|
||||
<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 text60R marginL-30 marginB-20>
|
||||
<Text text60R marginB-20>
|
||||
Chore Reward Settings
|
||||
</Text>
|
||||
<ToDosList />
|
||||
<ToDosList isSettings />
|
||||
</ScrollView>
|
||||
</View>
|
||||
</ToDosContextProvider>
|
||||
|
||||
@ -6,15 +6,10 @@ 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";
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
mainBtn: {
|
||||
width: "100%",
|
||||
justifyContent: "flex-start",
|
||||
marginBottom: 20,
|
||||
height: 60,
|
||||
},
|
||||
});
|
||||
const pageIndex = {
|
||||
main: 0,
|
||||
user: 1,
|
||||
@ -31,30 +26,22 @@ const SettingsPage = () => {
|
||||
<Button
|
||||
backgroundColor="white"
|
||||
style={styles.mainBtn}
|
||||
label="Manage my profile"
|
||||
label="Manage My Profile"
|
||||
labelStyle={styles.label}
|
||||
color="#07b8c7"
|
||||
iconSource={() => (
|
||||
<Ionicons
|
||||
name="person-circle-sharp"
|
||||
size={24}
|
||||
color="#07b8c7"
|
||||
style={{ marginRight: 10 }}
|
||||
/>
|
||||
<ProfileIcon style={{marginRight: 10}} color="#07b9c8" />
|
||||
)}
|
||||
onPress={() => setSelectedPage(pageIndex.user)}
|
||||
/>
|
||||
<Button
|
||||
backgroundColor="white"
|
||||
style={styles.mainBtn}
|
||||
label="Calendar settings"
|
||||
label="Calendar Settings"
|
||||
labelStyle={styles.label}
|
||||
color="#fd1775"
|
||||
iconSource={() => (
|
||||
<Ionicons
|
||||
name="home-outline"
|
||||
size={24}
|
||||
color="#fd1775"
|
||||
style={{ marginRight: 10 }}
|
||||
/>
|
||||
<CalendarIcon style={{marginRight: 10}}/>
|
||||
)}
|
||||
onPress={() => {
|
||||
setSelectedPage(pageIndex.calendar);
|
||||
@ -63,7 +50,8 @@ const SettingsPage = () => {
|
||||
<Button
|
||||
backgroundColor="white"
|
||||
style={styles.mainBtn}
|
||||
label="To Do reward settings"
|
||||
label="To-Do Reward Settings"
|
||||
labelStyle={styles.label}
|
||||
color="#ff9900"
|
||||
iconSource={() => (
|
||||
<Octicons
|
||||
@ -78,14 +66,10 @@ const SettingsPage = () => {
|
||||
<Button
|
||||
backgroundColor="white"
|
||||
style={styles.mainBtn}
|
||||
label="Kaly privacy policy"
|
||||
label="Cally Privacy Policy"
|
||||
labelStyle={styles.label}
|
||||
iconSource={() => (
|
||||
<Entypo
|
||||
name="text-document"
|
||||
size={24}
|
||||
color="#6c645b"
|
||||
style={{ marginRight: 10 }}
|
||||
/>
|
||||
<PrivacyPolicyIcon style={{marginRight: 10}}/>
|
||||
)}
|
||||
color="#6c645b"
|
||||
/>
|
||||
@ -105,3 +89,17 @@ const SettingsPage = () => {
|
||||
};
|
||||
|
||||
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'
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,83 +1,99 @@
|
||||
import {Text, TouchableOpacity, View} from "react-native-ui-lib";
|
||||
import React, {useState} from "react";
|
||||
import {Ionicons} from "@expo/vector-icons";
|
||||
import {ScrollView, StyleSheet,} from "react-native";
|
||||
import { Text, TouchableOpacity, View } from "react-native-ui-lib";
|
||||
import React, { useState } from "react";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { ScrollView, StyleSheet } from "react-native";
|
||||
import MyProfile from "./user_settings_views/MyProfile";
|
||||
import MyGroup from "./user_settings_views/MyGroup";
|
||||
|
||||
const UserSettings = (props: { setSelectedPage: (page: number) => void }) => {
|
||||
const [selectedView, setSelectedView] = useState<boolean>(true);
|
||||
return (
|
||||
<View flexG>
|
||||
<ScrollView style={{paddingBottom: 20, minHeight: "100%"}}>
|
||||
<TouchableOpacity onPress={() => props.setSelectedPage(0)}>
|
||||
<View row marginT-20 marginL-20 marginB-35 centerV>
|
||||
<Ionicons name="chevron-back" size={22} color="#979797"/>
|
||||
<Text text70 color="#979797">
|
||||
Return to main settings
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<View marginH-20 flexG style={{minHeight: "90%"}}>
|
||||
<Text text60R marginB-25>
|
||||
User Management
|
||||
</Text>
|
||||
<View style={styles.buttonSwitch} spread row>
|
||||
<TouchableOpacity
|
||||
onPress={() => setSelectedView(true)}
|
||||
centerV
|
||||
centerH
|
||||
style={
|
||||
selectedView == true ? styles.btnSelected : styles.btnNot
|
||||
}
|
||||
>
|
||||
<View>
|
||||
<Text text70 color={selectedView ? "white" : "black"}>
|
||||
My Profile
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={() => setSelectedView(false)}
|
||||
centerV
|
||||
centerH
|
||||
style={
|
||||
selectedView == false ? styles.btnSelected : styles.btnNot
|
||||
}
|
||||
>
|
||||
<View>
|
||||
<Text text70 color={!selectedView ? "white" : "black"}>
|
||||
My Group
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
{selectedView && <MyProfile/>}
|
||||
{!selectedView && <MyGroup/>}
|
||||
</View>
|
||||
</ScrollView>
|
||||
const [selectedView, setSelectedView] = useState<boolean>(true);
|
||||
return (
|
||||
<View flexG>
|
||||
<ScrollView style={{ paddingBottom: 20, minHeight: "100%" }}>
|
||||
<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>
|
||||
<View marginH-20 flexG style={{ minHeight: "90%" }}>
|
||||
<Text text60R marginB-25>
|
||||
User Management
|
||||
</Text>
|
||||
<View style={styles.buttonSwitch} spread row>
|
||||
<TouchableOpacity
|
||||
onPress={() => setSelectedView(true)}
|
||||
centerV
|
||||
centerH
|
||||
style={selectedView == true ? styles.btnSelected : styles.btnNot}
|
||||
>
|
||||
<View>
|
||||
<Text
|
||||
style={styles.btnTxt}
|
||||
color={selectedView ? "white" : "black"}
|
||||
>
|
||||
My Profile
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={() => setSelectedView(false)}
|
||||
centerV
|
||||
centerH
|
||||
style={selectedView == false ? styles.btnSelected : styles.btnNot}
|
||||
>
|
||||
<View>
|
||||
<Text
|
||||
style={styles.btnTxt}
|
||||
color={!selectedView ? "white" : "black"}
|
||||
>
|
||||
My Group
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
{selectedView && <MyProfile />}
|
||||
{!selectedView && <MyGroup />}
|
||||
</View>
|
||||
);
|
||||
</ScrollView>
|
||||
|
||||
{!selectedView && (
|
||||
<View>
|
||||
<Text>selview</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
buttonSwitch: {
|
||||
borderRadius: 50,
|
||||
width: "100%",
|
||||
backgroundColor: "#ebebeb",
|
||||
height: 45,
|
||||
},
|
||||
btnSelected: {
|
||||
backgroundColor: "#05a8b6",
|
||||
height: "100%",
|
||||
width: "50%",
|
||||
borderRadius: 50,
|
||||
},
|
||||
btnNot: {
|
||||
height: "100%",
|
||||
width: "50%",
|
||||
borderRadius: 50,
|
||||
},
|
||||
buttonSwitch: {
|
||||
borderRadius: 50,
|
||||
width: "100%",
|
||||
backgroundColor: "#ebebeb",
|
||||
height: 45,
|
||||
},
|
||||
btnSelected: {
|
||||
backgroundColor: "#05a8b6",
|
||||
height: "100%",
|
||||
width: "50%",
|
||||
borderRadius: 50,
|
||||
},
|
||||
btnTxt: {
|
||||
fontFamily: "Manrope_500Medium",
|
||||
fontSize: 15,
|
||||
},
|
||||
btnNot: {
|
||||
height: "100%",
|
||||
width: "50%",
|
||||
borderRadius: 50,
|
||||
},
|
||||
title: { fontFamily: "Manrope_600SemiBold", fontSize: 18 },
|
||||
});
|
||||
|
||||
export default UserSettings;
|
||||
|
||||
@ -10,7 +10,7 @@ import {
|
||||
Text,
|
||||
TextField,
|
||||
TouchableOpacity,
|
||||
View
|
||||
View,
|
||||
} from "react-native-ui-lib";
|
||||
import React, {useState} from "react";
|
||||
import {ScrollView, StyleSheet} from "react-native";
|
||||
@ -19,53 +19,75 @@ import {useCreateSubUser} from "@/hooks/firebase/useCreateSubUser";
|
||||
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";
|
||||
|
||||
const MyGroup = () => {
|
||||
const [showAddUserDialog, setShowAddUserDialog] = useState(false);
|
||||
const [showNewUserInfoDialog, setShowNewUserInfoDialog] = useState(false);
|
||||
const [selectedStatus, setSelectedStatus] = useState<string | PickerSingleValue>(ProfileType.CHILD);
|
||||
const [firstName, setFirstName] = useState('');
|
||||
const [lastName, setLastName] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [selectedStatus, setSelectedStatus] = useState<
|
||||
string | PickerSingleValue
|
||||
>(ProfileType.CHILD);
|
||||
const [firstName, setFirstName] = useState("");
|
||||
const [lastName, setLastName] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
|
||||
const [showQRCodeDialog, setShowQRCodeDialog] = useState("");
|
||||
|
||||
const {mutateAsync: createSubUser, isLoading, isError} = useCreateSubUser();
|
||||
const {data: familyMembers} = useGetFamilyMembers(true);
|
||||
|
||||
const parents = familyMembers?.filter(x => x.userType === ProfileType.PARENT) ?? [];
|
||||
const children = familyMembers?.filter(x => x.userType === ProfileType.CHILD) ?? [];
|
||||
const caregivers = familyMembers?.filter(x => x.userType === ProfileType.CAREGIVER) ?? [];
|
||||
const parents =
|
||||
familyMembers?.filter((x) => x.userType === ProfileType.PARENT) ?? [];
|
||||
const children =
|
||||
familyMembers?.filter((x) => x.userType === ProfileType.CHILD) ?? [];
|
||||
const caregivers =
|
||||
familyMembers?.filter((x) => x.userType === ProfileType.CAREGIVER) ?? [];
|
||||
const familyDevices =
|
||||
familyMembers?.filter((x) => x.userType === ProfileType.FAMILY_DEVICE) ?? [];
|
||||
|
||||
const handleCreateSubUser = async () => {
|
||||
if (!firstName || !lastName || !email) {
|
||||
console.error("All fields are required");
|
||||
if (!firstName || (selectedStatus !== ProfileType.FAMILY_DEVICE && !lastName)) {
|
||||
console.error("First name and last name are required");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!email.includes("@")) {
|
||||
if (selectedStatus !== ProfileType.FAMILY_DEVICE && !email) {
|
||||
console.error("Email is required for non-family device users");
|
||||
return;
|
||||
}
|
||||
|
||||
if (email && !email.includes("@")) {
|
||||
console.error("Invalid email address");
|
||||
return;
|
||||
}
|
||||
|
||||
await createSubUser({
|
||||
const res = await createSubUser({
|
||||
firstName,
|
||||
lastName,
|
||||
email,
|
||||
password: email,
|
||||
userType: selectedStatus as ProfileType
|
||||
lastName: selectedStatus === ProfileType.FAMILY_DEVICE ? "" : lastName,
|
||||
email: email || `placeholder_${uuidv4().split("-")[0]}@family.device`,
|
||||
password: uuidv4(),
|
||||
userType: selectedStatus as ProfileType,
|
||||
});
|
||||
console.log(res)
|
||||
|
||||
if (!isError) {
|
||||
setShowNewUserInfoDialog(false);
|
||||
|
||||
if(res?.data?.userId) {
|
||||
setTimeout(() => {
|
||||
setShowQRCodeDialog(res.data.userId)
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
console.log(familyMembers)
|
||||
|
||||
return (
|
||||
<View style={{flex: 1, height: "100%"}}>
|
||||
<View style={{flex: 1}}>
|
||||
<View>
|
||||
<ScrollView style={styles.card}>
|
||||
{(!parents.length && !children.length && !caregivers.length) && (
|
||||
{!parents.length && !children.length && !caregivers.length && (
|
||||
<Text text70 marginV-10>
|
||||
{isLoading ? "Loading...." : "No user devices added"}
|
||||
</Text>
|
||||
@ -73,26 +95,38 @@ const MyGroup = () => {
|
||||
|
||||
{(!!parents.length || !!children.length) && (
|
||||
<>
|
||||
<Text text70 marginV-10>
|
||||
<Text style={styles.subTit} marginV-10>
|
||||
Family
|
||||
</Text>
|
||||
{[...parents, ...children]?.map(member => (
|
||||
<Card enableShadow={false} elevation={0} key={`${member.firstName}_${member.lastName}`}
|
||||
style={styles.familyCard} row centerV padding-10>
|
||||
{[...parents, ...children]?.map((member, index) => (
|
||||
<Card
|
||||
enableShadow={false}
|
||||
elevation={0}
|
||||
key={`${member.firstName}_${member.lastName}_${index}`}
|
||||
style={styles.familyCard}
|
||||
row
|
||||
centerV
|
||||
padding-10
|
||||
>
|
||||
<Avatar
|
||||
source={{uri: 'https://via.placeholder.com/60'}}
|
||||
source={{uri: "https://via.placeholder.com/60"}}
|
||||
size={40}
|
||||
backgroundColor={Colors.grey60}
|
||||
/>
|
||||
<View marginL-10>
|
||||
<Text text70M>{member.firstName} {member.lastName}</Text>
|
||||
<Text text90
|
||||
grey40>{member.userType === ProfileType.PARENT ? "Admin (You)" : "Child"}</Text>
|
||||
<Text text70M>
|
||||
{member.firstName} {member.lastName}
|
||||
</Text>
|
||||
<Text text90 grey40>
|
||||
{member.userType === ProfileType.PARENT
|
||||
? "Admin (You)"
|
||||
: "Child"}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View flex-1/>
|
||||
|
||||
<UserMenu userId={member?.uid!}/>
|
||||
<UserMenu setShowQRCodeDialog={(val) => setShowQRCodeDialog("")} showQRCodeDialog={showQRCodeDialog === member?.uid} userId={member?.uid!}/>
|
||||
</Card>
|
||||
))}
|
||||
</>
|
||||
@ -103,18 +137,66 @@ const MyGroup = () => {
|
||||
<Text text70 marginB-10 marginT-15>
|
||||
Caregivers
|
||||
</Text>
|
||||
{caregivers?.map(member => (
|
||||
<Card enableShadow={false} elevation={0} key={`${member.firstName}_${member.lastName}`}
|
||||
style={styles.familyCard} row centerV padding-10>
|
||||
{caregivers?.map((member) => (
|
||||
<Card
|
||||
enableShadow={false}
|
||||
elevation={0}
|
||||
key={`${member.firstName}_${member.lastName}`}
|
||||
style={styles.familyCard}
|
||||
row
|
||||
centerV
|
||||
padding-10
|
||||
>
|
||||
<Avatar
|
||||
source={{uri: 'https://via.placeholder.com/60'}}
|
||||
source={{uri: "https://via.placeholder.com/60"}}
|
||||
size={40}
|
||||
backgroundColor={Colors.grey60}
|
||||
/>
|
||||
<View marginL-10>
|
||||
<Text text70M>{member.firstName} {member.lastName}</Text>
|
||||
<Text text90 grey40>Caregiver</Text>
|
||||
<Text text70M>
|
||||
{member.firstName} {member.lastName}
|
||||
</Text>
|
||||
<Text text90 grey40>
|
||||
Caregiver
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<UserMenu setShowQRCodeDialog={(val) => setShowQRCodeDialog("")} showQRCodeDialog={showQRCodeDialog === member?.uid} userId={member?.uid!}/>
|
||||
</Card>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
{!!familyDevices.length && (
|
||||
<>
|
||||
<Text text70 marginB-10 marginT-15>
|
||||
Family Devices
|
||||
</Text>
|
||||
{familyDevices?.map((member, index) => (
|
||||
<Card
|
||||
enableShadow={false}
|
||||
elevation={0}
|
||||
key={`${member.firstName}_${member.lastName}_${index}`}
|
||||
style={styles.familyCard}
|
||||
row
|
||||
centerV
|
||||
padding-10
|
||||
>
|
||||
<Avatar
|
||||
source={{uri: "https://via.placeholder.com/60"}}
|
||||
size={40}
|
||||
backgroundColor={Colors.grey60}
|
||||
/>
|
||||
<View marginL-10>
|
||||
<Text text70M>
|
||||
{member.firstName}
|
||||
</Text>
|
||||
<Text text90 grey40>
|
||||
Family Device
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<UserMenu setShowQRCodeDialog={(val) => setShowQRCodeDialog("")} showQRCodeDialog={showQRCodeDialog === member?.uid} userId={member?.uid!}/>
|
||||
</Card>
|
||||
))}
|
||||
</>
|
||||
@ -122,8 +204,15 @@ const MyGroup = () => {
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
<FloatingButton fullWidth hideBackgroundOverlay visible
|
||||
button={{label: '+ Add a user device', onPress: () => setShowAddUserDialog(true)}}/>
|
||||
<FloatingButton
|
||||
fullWidth
|
||||
hideBackgroundOverlay
|
||||
visible
|
||||
button={{
|
||||
label: "+ Add a user device",
|
||||
onPress: () => setShowAddUserDialog(true),
|
||||
}}
|
||||
/>
|
||||
|
||||
<Dialog
|
||||
visible={showAddUserDialog}
|
||||
@ -131,21 +220,26 @@ const MyGroup = () => {
|
||||
panDirection={PanningProvider.Directions.DOWN}
|
||||
>
|
||||
<Card padding-25 gap-10>
|
||||
<Text>
|
||||
Add a new user device
|
||||
</Text>
|
||||
<Text>Add a new user device</Text>
|
||||
|
||||
<Button backgroundColor={"#FD1775"}><Text white>Show a QR Code</Text></Button>
|
||||
<Button backgroundColor={"#05A8B6"} onPress={() => {
|
||||
setShowAddUserDialog(false)
|
||||
setTimeout(() => {
|
||||
setShowNewUserInfoDialog(true)
|
||||
}, 500)
|
||||
}}><Text white>Enter email address</Text></Button>
|
||||
<Button backgroundColor={"#FD1775"}>
|
||||
<Text white>Show a QR Code</Text>
|
||||
</Button>
|
||||
<Button
|
||||
backgroundColor={"#05A8B6"}
|
||||
onPress={() => {
|
||||
setShowAddUserDialog(false);
|
||||
setTimeout(() => {
|
||||
setShowNewUserInfoDialog(true);
|
||||
}, 500);
|
||||
}}
|
||||
>
|
||||
<Text white>Enter email address</Text>
|
||||
</Button>
|
||||
|
||||
|
||||
<TouchableOpacity onPress={() => setShowAddUserDialog(false)} center><Text>Return to user
|
||||
settings</Text></TouchableOpacity>
|
||||
<TouchableOpacity onPress={() => setShowAddUserDialog(false)} center>
|
||||
<Text>Return to user settings</Text>
|
||||
</TouchableOpacity>
|
||||
</Card>
|
||||
</Dialog>
|
||||
|
||||
@ -156,20 +250,24 @@ const MyGroup = () => {
|
||||
>
|
||||
<Card padding-25 style={styles.dialogCard}>
|
||||
<View row spread>
|
||||
<Text text60M>
|
||||
New User Information
|
||||
</Text>
|
||||
<Text text60M>New User Information</Text>
|
||||
<TouchableOpacity onPress={() => setShowAddUserDialog(false)}>
|
||||
<Text>X</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<View row centerV gap-20 marginV-20>
|
||||
<Avatar imageStyle={{borderRadius: 10}} containerStyle={{borderRadius: 10,}} size={60}
|
||||
backgroundColor={Colors.grey60}/>
|
||||
<Avatar
|
||||
imageStyle={{borderRadius: 10}}
|
||||
containerStyle={{borderRadius: 10}}
|
||||
size={60}
|
||||
backgroundColor={Colors.grey60}
|
||||
/>
|
||||
<TouchableOpacity onPress={() => {
|
||||
}}>
|
||||
<Text style={{color: Colors.green10}}>Upload User Profile Photo</Text>
|
||||
<Text style={{color: Colors.green10}}>
|
||||
Upload User Profile Photo
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
@ -178,7 +276,7 @@ const MyGroup = () => {
|
||||
editable={!isLoading}
|
||||
value={selectedStatus}
|
||||
//@ts-ignore
|
||||
onChange={item => setSelectedStatus(item)}
|
||||
onChange={(item) => setSelectedStatus(item)}
|
||||
style={styles.picker}
|
||||
showSearch
|
||||
floatingPlaceholder
|
||||
@ -186,40 +284,50 @@ const MyGroup = () => {
|
||||
<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}>First Name</Text>
|
||||
<Text style={styles.label}>
|
||||
{selectedStatus === ProfileType.FAMILY_DEVICE ? "Device Name" : "First Name"}
|
||||
</Text>
|
||||
<TextField
|
||||
editable={!isLoading}
|
||||
placeholder="First name"
|
||||
placeholder={selectedStatus === ProfileType.FAMILY_DEVICE ? "Device name" : "First name"}
|
||||
value={firstName}
|
||||
onChangeText={setFirstName}
|
||||
style={styles.inputField}
|
||||
/>
|
||||
|
||||
<Text style={styles.label}>Last Name</Text>
|
||||
<TextField
|
||||
editable={!isLoading}
|
||||
placeholder="Last name"
|
||||
value={lastName}
|
||||
onChangeText={setLastName}
|
||||
style={styles.inputField}
|
||||
/>
|
||||
{selectedStatus !== ProfileType.FAMILY_DEVICE && (
|
||||
<>
|
||||
<Text style={styles.label}>Last Name</Text>
|
||||
<TextField
|
||||
editable={!isLoading}
|
||||
placeholder="Last name"
|
||||
value={lastName}
|
||||
onChangeText={setLastName}
|
||||
style={styles.inputField}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Text style={styles.label}>Email Address</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.label}>Email Address (Optional)</Text>
|
||||
<TextField
|
||||
editable={!isLoading}
|
||||
placeholder="Email address"
|
||||
value={email}
|
||||
onChangeText={setEmail}
|
||||
keyboardType="email-address"
|
||||
autoCapitalize="none"
|
||||
style={styles.inputField}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Button
|
||||
disabled={!firstName || !lastName || !email || isLoading}
|
||||
disabled={!firstName || (selectedStatus !== ProfileType.FAMILY_DEVICE && !lastName) || isLoading}
|
||||
label={isLoading ? "Adding..." : "Add group member"}
|
||||
backgroundColor="#FD1775"
|
||||
style={{marginTop: 20}}
|
||||
@ -243,7 +351,7 @@ const styles = StyleSheet.create({
|
||||
marginBottom: 10,
|
||||
borderRadius: 10,
|
||||
backgroundColor: Colors.white,
|
||||
width: "100%"
|
||||
width: "100%",
|
||||
},
|
||||
inputField: {
|
||||
borderRadius: 50,
|
||||
@ -275,6 +383,10 @@ const styles = StyleSheet.create({
|
||||
borderRadius: 10,
|
||||
gap: 10,
|
||||
},
|
||||
subTit: {
|
||||
fontFamily: "Manrope_500Medium",
|
||||
fontSize: 15,
|
||||
},
|
||||
});
|
||||
|
||||
export default MyGroup;
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { View, Text, TextField } from "react-native-ui-lib";
|
||||
import { Text, TextField, View } from "react-native-ui-lib";
|
||||
import React, { useState } from "react";
|
||||
import { StyleSheet } from "react-native";
|
||||
import { ImageBackground, StyleSheet } from "react-native";
|
||||
import { ScrollView } from "react-native-gesture-handler";
|
||||
import { useAuthContext } from "@/contexts/AuthContext";
|
||||
import { useSettingsContext } from "@/contexts/SettingsContext";
|
||||
import { useUpdateUserData } from "@/hooks/firebase/useUpdateUserData";
|
||||
|
||||
const MyProfile = () => {
|
||||
const { user, profileData } = useAuthContext();
|
||||
|
||||
@ -15,18 +15,22 @@ const MyProfile = () => {
|
||||
|
||||
const { mutateAsync: updateUserData } = useUpdateUserData();
|
||||
return (
|
||||
<ScrollView style={{paddingBottom: 100, flex: 1}}>
|
||||
<ScrollView style={{ paddingBottom: 100, flex: 1 }}>
|
||||
<View style={styles.card}>
|
||||
<Text text70>Your Profile</Text>
|
||||
<Text style={styles.subTit}>Your Profile</Text>
|
||||
<View row spread paddingH-15 centerV marginV-15>
|
||||
<View style={styles.pfp}></View>
|
||||
<Text text80 color="#50be0c">
|
||||
<ImageBackground
|
||||
style={styles.pfp}
|
||||
source={require("../../../../assets/images/profile-picture.png")}
|
||||
/>
|
||||
|
||||
<Text style={styles.photoSet} color="#50be0c">
|
||||
Change Photo
|
||||
</Text>
|
||||
<Text text80>Remove Photo</Text>
|
||||
<Text style={styles.photoSet}>Remove Photo</Text>
|
||||
</View>
|
||||
<View paddingH-15>
|
||||
<Text text80 marginT-10 marginB-7 color="#a1a1a1">
|
||||
<Text text80 marginT-10 marginB-7 style={styles.label}>
|
||||
First name
|
||||
</Text>
|
||||
<TextField
|
||||
@ -39,7 +43,7 @@ const MyProfile = () => {
|
||||
await updateUserData({ newUserData: { firstName: value } });
|
||||
}}
|
||||
/>
|
||||
<Text text80 marginT-10 marginB-7 color="#a1a1a1">
|
||||
<Text text80 marginT-10 marginB-7 style={styles.label}>
|
||||
Last name
|
||||
</Text>
|
||||
<TextField
|
||||
@ -52,7 +56,7 @@ const MyProfile = () => {
|
||||
await updateUserData({ newUserData: { lastName: value } });
|
||||
}}
|
||||
/>
|
||||
<Text text80 marginT-10 marginB-7 color="#a1a1a1">
|
||||
<Text text80 marginT-10 marginB-7 style={styles.label}>
|
||||
Email address
|
||||
</Text>
|
||||
<TextField
|
||||
@ -65,8 +69,8 @@ const MyProfile = () => {
|
||||
</View>
|
||||
|
||||
<View style={styles.card}>
|
||||
<Text text70>Settings</Text>
|
||||
<Text text80 marginT-20 marginB-7 color="#a1a1a1">
|
||||
<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} />
|
||||
@ -80,12 +84,13 @@ const styles = StyleSheet.create({
|
||||
marginVertical: 15,
|
||||
backgroundColor: "white",
|
||||
width: "100%",
|
||||
borderRadius: 15,
|
||||
padding: 20,
|
||||
borderRadius: 12,
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 21,
|
||||
},
|
||||
pfp: {
|
||||
aspectRatio: 1,
|
||||
width: 60,
|
||||
width: 65.54,
|
||||
backgroundColor: "green",
|
||||
borderRadius: 20,
|
||||
},
|
||||
@ -96,7 +101,22 @@ const styles = StyleSheet.create({
|
||||
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
|
||||
}
|
||||
});
|
||||
|
||||
export default MyProfile;
|
||||
|
||||
@ -3,15 +3,22 @@ import {Button, Card, Colors, Dialog, Hint, ListItem, Text, View} from 'react-na
|
||||
import QRCode from 'react-native-qrcode-svg';
|
||||
import {PanningDirectionsEnum} from "react-native-ui-lib/src/components/panningViews/panningProvider";
|
||||
|
||||
const UserMenu = ({userId}:{userId: string}) => {
|
||||
const UserMenu = ({
|
||||
userId,
|
||||
showQRCodeDialog,
|
||||
setShowQRCodeDialog
|
||||
}: {
|
||||
userId: string,
|
||||
showQRCodeDialog: boolean,
|
||||
setShowQRCodeDialog: (value: boolean) => void
|
||||
}) => {
|
||||
const [showHint, setShowHint] = useState(false);
|
||||
const [showQRCodeDialog, setShowQRCodeDialog] = useState(false);
|
||||
|
||||
const handleShowQRCode = () => {
|
||||
setShowHint(false);
|
||||
setTimeout(() => {
|
||||
setShowQRCodeDialog(true);
|
||||
}, 500)
|
||||
}, 500);
|
||||
};
|
||||
|
||||
return (
|
||||
@ -22,9 +29,7 @@ const UserMenu = ({userId}:{userId: string}) => {
|
||||
color={Colors.white}
|
||||
customContent={
|
||||
<View height={18}>
|
||||
<ListItem
|
||||
onPress={handleShowQRCode}
|
||||
>
|
||||
<ListItem onPress={handleShowQRCode}>
|
||||
<Text>Show Login QR Code</Text>
|
||||
</ListItem>
|
||||
</View>
|
||||
@ -34,7 +39,7 @@ const UserMenu = ({userId}:{userId: string}) => {
|
||||
backdropColor="transparent"
|
||||
>
|
||||
<View>
|
||||
<Button link onPress={() => setShowHint(x => !x)}>
|
||||
<Button link onPress={() => setShowHint(!showHint)}>
|
||||
<Text>...</Text>
|
||||
</Button>
|
||||
</View>
|
||||
@ -47,7 +52,7 @@ const UserMenu = ({userId}:{userId: string}) => {
|
||||
>
|
||||
<Card padding-20 center>
|
||||
<Text marginB-10>Scan this QR Code to Login:</Text>
|
||||
<QRCode value={userId} size={150} />
|
||||
<QRCode value={userId} size={150}/>
|
||||
<Button
|
||||
marginT-20
|
||||
label="Close"
|
||||
|
||||
@ -1,34 +1,14 @@
|
||||
import { StyleSheet } from "react-native";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
ButtonSize,
|
||||
View,
|
||||
Text,
|
||||
ActionSheet,
|
||||
TextField,
|
||||
Dialog,
|
||||
Slider,
|
||||
NumberInput,
|
||||
NumberInputData,
|
||||
DateTimePicker,
|
||||
Switch,
|
||||
Picker,
|
||||
} from "react-native-ui-lib";
|
||||
import { AntDesign, Feather, Ionicons } from "@expo/vector-icons";
|
||||
import React, { useState } from "react";
|
||||
import { Button, ButtonSize, Text, View } from "react-native-ui-lib";
|
||||
import { AntDesign } from "@expo/vector-icons";
|
||||
import LinearGradient from "react-native-linear-gradient";
|
||||
import { PanningDirectionsEnum } from "react-native-ui-lib/src/components/panningViews/panningProvider";
|
||||
import { repeatOptions, useToDosContext } from "@/contexts/ToDosContext";
|
||||
import { setDate } from "date-fns";
|
||||
import PointsSlider from "@/components/shared/PointsSlider";
|
||||
import AddChoreDialog from "./AddChoreDialog";
|
||||
|
||||
const AddChore = () => {
|
||||
|
||||
const [isVisible, setIsVisible] = useState<boolean>(false);
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<LinearGradient
|
||||
colors={["transparent", "#f9f8f7"]}
|
||||
@ -44,11 +24,11 @@ const AddChore = () => {
|
||||
>
|
||||
<AntDesign name="plus" size={24} color="white" />
|
||||
<Text white text60R marginL-10>
|
||||
Add to do
|
||||
Add To Do
|
||||
</Text>
|
||||
</Button>
|
||||
</View>
|
||||
<AddChoreDialog isVisible={isVisible} setIsVisible={setIsVisible} />
|
||||
<AddChoreDialog isVisible={isVisible} setIsVisible={setIsVisible} />
|
||||
</LinearGradient>
|
||||
);
|
||||
};
|
||||
|
||||
@ -11,20 +11,38 @@ import {
|
||||
ButtonSize,
|
||||
} from "react-native-ui-lib";
|
||||
import { PanningDirectionsEnum } from "react-native-ui-lib/src/incubator/panView";
|
||||
import { StyleSheet } from "react-native";
|
||||
import { Dimensions, StyleSheet } from "react-native";
|
||||
import DropModalIcon from "@/assets/svgs/DropModalIcon";
|
||||
import { IToDo } from "@/hooks/firebase/types/todoData";
|
||||
|
||||
interface IAddChoreDialog {
|
||||
isVisible: boolean;
|
||||
setIsVisible: (value: boolean) => void;
|
||||
selectedTodo?: IToDo;
|
||||
}
|
||||
const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
|
||||
const { addToDo, toDos } = useToDosContext();
|
||||
|
||||
const [newTitle, setNewTitle] = useState<string>("");
|
||||
const [points, setPoints] = useState<number>(10);
|
||||
const [choreDate, setChoreDate] = useState<Date | null>(new Date());
|
||||
const [rotate, setRotate] = useState<boolean>(false);
|
||||
const [repeatType, setRepeatType] = useState<string>("Every week");
|
||||
const defaultTodo = {
|
||||
id: "",
|
||||
title: "",
|
||||
points: 10,
|
||||
date: new Date(),
|
||||
rotate: false,
|
||||
repeatType: "Every week",
|
||||
};
|
||||
|
||||
const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
|
||||
const { addToDo, updateToDo } = useToDosContext();
|
||||
const [todo, setTodo] = useState<IToDo>(
|
||||
addChoreDialogProps.selectedTodo ?? defaultTodo
|
||||
);
|
||||
const { width, height } = Dimensions.get("screen");
|
||||
|
||||
const [points, setPoints] = useState<number>(todo.points);
|
||||
|
||||
const handleClose = () => {
|
||||
setTodo(defaultTodo);
|
||||
addChoreDialogProps.setIsVisible(false);
|
||||
};
|
||||
|
||||
const handleChange = (text: string) => {
|
||||
const numericValue = parseInt(text, 10);
|
||||
@ -35,19 +53,20 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
|
||||
setPoints(0);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
bottom={true}
|
||||
height={"90%"}
|
||||
width={width}
|
||||
panDirection={PanningDirectionsEnum.DOWN}
|
||||
onDismiss={() => addChoreDialogProps.setIsVisible(false)}
|
||||
onDismiss={() => handleClose}
|
||||
containerStyle={{
|
||||
borderRadius: 10,
|
||||
backgroundColor: "white",
|
||||
width: "100%",
|
||||
alignSelf: "stretch",
|
||||
padding: 0,
|
||||
paddingTop: 3,
|
||||
paddingTop: 4,
|
||||
margin: 0,
|
||||
}}
|
||||
visible={addChoreDialogProps.isVisible}
|
||||
@ -58,46 +77,43 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
|
||||
style={styles.topBtn}
|
||||
label="Cancel"
|
||||
onPress={() => {
|
||||
addChoreDialogProps.setIsVisible(false);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
style={styles.topBtn}
|
||||
iconSource={() => (
|
||||
<Feather name="chevron-down" size={24} color="black" />
|
||||
)}
|
||||
onPress={() => {
|
||||
addChoreDialogProps.setIsVisible(false);
|
||||
handleClose();
|
||||
}}
|
||||
/>
|
||||
<View marginT-12>
|
||||
<DropModalIcon
|
||||
onPress={() => {
|
||||
handleClose();
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<Button
|
||||
color="#05a8b6"
|
||||
style={styles.topBtn}
|
||||
label="Save"
|
||||
onPress={() => {
|
||||
try {
|
||||
addToDo({
|
||||
id: 0,
|
||||
title: newTitle,
|
||||
done: false,
|
||||
date: choreDate,
|
||||
points: points,
|
||||
rotate: rotate,
|
||||
repeatType: repeatType,
|
||||
});
|
||||
addChoreDialogProps.setIsVisible(false);
|
||||
console.log(toDos);
|
||||
if (addChoreDialogProps.selectedTodo) {
|
||||
updateToDo({ ...todo, points: points });
|
||||
} else {
|
||||
addToDo({
|
||||
...todo,
|
||||
done: false,
|
||||
points: points,
|
||||
});
|
||||
}
|
||||
handleClose();
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
console.error(error);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<TextField
|
||||
placeholder="Add a To Do"
|
||||
value={newTitle}
|
||||
value={todo?.title}
|
||||
onChangeText={(text) => {
|
||||
setNewTitle(text);
|
||||
setTodo((oldValue: IToDo) => ({ ...oldValue, title: text }));
|
||||
}}
|
||||
placeholderTextColor="#2d2d30"
|
||||
text60R
|
||||
@ -107,15 +123,15 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
|
||||
<View style={styles.divider} marginT-8 />
|
||||
<View marginL-30 centerV>
|
||||
<View row marginB-10>
|
||||
{choreDate && (
|
||||
{todo?.date && (
|
||||
<View row centerV>
|
||||
<Feather name="calendar" size={25} color="#919191" />
|
||||
<DateTimePicker
|
||||
value={choreDate}
|
||||
value={todo.date}
|
||||
text70
|
||||
marginL-8
|
||||
onChange={(date) => {
|
||||
setChoreDate(date);
|
||||
setTodo((oldValue: IToDo) => ({ ...oldValue, date: date }));
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
@ -126,15 +142,21 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
|
||||
<Picker
|
||||
marginL-8
|
||||
placeholder="Select Repeat Type"
|
||||
value={repeatType}
|
||||
value={todo?.repeatType}
|
||||
onChange={(value) => {
|
||||
if (value) {
|
||||
if (value.toString() == "None") {
|
||||
setChoreDate(null);
|
||||
setRepeatType("None");
|
||||
setTodo((oldValue) => ({
|
||||
...oldValue,
|
||||
date: null,
|
||||
repeatType: "None",
|
||||
}));
|
||||
} else {
|
||||
setRepeatType(value.toString());
|
||||
setChoreDate(new Date());
|
||||
setTodo((oldValue) => ({
|
||||
...oldValue,
|
||||
date: new Date(),
|
||||
repeatType: value.toString(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
}}
|
||||
@ -199,9 +221,11 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
|
||||
<Text text80>Take Turns</Text>
|
||||
<Switch
|
||||
onColor={"#ea156c"}
|
||||
value={rotate}
|
||||
value={todo.rotate}
|
||||
marginL-10
|
||||
onValueChange={(value) => setRotate(value)}
|
||||
onValueChange={(value) =>
|
||||
setTodo((oldValue) => ({ ...oldValue, rotate: value }))
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.divider} />
|
||||
|
||||
@ -3,6 +3,7 @@ import React from "react";
|
||||
import { Fontisto } from "@expo/vector-icons";
|
||||
import { ProgressBar } from "react-native-ui-lib/src/components/progressBar";
|
||||
import { useToDosContext } from "@/contexts/ToDosContext";
|
||||
import FireworksOrangeIcon from "@/assets/svgs/FireworksOrangeIcon";
|
||||
|
||||
const ProgressCard = ({children}: {children?: React.ReactNode}) => {
|
||||
const { maxPoints } = useToDosContext();
|
||||
@ -14,8 +15,8 @@ const ProgressCard = ({children}: {children?: React.ReactNode}) => {
|
||||
style={{ borderRadius: 22 }}
|
||||
>
|
||||
<View row centerV>
|
||||
<Fontisto name="day-sunny" size={30} color="#ff9900" />
|
||||
<Text marginL-5 text70>
|
||||
<FireworksOrangeIcon />
|
||||
<Text marginL-15 text70 style={{fontFamily: 'Manrope_600SemiBold', fontSize: 14}}>
|
||||
You have earned XX points this week!{" "}
|
||||
</Text>
|
||||
</View>
|
||||
@ -30,8 +31,8 @@ const ProgressCard = ({children}: {children?: React.ReactNode}) => {
|
||||
}}
|
||||
/>
|
||||
<View row spread>
|
||||
<Text color={"#868686"}>0</Text>
|
||||
<Text color={"#868686"}>{maxPoints}</Text>
|
||||
<Text style={{fontSize: 13, color: '#858585'}}>0</Text>
|
||||
<Text style={{fontSize: 13, color: '#858585'}}>{maxPoints}</Text>
|
||||
</View>
|
||||
<View centerV centerH>
|
||||
{children}
|
||||
|
||||
@ -1,65 +1,157 @@
|
||||
import { View, Text, Checkbox } from "react-native-ui-lib";
|
||||
import React from "react";
|
||||
import { IToDo, useToDosContext } from "@/contexts/ToDosContext";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
Checkbox,
|
||||
TouchableOpacity,
|
||||
Dialog,
|
||||
Button,
|
||||
ButtonSize,
|
||||
} from "react-native-ui-lib";
|
||||
import React, { useState } from "react";
|
||||
import { useToDosContext } from "@/contexts/ToDosContext";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import PointsSlider from "@/components/shared/PointsSlider";
|
||||
import { IToDo } from "@/hooks/firebase/types/todoData";
|
||||
import { ImageBackground } from "react-native";
|
||||
import AddChoreDialog from "@/components/pages/todos/AddChoreDialog";
|
||||
|
||||
const ToDoItem = (props: { item: IToDo }) => {
|
||||
const ToDoItem = (props: { item: IToDo; isSettings?: boolean }) => {
|
||||
const { updateToDo } = useToDosContext();
|
||||
const [editing, setEditing] = useState<boolean>(false);
|
||||
const [points, setPoints] = useState(props.item.points);
|
||||
const [pointsModalVisible, setPointsModalVisible] = useState<boolean>(false);
|
||||
|
||||
const handlePointsChange = (text: string) => {
|
||||
const numericValue = parseInt(text, 10);
|
||||
|
||||
if (!isNaN(numericValue) && numericValue >= 0 && numericValue <= 100) {
|
||||
setPoints(numericValue);
|
||||
} else if (text === "") {
|
||||
setPoints(0);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<View
|
||||
centerV
|
||||
paddingV-10
|
||||
paddingH-10
|
||||
paddingH-13
|
||||
marginV-10
|
||||
style={{
|
||||
borderRadius: 22,
|
||||
borderRadius: 17,
|
||||
backgroundColor: props.item.done ? "#e0e0e0" : "white",
|
||||
opacity: props.item.done ? 0.3 : 1,
|
||||
}}
|
||||
>
|
||||
<View paddingB-5 row spread>
|
||||
<Text
|
||||
text70R
|
||||
<AddChoreDialog isVisible={editing} setIsVisible={setEditing} selectedTodo={props.item}/>
|
||||
<View paddingB-8 row spread>
|
||||
<Text
|
||||
text70
|
||||
style={{
|
||||
textDecorationLine: props.item.done ? "line-through" : "none",
|
||||
textDecorationLine: props.item.done ? "line-through" : "none",
|
||||
fontFamily: "Manrope_500Medium",
|
||||
fontSize: 15,
|
||||
}}
|
||||
>
|
||||
onPress={() => {
|
||||
setEditing(true);
|
||||
}}
|
||||
>
|
||||
{props.item.title}
|
||||
</Text>
|
||||
</Text>
|
||||
<Checkbox
|
||||
value={props.item.done}
|
||||
containerStyle={{borderWidth: 0.7, borderRadius: 50, borderColor: 'gray', height: 24.64, width: 24.64}}
|
||||
color="#fd1575"
|
||||
onValueChange={(value) => {
|
||||
updateToDo(props.item.id, { done: !props.item.done });
|
||||
updateToDo({ id: props.item.id, done: !props.item.done });
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View centerH paddingV-5>
|
||||
<View centerH paddingV-0>
|
||||
<View
|
||||
centerV
|
||||
height={2}
|
||||
height={0.7}
|
||||
width={"100%"}
|
||||
style={{
|
||||
backgroundColor: props.item.done ? '#b8b8b8' : "#e7e7e7",
|
||||
backgroundColor: props.item.done ? "#b8b8b8" : "#e7e7e7",
|
||||
}}
|
||||
centerH
|
||||
/>
|
||||
</View>
|
||||
<View centerH row spread>
|
||||
<View centerV centerH marginT-8 row spread>
|
||||
{props.item.points && props.item.points > 0 ? (
|
||||
<View centerV row>
|
||||
<Ionicons name="gift-outline" size={20} color="#46a80a" />
|
||||
<Text color="#46a80a">{props.item.points} points</Text>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
if (props.isSettings) {
|
||||
setPointsModalVisible(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<View centerV row gap-3>
|
||||
<Ionicons name="gift-outline" size={20} color="#46a80a" />
|
||||
<Text color="#46a80a" style={{ fontSize: 12, fontFamily: "Manrope_500Medium" }}>
|
||||
{props.item.points} points
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
<View />
|
||||
)}
|
||||
<View
|
||||
height={25}
|
||||
width={25}
|
||||
backgroundColor="red"
|
||||
style={{ borderRadius: 50 }}
|
||||
<ImageBackground
|
||||
source={require("../../../assets/images/child-picture.png")}
|
||||
style={{
|
||||
height: 24.64,
|
||||
aspectRatio: 1,
|
||||
borderRadius: 22,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<Dialog
|
||||
visible={pointsModalVisible}
|
||||
onDismiss={() => setPointsModalVisible(false)}
|
||||
containerStyle={{
|
||||
padding: 20,
|
||||
borderRadius: 15,
|
||||
backgroundColor: "white",
|
||||
}}
|
||||
children={
|
||||
<View gap-20>
|
||||
<View row centerV marginL-30>
|
||||
<Ionicons name="gift-outline" size={22} color="#919191" />
|
||||
<Text marginH-10 text70>
|
||||
Reward points
|
||||
</Text>
|
||||
</View>
|
||||
{points && (
|
||||
<PointsSlider
|
||||
points={points}
|
||||
setPoints={setPoints}
|
||||
handleChange={handlePointsChange}
|
||||
/>
|
||||
)}
|
||||
<View row marginH-30 spread>
|
||||
<Button
|
||||
size={ButtonSize.large}
|
||||
label="Cancel"
|
||||
backgroundColor="#d9d9d9"
|
||||
onPress={() => {
|
||||
setPointsModalVisible(false);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
size={ButtonSize.large}
|
||||
label="Save points"
|
||||
backgroundColor="#fd1775"
|
||||
style={{ height: 60, width: 150 }}
|
||||
onPress={() => {
|
||||
updateToDo({ id: props.item.id, points: points });
|
||||
setPointsModalVisible(false);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import { View, Text } from "react-native-ui-lib";
|
||||
import React from "react";
|
||||
import { IToDo, useToDosContext } from "@/contexts/ToDosContext";
|
||||
import { View, Text, TouchableOpacity, Icon } from "react-native-ui-lib";
|
||||
import React, { useState } from "react";
|
||||
import { useToDosContext } from "@/contexts/ToDosContext";
|
||||
import ToDoItem from "./ToDoItem";
|
||||
import { format, isToday, isTomorrow } from "date-fns";
|
||||
import { format, isToday, isTomorrow } from "date-fns";
|
||||
import { AntDesign } from "@expo/vector-icons";
|
||||
import { IToDo } from "@/hooks/firebase/types/todoData";
|
||||
|
||||
const groupToDosByDate = (toDos: IToDo[]) => {
|
||||
return toDos.reduce((groups, toDo) => {
|
||||
let sortedTodos = toDos.sort((a, b) => a.date - b.date);
|
||||
return sortedTodos.reduce((groups, toDo) => {
|
||||
let dateKey;
|
||||
|
||||
if (toDo.date === null) {
|
||||
@ -27,27 +30,73 @@ const groupToDosByDate = (toDos: IToDo[]) => {
|
||||
}, {} as { [key: string]: IToDo[] });
|
||||
};
|
||||
|
||||
const ToDosList = () => {
|
||||
const ToDosList = ({ isSettings }: { isSettings?: boolean }) => {
|
||||
const { toDos } = useToDosContext();
|
||||
const groupedToDos = groupToDosByDate(toDos);
|
||||
|
||||
const [expandedGroups, setExpandedGroups] = useState<{
|
||||
[key: string]: boolean;
|
||||
}>({});
|
||||
|
||||
const [expandNoDate, setExpandNoDate] = useState<boolean>(true);
|
||||
|
||||
const toggleExpand = (dateKey: string) => {
|
||||
setExpandedGroups((prev) => ({
|
||||
...prev,
|
||||
[dateKey]: !prev[dateKey],
|
||||
}));
|
||||
};
|
||||
|
||||
const noDateToDos = groupedToDos["No Date"] || [];
|
||||
const datedToDos = Object.keys(groupedToDos).filter((key) => key !== "No Date");
|
||||
const datedToDos = Object.keys(groupedToDos).filter(
|
||||
(key) => key !== "No Date"
|
||||
);
|
||||
|
||||
return (
|
||||
<View marginB-140>
|
||||
|
||||
<View marginB-402>
|
||||
{noDateToDos.length > 0 && (
|
||||
<View key="No Date">
|
||||
{noDateToDos
|
||||
.sort((a, b) => Number(a.done) - Number(b.done))
|
||||
.map((item) => (
|
||||
<ToDoItem key={item.id} item={item} />
|
||||
))}
|
||||
<View row spread paddingH-19 marginB-12>
|
||||
<Text
|
||||
text70
|
||||
style={{
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
Unscheduled
|
||||
</Text>
|
||||
{!expandNoDate && (
|
||||
<AntDesign
|
||||
name="caretright"
|
||||
size={24}
|
||||
color="#fd1575"
|
||||
onPress={() => {
|
||||
setExpandNoDate(!expandNoDate);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{expandNoDate && (
|
||||
<AntDesign
|
||||
name="caretdown"
|
||||
size={24}
|
||||
color="#fd1575"
|
||||
onPress={() => {
|
||||
setExpandNoDate(!expandNoDate);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
{expandNoDate &&
|
||||
noDateToDos
|
||||
.sort((a, b) => Number(a.done) - Number(b.done))
|
||||
.map((item) => (
|
||||
<ToDoItem isSettings={isSettings} key={item.id} item={item} />
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{datedToDos.map((dateKey) => {
|
||||
const isExpanded = expandedGroups[dateKey] || false;
|
||||
const sortedToDos = [
|
||||
...groupedToDos[dateKey].filter((toDo) => !toDo.done),
|
||||
...groupedToDos[dateKey].filter((toDo) => toDo.done),
|
||||
@ -55,19 +104,38 @@ const ToDosList = () => {
|
||||
|
||||
return (
|
||||
<View key={dateKey}>
|
||||
<Text
|
||||
text70
|
||||
<TouchableOpacity
|
||||
onPress={() => toggleExpand(dateKey)}
|
||||
style={{
|
||||
fontWeight: "bold",
|
||||
marginVertical: 8,
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 20,
|
||||
marginVertical: 8,
|
||||
}}
|
||||
>
|
||||
{dateKey}
|
||||
</Text>
|
||||
{sortedToDos.map((item) => (
|
||||
<ToDoItem key={item.id} item={item} />
|
||||
))}
|
||||
<Text
|
||||
text70
|
||||
style={{
|
||||
fontFamily: "Manrope_700Bold",
|
||||
fontSize: 15,
|
||||
color: "#484848",
|
||||
}}
|
||||
>
|
||||
{dateKey}
|
||||
</Text>
|
||||
{!isExpanded && (
|
||||
<AntDesign name="caretright" size={24} color="#fd1575" />
|
||||
)}
|
||||
{isExpanded && (
|
||||
<AntDesign name="caretdown" size={24} color="#fd1575" />
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
|
||||
{isExpanded &&
|
||||
sortedToDos.map((item) => (
|
||||
<ToDoItem isSettings={isSettings} key={item.id} item={item} />
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
|
||||
@ -4,7 +4,7 @@ import HeaderTemplate from "@/components/shared/HeaderTemplate";
|
||||
import AddChore from "./AddChore";
|
||||
import ProgressCard from "./ProgressCard";
|
||||
import ToDosList from "./ToDosList";
|
||||
import { ScrollView } from "react-native";
|
||||
import { Dimensions, ScrollView } from "react-native";
|
||||
import { StyleSheet } from "react-native";
|
||||
import { TouchableOpacity } from "react-native-gesture-handler";
|
||||
import { ProfileType, useAuthContext } from "@/contexts/AuthContext";
|
||||
@ -14,13 +14,16 @@ import UserChoresProgress from "./user-chores/UserChoresProgress";
|
||||
const ToDosPage = () => {
|
||||
const [pageIndex, setPageIndex] = useState<number>(0);
|
||||
const { profileData } = useAuthContext();
|
||||
const { width, height } = Dimensions.get("screen");
|
||||
const pageLink = (
|
||||
<TouchableOpacity onPress={() => setPageIndex(1)}>
|
||||
<Text color="#ea156d">View family progress</Text>
|
||||
<Text color="#ea156d" style={{ fontSize: 14 }}>
|
||||
View family progress
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
return (
|
||||
<View paddingH-25 backgroundColor="#f9f8f7" height={"100%"}>
|
||||
<View paddingH-25 backgroundColor="#f9f8f7" height={"100%"} width={width}>
|
||||
{pageIndex == 0 && (
|
||||
<View>
|
||||
<ScrollView
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { View, Text } from "react-native-ui-lib";
|
||||
import React from "react";
|
||||
import { StyleSheet } from "react-native";
|
||||
import { ImageBackground, StyleSheet } from "react-native";
|
||||
import FamilyChart from "./FamilyChart";
|
||||
import { TouchableOpacity } from "react-native-ui-lib/src/incubator";
|
||||
|
||||
@ -12,34 +12,116 @@ const FamilyChoresProgress = ({
|
||||
return (
|
||||
<View marginT-20 marginH-5>
|
||||
<TouchableOpacity onPress={() => setPageIndex(0)}>
|
||||
<Text>Back to ToDos</Text>
|
||||
<Text style={{ fontFamily: "Manrope_200", fontSize: 12 }}>Back to ToDos</Text>
|
||||
</TouchableOpacity>
|
||||
<View centerH>
|
||||
<Text text50R>Family Chores Progress Report</Text>
|
||||
<Text style={{ fontFamily: "Manrope_700Bold", fontSize: 19 }}>
|
||||
Family Chores Progress Report
|
||||
</Text>
|
||||
</View>
|
||||
<View row spread marginT-25 marginB-20>
|
||||
<Text text70>Points earned this week</Text>
|
||||
<View row spread marginT-35 marginB-20>
|
||||
<Text text70 style={{ fontFamily: "Manrope_600SemiBold" }}>
|
||||
Points earned this week
|
||||
</Text>
|
||||
<View row>
|
||||
<View style={styles.pfpSmall} backgroundColor="#05a8b6" />
|
||||
<View style={styles.pfpSmall} backgroundColor="#ebd825" />
|
||||
<View
|
||||
style={styles.pfpSmall}
|
||||
backgroundColor="#05a8b6"
|
||||
center
|
||||
children={
|
||||
<ImageBackground
|
||||
source={require("../../../../assets/images/child1-picture.png")}
|
||||
style={{
|
||||
height: 25,
|
||||
aspectRatio: 1,
|
||||
borderRadius: 22,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<View
|
||||
style={styles.pfpSmall}
|
||||
backgroundColor="#ebd825"
|
||||
center
|
||||
children={
|
||||
<ImageBackground
|
||||
source={require("../../../../assets/images/child-picture.png")}
|
||||
style={{
|
||||
height: 25,
|
||||
aspectRatio: 1,
|
||||
borderRadius: 22,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.card} paddingL-10>
|
||||
<FamilyChart />
|
||||
</View>
|
||||
<Text text70 marginV-20>
|
||||
<Text text70 style={{ fontFamily: "Manrope_600SemiBold" }} marginV-20>
|
||||
Chore Tracker
|
||||
</Text>
|
||||
<View style={styles.card} marginB-20 row spread>
|
||||
<View style={styles.pfpBig} backgroundColor="#05a8b6" />
|
||||
<View width={"100%"} centerV centerH>
|
||||
<Text> x/y chores completed</Text>
|
||||
<View style={styles.card} marginB-20 row spread centerV>
|
||||
<View
|
||||
style={styles.pfpBig}
|
||||
backgroundColor="#05a8b6"
|
||||
center
|
||||
children={
|
||||
<ImageBackground
|
||||
source={require("../../../../assets/images/child1-picture.png")}
|
||||
style={{
|
||||
height: 45,
|
||||
aspectRatio: 1,
|
||||
borderRadius: 22,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
color="#858585"
|
||||
style={{ fontFamily: "Manrope_600SemiBold", fontSize: 17 }}
|
||||
>
|
||||
Emily
|
||||
</Text>
|
||||
<View centerV>
|
||||
<Text style={{ fontSize: 16, fontFamily: "Manrope_700Bold" }}>
|
||||
{" "}
|
||||
x/y chores completed
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.card} row spread>
|
||||
<View style={styles.pfpBig} backgroundColor="#ebd825" />
|
||||
<View width={"100%"} centerV centerH>
|
||||
<Text> x/y chores completed</Text>
|
||||
<View style={styles.card} row spread centerV>
|
||||
<View
|
||||
style={styles.pfpBig}
|
||||
backgroundColor="#ebd825"
|
||||
center
|
||||
children={
|
||||
<ImageBackground
|
||||
source={require("../../../../assets/images/child-picture.png")}
|
||||
style={{
|
||||
height: 45,
|
||||
aspectRatio: 1,
|
||||
borderRadius: 22,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
color="#858585"
|
||||
style={{ fontFamily: "Manrope_600SemiBold", fontSize: 17 }}
|
||||
>
|
||||
Austin
|
||||
</Text>
|
||||
<View row centerV centerH>
|
||||
<Text style={{ fontSize: 16, fontFamily: "Manrope_700Bold" }}>
|
||||
{" "}
|
||||
x/y chores completed
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@ -6,15 +6,16 @@ import {
|
||||
ButtonSize,
|
||||
Modal,
|
||||
Dialog,
|
||||
TouchableOpacity,
|
||||
} from "react-native-ui-lib";
|
||||
import React, { useState } from "react";
|
||||
import { StyleSheet } from "react-native";
|
||||
import { TouchableOpacity } from "react-native-ui-lib/src/incubator";
|
||||
import UserChart from "./UserChart";
|
||||
import ProgressCard from "../ProgressCard";
|
||||
import { AntDesign, Feather, Ionicons } from "@expo/vector-icons";
|
||||
import { ScrollView } from "react-native-gesture-handler";
|
||||
import { PanViewDirectionsEnum } from "react-native-ui-lib/src/incubator/panView";
|
||||
import FireworksOrangeIcon from "@/assets/svgs/FireworksOrangeIcon";
|
||||
|
||||
const UserChoresProgress = ({
|
||||
setPageIndex,
|
||||
@ -29,29 +30,41 @@ const UserChoresProgress = ({
|
||||
showsHorizontalScrollIndicator={false}
|
||||
>
|
||||
<TouchableOpacity onPress={() => setPageIndex(0)}>
|
||||
<Text>Back to ToDos</Text>
|
||||
<Text style={{ fontSize: 14 }}>Back to ToDos</Text>
|
||||
</TouchableOpacity>
|
||||
<View centerH>
|
||||
<Text text50R>Your To Dos Progress Report</Text>
|
||||
<View>
|
||||
<Text style={{ fontFamily: "Manrope_700Bold", fontSize: 20 }}>
|
||||
Your To Do's Progress Report
|
||||
</Text>
|
||||
</View>
|
||||
<View row spread marginT-25 marginB-5>
|
||||
<Text text70>Daily Goal</Text>
|
||||
<Text text70 style={{ fontFamily: "Manrope_600SemiBold" }}>
|
||||
Daily Goal
|
||||
</Text>
|
||||
</View>
|
||||
<ProgressCard />
|
||||
<View row spread marginT-10 marginB-5>
|
||||
<Text text70>Points Earned This Week</Text>
|
||||
<View row spread marginT-15 marginB-8>
|
||||
<Text text70 style={{ fontFamily: "Manrope_600SemiBold" }}>
|
||||
Points Earned This Week
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.card} paddingL-10>
|
||||
<UserChart />
|
||||
</View>
|
||||
<View row spread marginT-20 marginB-8 centerV>
|
||||
<Text text70>Total Reward Points</Text>
|
||||
<Text text70 style={{ fontFamily: "Manrope_600SemiBold" }}>
|
||||
Total Reward Points
|
||||
</Text>
|
||||
<Button
|
||||
size={ButtonSize.small}
|
||||
label="Spend my points"
|
||||
color="#50be0c"
|
||||
backgroundColor="#ebf2e4"
|
||||
onPress={() => setModalVisible(true)}
|
||||
labelStyle={{
|
||||
fontSize: 13,
|
||||
fontFamily: "Manrope_400Regular",
|
||||
}}
|
||||
iconSource={() => (
|
||||
<AntDesign
|
||||
name="gift"
|
||||
@ -64,8 +77,12 @@ const UserChoresProgress = ({
|
||||
</View>
|
||||
<View style={styles.card}>
|
||||
<View row centerV>
|
||||
<Ionicons name="flower-outline" size={30} color="#8005eb" />
|
||||
<Text text70 marginL-5>
|
||||
<FireworksOrangeIcon color="#8005eb" />
|
||||
<Text
|
||||
marginL-8
|
||||
text70
|
||||
style={{ fontFamily: "Manrope_600SemiBold", fontSize: 14 }}
|
||||
>
|
||||
You have 1200 points saved!
|
||||
</Text>
|
||||
</View>
|
||||
@ -80,8 +97,8 @@ const UserChoresProgress = ({
|
||||
}}
|
||||
/>
|
||||
<View row spread>
|
||||
<Text>0</Text>
|
||||
<Text>5000</Text>
|
||||
<Text style={{ fontSize: 13, color: "#858585" }}>0</Text>
|
||||
<Text style={{ fontSize: 13, color: "#858585" }}>5000</Text>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
@ -90,7 +107,7 @@ const UserChoresProgress = ({
|
||||
onDismiss={() => setModalVisible(false)}
|
||||
children={
|
||||
<View style={styles.card} paddingH-35 paddingT-35>
|
||||
<Text text60 center marginB-35>
|
||||
<Text text60 center marginB-35 style={{fontFamily: 'Manrope_600SemiBold'}}>
|
||||
How would you like to spend your points?
|
||||
</Text>
|
||||
<Button
|
||||
@ -99,6 +116,7 @@ const UserChoresProgress = ({
|
||||
marginB-15
|
||||
backgroundColor="#05a8b6"
|
||||
size={ButtonSize.large}
|
||||
labelStyle={styles.bigButtonText}
|
||||
/>
|
||||
<Button
|
||||
label="Extra Screen Time - 100 pts"
|
||||
@ -106,6 +124,7 @@ const UserChoresProgress = ({
|
||||
marginB-15
|
||||
backgroundColor="#ea156c"
|
||||
size={ButtonSize.large}
|
||||
labelStyle={styles.bigButtonText}
|
||||
/>
|
||||
<Button
|
||||
label="Movie Night - 50 pts"
|
||||
@ -113,6 +132,7 @@ const UserChoresProgress = ({
|
||||
marginB-15
|
||||
backgroundColor="#7305d4"
|
||||
size={ButtonSize.large}
|
||||
labelStyle={styles.bigButtonText}
|
||||
/>
|
||||
<Button
|
||||
label="Ice Cream Treat - 25 pts"
|
||||
@ -120,9 +140,12 @@ const UserChoresProgress = ({
|
||||
marginB-15
|
||||
backgroundColor="#e28800"
|
||||
size={ButtonSize.large}
|
||||
labelStyle={styles.bigButtonText}
|
||||
/>
|
||||
<TouchableOpacity onPress={() => setModalVisible(false)}>
|
||||
<Text text70 center color="#999999">Go back to my to dos</Text>
|
||||
<Text text70 marginT-20 center color="#999999" style={{fontFamily: 'Manrope_500Medium'}} >
|
||||
Go back to my to dos
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
}
|
||||
@ -147,6 +170,9 @@ const styles = StyleSheet.create({
|
||||
borderRadius: 20,
|
||||
padding: 20,
|
||||
},
|
||||
bigButtonText:{
|
||||
fontFamily: 'Manrope_400Regular'
|
||||
}
|
||||
});
|
||||
|
||||
export default UserChoresProgress;
|
||||
|
||||
23
components/shared/AssigneesDisplay.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
import { ImageBackground, StyleSheet } from "react-native";
|
||||
import { View } from "react-native-ui-lib";
|
||||
import RemoveAssigneeBtn from "./RemoveAssigneeBtn";
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
export default AssigneesDisplay;
|
||||
@ -15,7 +15,7 @@ const DrawerButton = (props: IDrawerButtonProps) => {
|
||||
<Button
|
||||
onPress={props.pressFunc}
|
||||
label={props.title}
|
||||
labelStyle={{ fontSize: 14 }}
|
||||
labelStyle={styles.labelStyle}
|
||||
color={props.color}
|
||||
backgroundColor="white"
|
||||
iconSource={() => (
|
||||
@ -30,7 +30,7 @@ const DrawerButton = (props: IDrawerButtonProps) => {
|
||||
)}
|
||||
style={{
|
||||
aspectRatio: 1,
|
||||
borderRadius: 15,
|
||||
borderRadius: 18.55,
|
||||
marginBottom: 12,
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between",
|
||||
@ -43,8 +43,9 @@ export default DrawerButton;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
iconContainer: {
|
||||
width: '70%',
|
||||
width: "70%",
|
||||
aspectRatio: 1,
|
||||
borderRadius: 50,
|
||||
},
|
||||
labelStyle: { fontSize: 15, fontFamily: "Poppins_400Regular" },
|
||||
});
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { View, Text } from "react-native-ui-lib";
|
||||
import React from "react";
|
||||
import { useAuthContext } from "@/contexts/AuthContext";
|
||||
import { ImageBackground } from "react-native";
|
||||
|
||||
const HeaderTemplate = (props: {
|
||||
message: string;
|
||||
@ -9,22 +10,29 @@ const HeaderTemplate = (props: {
|
||||
link?: React.ReactNode;
|
||||
}) => {
|
||||
const { user, profileData } = useAuthContext();
|
||||
|
||||
const headerHeight:number = 72;
|
||||
return (
|
||||
<View row centerV marginV-15>
|
||||
<View
|
||||
backgroundColor="pink"
|
||||
height={65}
|
||||
width={65}
|
||||
style={{ borderRadius: 22 }}
|
||||
marginR-20
|
||||
<ImageBackground
|
||||
source={require("../../assets/images/profile-picture.png")}
|
||||
style={{
|
||||
height: headerHeight,
|
||||
aspectRatio: 1,
|
||||
borderRadius: 22,
|
||||
overflow: "hidden",
|
||||
marginRight: 20,
|
||||
}}
|
||||
/>
|
||||
<View>
|
||||
<View gap-3>
|
||||
{props.isWelcome && (
|
||||
<Text text70L>Welcome, {profileData?.firstName}!</Text>
|
||||
<Text text70L style={{fontSize: 19, fontFamily: "Manrope_400Regular"}}>Welcome, {profileData?.firstName}!</Text>
|
||||
)}
|
||||
<Text text70BL>{props.message}</Text>
|
||||
<Text text70B style={{ fontSize: 18 , fontFamily: "Manrope_600SemiBold" }}>
|
||||
{props.message}
|
||||
</Text>
|
||||
{props.children && <View>{props.children}</View>}
|
||||
{props.link && <View>{props.link}</View>}
|
||||
{props.link && <View marginT-8>{props.link}</View>}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
@ -14,12 +14,15 @@ const PointsSlider = (props: {
|
||||
onValueChange={(value) => props.setPoints(value)}
|
||||
minimumValue={0}
|
||||
step={10}
|
||||
thumbTintColor="white"
|
||||
minimumTrackTintColor="#91d5ff"
|
||||
thumbStyle={{borderWidth: 3, borderColor: '#91d5ff'}}
|
||||
maximumValue={100}
|
||||
/>
|
||||
<View row spread>
|
||||
<Text>0</Text>
|
||||
<Text>50</Text>
|
||||
<Text>100</Text>
|
||||
<Text style={{fontSize: 13, color: '#858585'}}>0</Text>
|
||||
<Text style={{fontSize: 13, color: '#858585'}}>50</Text>
|
||||
<Text style={{fontSize: 13, color: '#858585'}}>100</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={{ marginLeft: "auto" }}>
|
||||
|
||||
27
components/shared/RemoveAssigneeBtn.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { Text, StyleSheet } from "react-native";
|
||||
import React from "react";
|
||||
import CloseXIcon from "@/assets/svgs/CloseXIcon";
|
||||
import { View } from "react-native-ui-lib";
|
||||
|
||||
const RemoveAssigneeBtn = () => {
|
||||
return (
|
||||
<View style={styles.removeBtn} center>
|
||||
<CloseXIcon />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
removeBtn: {
|
||||
aspectRatio: 1,
|
||||
width: 20,
|
||||
backgroundColor: "#f0efef",
|
||||
right: 0,
|
||||
borderRadius: 50,
|
||||
position: "absolute",
|
||||
borderWidth: 1,
|
||||
borderColor: "#7f7f7f",
|
||||
},
|
||||
});
|
||||
|
||||
export default RemoveAssigneeBtn;
|
||||