Merge branch 'dev'

# Conflicts:
#	ios/cally/Info.plist
This commit is contained in:
Milan Paunovic
2024-10-28 01:27:48 +01:00
13 changed files with 1652 additions and 1454 deletions

View File

@ -1,12 +1,9 @@
import { Dimensions, ScrollView } from "react-native"; import {Dimensions, ScrollView, StyleSheet} from "react-native";
import React, {useState} from "react"; import React, {useState} from "react";
import { View, Text, Button } from "react-native-ui-lib"; import {Button, Text, TextField, View} from "react-native-ui-lib";
import DumpList from "./DumpList"; import DumpList from "./DumpList";
import HeaderTemplate from "@/components/shared/HeaderTemplate"; import HeaderTemplate from "@/components/shared/HeaderTemplate";
import { TextField } from "react-native-ui-lib";
import { StyleSheet } from "react-native";
import {Feather, MaterialIcons} from "@expo/vector-icons"; import {Feather, MaterialIcons} from "@expo/vector-icons";
import { TextInput } from "react-native-gesture-handler";
import AddBrainDump from "./AddBrainDump"; import AddBrainDump from "./AddBrainDump";
import LinearGradient from "react-native-linear-gradient"; import LinearGradient from "react-native-linear-gradient";
@ -61,23 +58,22 @@ const BrainDumpPage = () => {
</ScrollView> </ScrollView>
</View> </View>
<LinearGradient <LinearGradient
colors={["#f2f2f2", "transparent"]} colors={["#f9f8f700", "#f9f8f7"]}
start={{ x: 0.5, y: 1 }} locations={[0,1]}
end={{ x: 0.5, y: 0 }}
style={{ style={{
position: "absolute", position: "absolute",
bottom: 0, bottom: 0,
height: 90, height: 120,
width: Dimensions.get("screen").width, width: Dimensions.get("screen").width,
justifyContent:'center',
alignItems:"center"
}} }}
> >
<Button <Button
style={{ style={{
height: 40, height: 40,
position: "relative", position: "relative",
marginLeft: "auto", width: "90%",
width: 20,
right: 20,
bottom: -10, bottom: -10,
borderRadius: 30, borderRadius: 30,
backgroundColor: "#fd1775", backgroundColor: "#fd1775",

View File

@ -1,12 +1,5 @@
import React, {memo} from "react"; import React, {memo} from "react";
import { import {Button, Picker, PickerModes, SegmentedControl, Text, View,} from "react-native-ui-lib";
Button,
Picker,
PickerModes,
SegmentedControl,
Text,
View,
} from "react-native-ui-lib";
import {MaterialIcons} from "@expo/vector-icons"; import {MaterialIcons} from "@expo/vector-icons";
import {modeMap, months} from "./constants"; import {modeMap, months} from "./constants";
import {StyleSheet} from "react-native"; import {StyleSheet} from "react-native";
@ -83,13 +76,13 @@ export const CalendarHeader = memo(() => {
size={"xSmall"} size={"xSmall"}
marginR-0 marginR-0
avoidInnerPadding avoidInnerPadding
padding-7
style={{ style={{
borderRadius: 5, borderRadius: 50,
backgroundColor: "white", backgroundColor: "white",
borderWidth: 0.7, borderWidth: 0.7,
borderColor: "#dadce0", borderColor: "#dadce0",
height: 30, height: 30,
paddingHorizontal: 10
}} }}
labelStyle={{ labelStyle={{
fontSize: 12, fontSize: 12,
@ -101,8 +94,6 @@ export const CalendarHeader = memo(() => {
})} })}
onPress={() => { onPress={() => {
setSelectedDate(new Date()); setSelectedDate(new Date());
setMode("day");
console.log(profileData?.timeZone)
}} }}
/> />
)} )}

View File

@ -1,6 +1,6 @@
import React, {useCallback, useEffect, useMemo, useState} from "react"; import React, {useCallback, useEffect, useMemo, useState} from "react";
import {Calendar} from "react-native-big-calendar"; import {Calendar} from "react-native-big-calendar";
import { ActivityIndicator, StyleSheet, View } from "react-native"; import {ActivityIndicator, StyleSheet, View, ViewStyle} from "react-native";
import {useGetEvents} from "@/hooks/firebase/useGetEvents"; import {useGetEvents} from "@/hooks/firebase/useGetEvents";
import {useAtom, useSetAtom} from "jotai"; import {useAtom, useSetAtom} from "jotai";
import { import {
@ -12,6 +12,7 @@ import {
} from "@/components/pages/calendar/atoms"; } from "@/components/pages/calendar/atoms";
import {useAuthContext} from "@/contexts/AuthContext"; import {useAuthContext} from "@/contexts/AuthContext";
import {CalendarEvent} from "@/components/pages/calendar/interfaces"; import {CalendarEvent} from "@/components/pages/calendar/interfaces";
import {Text} from "react-native-ui-lib";
interface EventCalendarProps { interface EventCalendarProps {
calendarHeight: number; calendarHeight: number;
@ -38,6 +39,8 @@ export const EventCalendar: React.FC<EventCalendarProps> = React.memo(
const [isRendering, setIsRendering] = useState(true); const [isRendering, setIsRendering] = useState(true);
const [offsetMinutes, setOffsetMinutes] = useState(getTotalMinutes()); const [offsetMinutes, setOffsetMinutes] = useState(getTotalMinutes());
const todaysDate = new Date()
useEffect(() => { useEffect(() => {
if (events && mode) { if (events && mode) {
setIsRendering(true); setIsRendering(true);
@ -91,13 +94,88 @@ export const EventCalendar: React.FC<EventCalendarProps> = React.memo(
[profileData] [profileData]
); );
const memoizedHeaderContentStyle = useMemo( const isSameDate = useCallback((date1: Date, date2: Date) => {
() => (mode === "day" ? styles.dayModeHeader : {}), return (
[mode] date1.getDate() === date2.getDate() &&
date1.getMonth() === date2.getMonth() &&
date1.getFullYear() === date2.getFullYear()
); );
}, []);
const dayHeaderColor = useMemo(() => {
return isSameDate(todaysDate, selectedDate) ? "white" : "#4d4d4d";
}, [selectedDate, mode]);
const dateStyle = useMemo(() => {
if (mode === "week") return undefined
return isSameDate(todaysDate, selectedDate) && mode === "day"
? styles.dayHeader
: styles.otherDayHeader;
}, [selectedDate, mode]);
const memoizedHeaderContentStyle = useMemo(() => {
if (mode === "day") {
return styles.dayModeHeader;
} else if (mode === "week") {
return styles.weekModeHeader;
} else if (mode === "month") {
return styles.monthModeHeader;
} else {
return {};
}
}, [mode]);
const memoizedEvents = useMemo(() => events ?? [], [events]); const memoizedEvents = useMemo(() => events ?? [], [events]);
const renderCustomDateForMonth = (date: Date) => {
const circleStyle = useMemo<ViewStyle>(
() => ({
position: "absolute",
width: 30,
height: 30,
justifyContent: "center",
alignItems: "center",
borderRadius: 15,
}),
[]
);
const defaultStyle = useMemo<ViewStyle>(
() => ({
...circleStyle,
}),
[circleStyle]
);
const currentDateStyle = useMemo<ViewStyle>(
() => ({
...circleStyle,
backgroundColor: "#4184f2",
}),
[circleStyle]
);
const renderDate = useCallback(
(date: Date) => {
const isCurrentDate = isSameDate(todaysDate, date);
const appliedStyle = isCurrentDate ? currentDateStyle : defaultStyle;
return (
<View style={{alignItems: "center"}}>
<View style={appliedStyle}>
<Text style={{color: isCurrentDate ? "white" : "black"}}>
{date.getDate()}
</Text>
</View>
</View>
);
},
[todaysDate, currentDateStyle, defaultStyle] // dependencies
);
return renderDate(date);
};
useEffect(() => { useEffect(() => {
setOffsetMinutes(getTotalMinutes()); setOffsetMinutes(getTotalMinutes());
}, [events, mode]); }, [events, mode]);
@ -121,12 +199,16 @@ export const EventCalendar: React.FC<EventCalendarProps> = React.memo(
onPressEvent={handlePressEvent} onPressEvent={handlePressEvent}
weekStartsOn={memoizedWeekStartsOn} weekStartsOn={memoizedWeekStartsOn}
height={calendarHeight} height={calendarHeight}
activeDate={selectedDate} activeDate={todaysDate}
date={selectedDate} date={selectedDate}
onPressCell={handlePressCell} onPressCell={handlePressCell}
headerContentStyle={memoizedHeaderContentStyle} headerContentStyle={memoizedHeaderContentStyle}
onSwipeEnd={handleSwipeEnd} onSwipeEnd={handleSwipeEnd}
scrollOffsetMinutes={offsetMinutes} scrollOffsetMinutes={offsetMinutes}
dayHeaderStyle={dateStyle}
dayHeaderHighlightColor={dayHeaderColor}
renderCustomDateForMonth={renderCustomDateForMonth}
showAdjacentMonths
/> />
); );
} }
@ -148,6 +230,8 @@ const styles = StyleSheet.create({
right: 42, right: 42,
height: 13, height: 13,
}, },
weekModeHeader: {},
monthModeHeader: {},
loadingContainer: { loadingContainer: {
flex: 1, flex: 1,
justifyContent: "center", justifyContent: "center",
@ -160,4 +244,12 @@ const styles = StyleSheet.create({
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
}, },
otherDayHeader: {
backgroundColor: "transparent",
color: "#919191",
aspectRatio: 1,
borderRadius: 100,
alignItems: "center",
justifyContent: "center",
},
}); });

View File

@ -1,30 +1,12 @@
import {StyleSheet} from "react-native"; import {StyleSheet} from "react-native";
import React, { useState } from "react"; import React from "react";
import { import {Button, View,} from "react-native-ui-lib";
Button,
Colors,
Dialog,
Drawer,
Text,
View,
PanningProvider,
} from "react-native-ui-lib";
import {useGroceryContext} from "@/contexts/GroceryContext"; import {useGroceryContext} from "@/contexts/GroceryContext";
import {FontAwesome6} from "@expo/vector-icons"; import {FontAwesome6} from "@expo/vector-icons";
interface AddGroceryItemProps {
visible: boolean;
onClose: () => void;
}
const AddGroceryItem = () => {
const { isAddingGrocery, setIsAddingGrocery } = useGroceryContext();
const [visible, setVisible] = useState<boolean>(false);
const handleShowDialog = () => { const AddGroceryItem = () => {
setVisible(true); const {setIsAddingGrocery} = useGroceryContext();
};
const handleHideDialog = () => {
setVisible(false);
};
return ( return (
<View <View
row row

View File

@ -1,11 +1,11 @@
import { Text, View } from "react-native-ui-lib"; import {Text, TextField, TextFieldRef, View} from "react-native-ui-lib";
import React, { useEffect, useRef, useState } from "react"; import React, {useEffect, useRef} from "react";
import { TextField, TextFieldRef } from "react-native-ui-lib";
import {GroceryCategory, useGroceryContext} from "@/contexts/GroceryContext"; import {GroceryCategory, useGroceryContext} from "@/contexts/GroceryContext";
import {Dropdown} from "react-native-element-dropdown"; import {Dropdown} from "react-native-element-dropdown";
import CloseXIcon from "@/assets/svgs/CloseXIcon"; import CloseXIcon from "@/assets/svgs/CloseXIcon";
import {StyleSheet} from "react-native"; import {StyleSheet} from "react-native";
import DropdownIcon from "@/assets/svgs/DropdownIcon"; import DropdownIcon from "@/assets/svgs/DropdownIcon";
import {AntDesign} from "@expo/vector-icons";
interface IEditGrocery { interface IEditGrocery {
id?: string; id?: string;
@ -14,10 +14,11 @@ interface IEditGrocery {
setTitle: (value: string) => void; setTitle: (value: string) => void;
setCategory?: (category: GroceryCategory) => void; setCategory?: (category: GroceryCategory) => void;
setSubmit?: (value: boolean) => void; setSubmit?: (value: boolean) => void;
closeEdit?: (value: boolean) => void; closeEdit?: () => void;
handleEditSubmit?: Function; handleEditSubmit?: Function;
} }
const EditGroceryItem = ({editGrocery}: { editGrocery: IEditGrocery }) => { const EditGroceryItem = ({editGrocery}: { editGrocery: IEditGrocery }) => {
const {fuzzyMatchGroceryCategory} = useGroceryContext(); const {fuzzyMatchGroceryCategory} = useGroceryContext();
const inputRef = useRef<TextFieldRef>(null); const inputRef = useRef<TextFieldRef>(null);
@ -29,10 +30,29 @@ const EditGroceryItem = ({ editGrocery }: { editGrocery: IEditGrocery }) => {
}) })
); );
const handleSubmit = () => {
inputRef?.current?.blur()
console.log("CALLLLLL")
if (editGrocery.setSubmit) {
editGrocery.setSubmit(true);
}
if (editGrocery.handleEditSubmit) {
editGrocery.handleEditSubmit({
id: editGrocery.id,
title: editGrocery.title,
category: editGrocery.category,
});
}
if (editGrocery.closeEdit) {
editGrocery.closeEdit();
}
}
useEffect(() => { useEffect(() => {
if (inputRef.current) { if (inputRef.current) {
inputRef.current.focus(); // Focus on the TextField inputRef.current.focus();
} }
console.log(editGrocery.category); console.log(editGrocery.category);
}, []); }, []);
@ -50,36 +70,40 @@ const EditGroceryItem = ({ editGrocery }: { editGrocery: IEditGrocery }) => {
<View row spread centerV> <View row spread centerV>
<TextField <TextField
text70T text70T
style={{}}
ref={inputRef} ref={inputRef}
placeholder="Grocery" placeholder="Grocery"
value={editGrocery.title} value={editGrocery.title}
onSubmitEditing={handleSubmit}
numberOfLines={1}
returnKeyType="done"
onChangeText={(value) => { onChangeText={(value) => {
editGrocery.setTitle(value); editGrocery.setTitle(value);
}} let groceryCategory = fuzzyMatchGroceryCategory(value);
onSubmitEditing={() => { if (editGrocery.setCategory) {
if (editGrocery.setSubmit) { editGrocery.setCategory(groceryCategory);
editGrocery.setSubmit(true);
}
if (editGrocery.handleEditSubmit) {
editGrocery.handleEditSubmit({
id: editGrocery.id,
title: editGrocery.title,
category: editGrocery.category,
});
}
if (editGrocery.closeEdit) {
editGrocery.closeEdit(false);
} }
}} }}
maxLength={25} maxLength={25}
/> />
<View row centerV>
<AntDesign
name="check"
size={24}
style={{
color: "green",
marginRight: 15,
}}
onPress={handleSubmit}
/>
<CloseXIcon <CloseXIcon
onPress={() => { onPress={() => {
if (editGrocery.closeEdit) editGrocery.closeEdit(false); if (editGrocery.closeEdit) {
editGrocery.closeEdit();
}
}} }}
/> />
</View> </View>
</View>
<Dropdown <Dropdown
style={{marginTop: 15}} style={{marginTop: 15}}
data={groceryCategoryOptions} data={groceryCategoryOptions}
@ -88,9 +112,7 @@ const EditGroceryItem = ({ editGrocery }: { editGrocery: IEditGrocery }) => {
labelField="label" labelField="label"
valueField="value" valueField="value"
value={ value={
editGrocery.category == GroceryCategory.None editGrocery.category
? null
: editGrocery.category
} }
iconColor="white" iconColor="white"
activeColor={"#fd1775"} activeColor={"#fd1775"}
@ -99,7 +121,8 @@ const EditGroceryItem = ({ editGrocery }: { editGrocery: IEditGrocery }) => {
itemContainerStyle={styles.itemStyle} itemContainerStyle={styles.itemStyle}
selectedTextStyle={styles.selectedText} selectedTextStyle={styles.selectedText}
renderLeftIcon={() => ( renderLeftIcon={() => (
<DropdownIcon style={{ marginRight: 8 }} color={editGrocery.category == GroceryCategory.None ? "#7b7b7b" : "#fd1775"} /> <DropdownIcon style={{marginRight: 8}}
color={editGrocery.category == GroceryCategory.None ? "#7b7b7b" : "#fd1775"}/>
)} )}
renderItem={(item) => { renderItem={(item) => {
return ( return (
@ -114,8 +137,7 @@ const EditGroceryItem = ({ editGrocery }: { editGrocery: IEditGrocery }) => {
id: editGrocery.id, id: editGrocery.id,
category: item.value, category: item.value,
}); });
console.log("kategorija vo diropdown: " + item.value); if (editGrocery.closeEdit) editGrocery.closeEdit();
if (editGrocery.closeEdit) editGrocery.closeEdit(false);
} else { } else {
if (editGrocery.setCategory) { if (editGrocery.setCategory) {
editGrocery.setCategory(item.value); editGrocery.setCategory(item.value);

View File

@ -23,12 +23,14 @@ const GroceryItem = ({
const [openFreqEdit, setOpenFreqEdit] = useState<boolean>(false); const [openFreqEdit, setOpenFreqEdit] = useState<boolean>(false);
const [isEditingTitle, setIsEditingTitle] = useState<boolean>(false); const [isEditingTitle, setIsEditingTitle] = useState<boolean>(false);
const [newTitle, setNewTitle] = useState<string>(""); const [newTitle, setNewTitle] = useState<string>(item.title ?? "");
const [category, setCategory] = useState<GroceryCategory>( const [category, setCategory] = useState<GroceryCategory>(item.category ?? GroceryCategory.None);
GroceryCategory.None
);
const [itemCreator, setItemCreator] = useState<UserProfile>(null); const [itemCreator, setItemCreator] = useState<UserProfile>(null);
const closeEdit = () => {
setIsEditingTitle(false);
}
const handleTitleChange = (newTitle: string) => { const handleTitleChange = (newTitle: string) => {
updateGroceryItem({ id: item?.id, title: newTitle }); updateGroceryItem({ id: item?.id, title: newTitle });
}; };
@ -38,7 +40,6 @@ const GroceryItem = ({
}; };
useEffect(() => { useEffect(() => {
setNewTitle(item.title);
console.log(item); console.log(item);
getItemCreator(item?.creatorId); getItemCreator(item?.creatorId);
}, []); }, []);
@ -81,9 +82,21 @@ const GroceryItem = ({
setOpenFreqEdit(false); setOpenFreqEdit(false);
}} }}
/> />
{!isEditingTitle ? ( {isEditingTitle ?
<EditGroceryItem
editGrocery={{
id: item.id,
title: newTitle,
category: category,
setTitle: setNewTitle,
setCategory: setCategory,
closeEdit: closeEdit,
handleEditSubmit: updateGroceryItem,
}}
/> :
<View> <View>
{ isParent ? <TouchableOpacity onPress={() => setIsEditingTitle(true)}> {isParent ?
<TouchableOpacity onPress={() => setIsEditingTitle(true)}>
<Text text70T black style={styles.title}> <Text text70T black style={styles.title}>
{item.title} {item.title}
</Text> </Text>
@ -93,40 +106,27 @@ const GroceryItem = ({
</Text> </Text>
} }
</View> </View>
) : ( }
<EditGroceryItem
editGrocery={{
id: item.id,
title: newTitle,
category: item.category,
setTitle: setNewTitle,
setCategory: setCategory,
closeEdit: setIsEditingTitle,
handleEditSubmit: updateGroceryItem,
}}
/>
)}
{!item.approved ? ( {!item.approved ? (
<View row centerV marginB-10> <View row centerV marginB-10>
{isParent && <><AntDesign {isParent &&
<>
<AntDesign
name="check" name="check"
size={24} size={24}
style={{ style={{
color: "green", color: "green",
marginRight: 15, marginRight: 15,
}} }}
onPress={() => { onPress={isParent ? () => handleItemApproved(item.id, { approved: true }) : null}
isParent ? handleItemApproved(item.id, { approved: true }) : null
}}
/> />
<AntDesign <AntDesign
name="close" name="close"
size={24} size={24}
style={{ color: "red" }} style={{ color: "red" }}
onPress={() => { onPress={isParent ? () => handleItemApproved(item.id, { approved: false }) : null}
isParent ? handleItemApproved(item.id, { approved: false }) : null />
}} </>}
/> </>}
</View> </View>
) : ( ) : (
!isEditingTitle && ( !isEditingTitle && (
@ -158,7 +158,8 @@ const GroceryItem = ({
borderRadius: 22, borderRadius: 22,
overflow: "hidden", overflow: "hidden",
}} }}
/> : <View /> :
<View
style={{ style={{
position: "relative", position: "relative",
width: 24.64, width: 24.64,
@ -183,7 +184,7 @@ const GroceryItem = ({
fontWeight: "bold", fontWeight: "bold",
}} }}
> >
{getInitials(itemCreator.firstName, itemCreator.lastName ?? "")} {itemCreator ? getInitials(itemCreator.firstName, itemCreator.lastName) : ""}
</Text> </Text>
</View> </View>
</View>} </View>}

View File

@ -1,14 +1,10 @@
import {FlatList, StyleSheet} from "react-native"; import {FlatList, StyleSheet} from "react-native";
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
import { Button, Text, TouchableOpacity, View } from "react-native-ui-lib"; import {Text, TouchableOpacity, View} from "react-native-ui-lib";
import GroceryItem from "./GroceryItem"; import GroceryItem from "./GroceryItem";
import { import {GroceryCategory, GroceryFrequency, useGroceryContext,} from "@/contexts/GroceryContext";
GroceryCategory,
GroceryFrequency,
useGroceryContext,
} from "@/contexts/GroceryContext";
import HeaderTemplate from "@/components/shared/HeaderTemplate"; import HeaderTemplate from "@/components/shared/HeaderTemplate";
import { AntDesign, MaterialIcons } from "@expo/vector-icons"; import {AntDesign} from "@expo/vector-icons";
import EditGroceryItem from "./EditGroceryItem"; import EditGroceryItem from "./EditGroceryItem";
import {ProfileType, useAuthContext} from "@/contexts/AuthContext"; import {ProfileType, useAuthContext} from "@/contexts/AuthContext";
import {IGrocery} from "@/hooks/firebase/types/groceryData"; import {IGrocery} from "@/hooks/firebase/types/groceryData";
@ -24,10 +20,10 @@ const GroceryList = () => {
} = useGroceryContext(); } = useGroceryContext();
const {profileData} = useAuthContext(); const {profileData} = useAuthContext();
const [approvedGroceries, setapprovedGroceries] = useState<IGrocery[]>( const [approvedGroceries, setapprovedGroceries] = useState<IGrocery[]>(
groceries?.filter((item) => item.approved === true) groceries?.filter((item) => item.approved)
); );
const [pendingGroceries, setPendingGroceries] = useState<IGrocery[]>( const [pendingGroceries, setPendingGroceries] = useState<IGrocery[]>(
groceries?.filter((item) => item.approved !== true) groceries?.filter((item) => !item.approved)
); );
const [category, setCategory] = useState<GroceryCategory>( const [category, setCategory] = useState<GroceryCategory>(
GroceryCategory.None GroceryCategory.None
@ -73,12 +69,8 @@ const GroceryList = () => {
}, [submit]); }, [submit]);
useEffect(() => { useEffect(() => {
/**/ setapprovedGroceries(groceries?.filter((item) => item.approved));
}, [category]); setPendingGroceries(groceries?.filter((item) => !item.approved));
useEffect(() => {
setapprovedGroceries(groceries?.filter((item) => item.approved === true));
setPendingGroceries(groceries?.filter((item) => item.approved !== true));
}, [groceries]); }, [groceries]);
return ( return (
@ -228,6 +220,7 @@ const GroceryList = () => {
</View> </View>
</View> </View>
{isAddingGrocery && ( {isAddingGrocery && (
<View style={{marginTop: 8}}>
<EditGroceryItem <EditGroceryItem
editGrocery={{ editGrocery={{
title: title, title: title,
@ -235,8 +228,10 @@ const GroceryList = () => {
category: category, category: category,
setTitle: setTitle, setTitle: setTitle,
setSubmit: setSubmitted, setSubmit: setSubmitted,
closeEdit: () => setIsAddingGrocery(false)
}} }}
/> />
</View>
)} )}
{/* Render Approved Groceries Grouped by Category */} {/* Render Approved Groceries Grouped by Category */}

View File

@ -1,4 +1,4 @@
import { Text, ScrollView } from "react-native"; import {ScrollView} from "react-native";
import {View} from "react-native-ui-lib"; import {View} from "react-native-ui-lib";
import React, {useEffect, useRef} from "react"; import React, {useEffect, useRef} from "react";
import AddGroceryItem from "./AddGroceryItem"; import AddGroceryItem from "./AddGroceryItem";
@ -22,7 +22,7 @@ const GroceryWrapper = () => {
<View height={"100%"} paddingT-15 paddingH-15> <View height={"100%"} paddingT-15 paddingH-15>
<View height={"100%"}> <View height={"100%"}>
<ScrollView <ScrollView
ref={scrollViewRef} // Assign the ref to the ScrollView ref={scrollViewRef}
automaticallyAdjustKeyboardInsets={true} automaticallyAdjustKeyboardInsets={true}
> >
<View marginB-70> <View marginB-70>

View File

@ -1,34 +1,47 @@
import {Button, ButtonSize, Dialog, Text, TextField, View} from "react-native-ui-lib"; import {
import React, {useState} from "react"; Button,
ButtonSize,
Dialog,
Text,
TextField,
TextFieldRef,
View,
} from "react-native-ui-lib";
import React, { useRef, useState } from "react";
import { useSignIn } from "@/hooks/firebase/useSignIn"; import { useSignIn } from "@/hooks/firebase/useSignIn";
import { StyleSheet } from "react-native"; import { StyleSheet } from "react-native";
import Toast from 'react-native-toast-message'; import Toast from "react-native-toast-message";
import { useLoginWithQrCode } from "@/hooks/firebase/useLoginWithQrCode"; import { useLoginWithQrCode } from "@/hooks/firebase/useLoginWithQrCode";
import {Camera, CameraView} from 'expo-camera'; import { Camera, CameraView } from "expo-camera";
const SignInPage = ({setTab}: { const SignInPage = ({
setTab: React.Dispatch<React.SetStateAction<"register" | "login" | "reset-password">> setTab,
}: {
setTab: React.Dispatch<
React.SetStateAction<"register" | "login" | "reset-password">
>;
}) => { }) => {
const [email, setEmail] = useState<string>(""); const [email, setEmail] = useState<string>("");
const [password, setPassword] = useState<string>(""); const [password, setPassword] = useState<string>("");
const [hasPermission, setHasPermission] = useState<boolean | null>(null); const [hasPermission, setHasPermission] = useState<boolean | null>(null);
const [showCameraDialog, setShowCameraDialog] = useState<boolean>(false); const [showCameraDialog, setShowCameraDialog] = useState<boolean>(false);
const passwordRef = useRef<TextFieldRef>(null);
const { mutateAsync: signIn, error, isError } = useSignIn(); const { mutateAsync: signIn, error, isError } = useSignIn();
const {mutateAsync: signInWithQrCode} = useLoginWithQrCode() const { mutateAsync: signInWithQrCode } = useLoginWithQrCode();
const handleSignIn = async () => { const handleSignIn = async () => {
await signIn({ email, password }); await signIn({ email, password });
if (!isError) { if (!isError) {
Toast.show({ Toast.show({
type: "success", type: "success",
text1: "Login successful!" text1: "Login successful!",
}); });
} else { } else {
Toast.show({ Toast.show({
type: "error", type: "error",
text1: "Error logging in", text1: "Error logging in",
text2: `${error}` text2: `${error}`,
}); });
} }
}; };
@ -39,21 +52,21 @@ const SignInPage = ({setTab}: {
await signInWithQrCode({ userId: data }); await signInWithQrCode({ userId: data });
Toast.show({ Toast.show({
type: "success", type: "success",
text1: "Login successful with QR code!" text1: "Login successful with QR code!",
}); });
} catch (err) { } catch (err) {
Toast.show({ Toast.show({
type: "error", type: "error",
text1: "Error logging in with QR code", text1: "Error logging in with QR code",
text2: `${err}` text2: `${err}`,
}); });
} }
}; };
const getCameraPermissions = async (callback: () => void) => { const getCameraPermissions = async (callback: () => void) => {
const { status } = await Camera.requestCameraPermissionsAsync(); const { status } = await Camera.requestCameraPermissionsAsync();
setHasPermission(status === 'granted'); setHasPermission(status === "granted");
if (status === 'granted') { if (status === "granted") {
callback(); callback();
} }
}; };
@ -63,10 +76,16 @@ const SignInPage = ({setTab}: {
<TextField <TextField
placeholder="Email" placeholder="Email"
value={email} value={email}
onChangeText={setEmail} onChangeText={setEmail}
style={styles.textfield} style={styles.textfield}
onSubmitEditing={() => {
// Move focus to the description field
passwordRef.current?.focus();
}}
/> />
<TextField <TextField
ref={passwordRef}
placeholder="Password" placeholder="Password"
value={password} value={password}
onChangeText={setPassword} onChangeText={setPassword}
@ -75,28 +94,42 @@ const SignInPage = ({setTab}: {
/> />
<Button <Button
label="Log in" label="Log in"
marginT-50
labelStyle={{
fontFamily: "PlusJakartaSans_600SemiBold",
fontSize: 16,
}}
onPress={handleSignIn} onPress={handleSignIn}
style={{marginBottom: 20}} style={{ marginBottom: 20, height: 50 }}
backgroundColor="#fd1775" backgroundColor="#fd1775"
/> />
<Button <Button
label="Log in with a QR Code" label="Log in with a QR Code"
labelStyle={{
fontFamily: "PlusJakartaSans_600SemiBold",
fontSize: 16,
}}
onPress={() => { onPress={() => {
getCameraPermissions(() => setShowCameraDialog(true)); getCameraPermissions(() => setShowCameraDialog(true));
} }}
} style={{ marginBottom: 20, height: 50 }}
style={{marginBottom: 20}}
backgroundColor="#fd1775" backgroundColor="#fd1775"
/> />
{isError && <Text center style={{marginBottom: 20}}>{`${error?.toString()?.split("]")?.[1]}`}</Text>} {isError && (
<Text center style={{ marginBottom: 20 }}>{`${
error?.toString()?.split("]")?.[1]
}`}</Text>
)}
<View row centerH marginB-5 gap-5> <View row centerH marginB-5 gap-5>
<Text text70> <Text style={styles.jakartaLight}>Don't have an account?</Text>
Don't have an account?
</Text>
<Button <Button
onPress={() => setTab("register")} onPress={() => setTab("register")}
label="Sign Up" label="Sign Up"
labelStyle={[
styles.jakartaMedium,
{ textDecorationLine: "none", color: "#fd1575" },
]}
link link
size={ButtonSize.xSmall} size={ButtonSize.xSmall}
padding-0 padding-0
@ -108,18 +141,21 @@ const SignInPage = ({setTab}: {
</View> </View>
<View row centerH marginB-5 gap-5> <View row centerH marginB-5 gap-5>
<Text text70> <Text text70>Forgot your password?</Text>
Forgot your password?
</Text>
<Button <Button
onPress={() => setTab("reset-password")} onPress={() => setTab("reset-password")}
label="Reset password" label="Reset password"
labelStyle={[
styles.jakartaMedium,
{ textDecorationLine: "none", color: "#fd1575" },
]}
link link
size={ButtonSize.xSmall} size={ButtonSize.xSmall}
padding-0 padding-0
margin-0 margin-0
text70 text70
left left
avoidInnerPadding
color="#fd1775" color="#fd1775"
/> />
</View> </View>
@ -164,6 +200,18 @@ const styles = StyleSheet.create({
padding: 30, padding: 30,
height: 45, height: 45,
borderRadius: 50, borderRadius: 50,
fontFamily: "PlusJakartaSans_300Light",
},
jakartaLight: {
fontFamily: "PlusJakartaSans_300Light",
fontSize: 16,
color: "#484848",
},
jakartaMedium: {
fontFamily: "PlusJakartaSans_500Medium",
fontSize: 16,
color: "#919191",
textDecorationLine: "underline",
}, },
}); });

View File

@ -11,7 +11,7 @@ import {
} from "react-native-ui-lib"; } from "react-native-ui-lib";
import { useSignUp } from "@/hooks/firebase/useSignUp"; import { useSignUp } from "@/hooks/firebase/useSignUp";
import { ProfileType } from "@/contexts/AuthContext"; import { ProfileType } from "@/contexts/AuthContext";
import { StyleSheet } from "react-native"; import { Dimensions, StyleSheet } from "react-native";
import { AntDesign } from "@expo/vector-icons"; import { AntDesign } from "@expo/vector-icons";
const SignUpPage = ({ const SignUpPage = ({
@ -40,19 +40,21 @@ const SignUpPage = ({
}; };
return ( return (
<View padding-10 height={"100%"} flexG> <View padding-15 marginT-30 height={Dimensions.get("window").height} flexG>
<Text text30 center> <Text style={styles.title}>Get started with Cally</Text>
Get started with Cally <Text style={styles.subtitle} marginT-15 color="#919191">
Please enter your details.
</Text> </Text>
<Text center>Please enter your details.</Text>
<TextField <TextField
marginT-60 marginT-30
autoFocus autoFocus
placeholder="First name" placeholder="First name"
value={firstName} value={firstName}
onChangeText={setFirstName} onChangeText={setFirstName}
style={styles.textfield} style={styles.textfield}
onSubmitEditing={() => {lnameRef.current?.focus()}} onSubmitEditing={() => {
lnameRef.current?.focus();
}}
blurOnSubmit={false} blurOnSubmit={false}
/> />
<TextField <TextField
@ -61,7 +63,9 @@ const SignUpPage = ({
value={lastName} value={lastName}
onChangeText={setLastName} onChangeText={setLastName}
style={styles.textfield} style={styles.textfield}
onSubmitEditing={() => {emailRef.current?.focus()}} onSubmitEditing={() => {
emailRef.current?.focus();
}}
blurOnSubmit={false} blurOnSubmit={false}
/> />
<TextField <TextField
@ -70,16 +74,22 @@ const SignUpPage = ({
value={email} value={email}
onChangeText={setEmail} onChangeText={setEmail}
style={styles.textfield} style={styles.textfield}
onSubmitEditing={() => {passwordRef.current?.focus()}} onSubmitEditing={() => {
passwordRef.current?.focus();
}}
blurOnSubmit={false} blurOnSubmit={false}
/> />
<View
centerV
style={[styles.textfield, { padding: 0, paddingHorizontal: 30 }]}
>
<TextField <TextField
ref={passwordRef} ref={passwordRef}
placeholder="Password" placeholder="Password"
style={styles.jakartaLight}
value={password} value={password}
onChangeText={setPassword} onChangeText={setPassword}
secureTextEntry={!isPasswordVisible} secureTextEntry={!isPasswordVisible}
style={styles.textfield}
trailingAccessory={ trailingAccessory={
<TouchableOpacity <TouchableOpacity
onPress={() => setIsPasswordVisible(!isPasswordVisible)} onPress={() => setIsPasswordVisible(!isPasswordVisible)}
@ -92,36 +102,41 @@ const SignUpPage = ({
</TouchableOpacity> </TouchableOpacity>
} }
/> />
<View gap-10 marginH-10> </View>
<View gap-5 marginT-15>
<View row centerV> <View row centerV>
<Checkbox <Checkbox
style={[styles.check]}
color="#919191"
value={allowFaceID} value={allowFaceID}
onValueChange={(value) => { onValueChange={(value) => {
setAllowFaceID(value); setAllowFaceID(value);
}} }}
/> />
<Text text90R marginL-10> <Text style={styles.jakartaLight} marginL-10>
Allow FaceID for login in future Allow FaceID for login in future
</Text> </Text>
</View> </View>
<View row centerV> <View row centerV>
<Checkbox <Checkbox
style={styles.check}
color="#919191"
value={acceptTerms} value={acceptTerms}
onValueChange={(value) => setAcceptTerms(value)} onValueChange={(value) => setAcceptTerms(value)}
/> />
<View row> <View row>
<Text text90R marginL-10> <Text style={styles.jakartaLight} marginL-10>
I accept the I accept the
</Text> </Text>
<TouchableOpacity> <TouchableOpacity>
<Text text90 style={{ textDecorationLine: "underline" }}> <Text text90 style={styles.jakartaMedium}>
{" "} {" "}
terms and conditions terms and conditions
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
<Text text90R> and </Text> <Text style={styles.jakartaLight}> and </Text>
<TouchableOpacity> <TouchableOpacity>
<Text text90 style={{ textDecorationLine: "underline" }}> <Text text90 style={styles.jakartaMedium}>
{" "} {" "}
privacy policy privacy policy
</Text> </Text>
@ -132,16 +147,24 @@ const SignUpPage = ({
<View style={styles.bottomView}> <View style={styles.bottomView}>
<Button <Button
label="Register" label="Register"
labelStyle={{
fontFamily: "PlusJakartaSans_600SemiBold",
fontSize: 16,
}}
onPress={handleSignUp} onPress={handleSignUp}
style={{ marginBottom: 10, backgroundColor: "#fd1775" }} style={{ marginBottom: 0, backgroundColor: "#fd1775", height: 50 }}
/> />
<View row centerH marginT-10 marginB-5 gap-5> <View row centerH marginT-10 marginB-2 gap-5>
<Text text70 center> <Text style={[styles.jakartaLight, { fontSize: 16, color: "#484848" }]} center>
Already have an account? Already have an account?
</Text> </Text>
<Button <Button
label="Sign In" label="Log in"
labelStyle={[
styles.jakartaMedium,
{ fontSize: 16, textDecorationLine: "none", color: "#fd1775" },
]}
flexS flexS
margin-0 margin-0
link link
@ -161,11 +184,35 @@ export default SignUpPage;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
textfield: { textfield: {
backgroundColor: "white", backgroundColor: "white",
marginVertical: 10, marginVertical: 8,
padding: 30, padding: 30,
height: 45, height: 44,
borderRadius: 50, borderRadius: 50,
fontFamily: "PlusJakartaSans_300Light",
fontSize: 13,
color: "#919191",
}, },
//mora da se izmeni kako treba //mora da se izmeni kako treba
bottomView: { marginTop: 150 }, bottomView: { marginTop: "auto", marginBottom: 30 },
jakartaLight: {
fontFamily: "PlusJakartaSans_300Light",
fontSize: 13,
color: "#919191",
},
jakartaMedium: {
fontFamily: "PlusJakartaSans_500Medium",
fontSize: 13,
color: "#919191",
textDecorationLine: "underline",
},
title: { fontFamily: "Manrope_600SemiBold", fontSize: 34, marginTop: 50 },
subtitle: { fontFamily: "PlusJakartaSans_400Regular", fontSize: 16 },
check: {
borderRadius: 3,
aspectRatio: 1,
width: 18,
color: "#919191",
borderColor: "#919191",
borderWidth: 1,
},
}); });

View File

@ -1,23 +1,27 @@
import { View, Text, Button, Switch, PickerModes } from "react-native-ui-lib"; import {
import React, { useRef, useState } from "react"; Button,
ButtonSize,
DateTimePicker,
Dialog,
Picker,
PickerModes,
Switch,
Text,
TextField,
TextFieldRef,
View
} from "react-native-ui-lib";
import React, {useEffect, useRef, useState} from "react";
import PointsSlider from "@/components/shared/PointsSlider"; import PointsSlider from "@/components/shared/PointsSlider";
import {repeatOptions, useToDosContext} from "@/contexts/ToDosContext"; import {repeatOptions, useToDosContext} from "@/contexts/ToDosContext";
import { Feather, AntDesign, Ionicons } from "@expo/vector-icons"; import {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 {PanningDirectionsEnum} from "react-native-ui-lib/src/incubator/panView";
import { Dimensions, StyleSheet } from "react-native"; import {Dimensions, KeyboardAvoidingView, StyleSheet} from "react-native";
import DropModalIcon from "@/assets/svgs/DropModalIcon"; import DropModalIcon from "@/assets/svgs/DropModalIcon";
import {IToDo} from "@/hooks/firebase/types/todoData"; import {IToDo} from "@/hooks/firebase/types/todoData";
import AssigneesDisplay from "@/components/shared/AssigneesDisplay"; import AssigneesDisplay from "@/components/shared/AssigneesDisplay";
import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers"; import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers";
import CalendarIcon from "@/assets/svgs/CalendarIcon"; import CalendarIcon from "@/assets/svgs/CalendarIcon";
import ClockIcon from "@/assets/svgs/ClockIcon";
import ClockOIcon from "@/assets/svgs/ClockOIcon"; import ClockOIcon from "@/assets/svgs/ClockOIcon";
import ProfileIcon from "@/assets/svgs/ProfileIcon"; import ProfileIcon from "@/assets/svgs/ProfileIcon";
import RepeatFreq from "./RepeatFreq"; import RepeatFreq from "./RepeatFreq";
@ -47,9 +51,11 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
const [selectedAssignees, setSelectedAssignees] = useState<string[]>( const [selectedAssignees, setSelectedAssignees] = useState<string[]>(
addChoreDialogProps?.selectedTodo?.assignees ?? [] addChoreDialogProps?.selectedTodo?.assignees ?? []
); );
const { width, height } = Dimensions.get("screen"); const {width} = Dimensions.get("screen");
const [points, setPoints] = useState<number>(todo.points); const [points, setPoints] = useState<number>(todo.points);
const titleRef = useRef<TextFieldRef>(null)
const {data: members} = useGetFamilyMembers(); const {data: members} = useGetFamilyMembers();
const handleClose = () => { const handleClose = () => {
@ -87,6 +93,12 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
} }
} }
useEffect(() => {
setTimeout(() => {
titleRef?.current?.focus()
}, 500)
}, []);
return ( return (
<Dialog <Dialog
bottom={true} bottom={true}
@ -148,9 +160,9 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
}} }}
/> />
</View> </View>
<KeyboardAvoidingView>
<TextField <TextField
placeholder="Add a To Do" placeholder="Add a To Do"
autoFocus
value={todo?.title} value={todo?.title}
onChangeText={(text) => { onChangeText={(text) => {
setTodo((oldValue: IToDo) => ({...oldValue, title: text})); setTodo((oldValue: IToDo) => ({...oldValue, title: text}));
@ -159,6 +171,7 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
text60R text60R
marginT-15 marginT-15
marginL-30 marginL-30
ref={titleRef}
/> />
<View style={styles.divider} marginT-8/> <View style={styles.divider} marginT-8/>
<View marginL-30 centerV> <View marginL-30 centerV>
@ -220,7 +233,8 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
))} ))}
</Picker> </Picker>
</View> </View>
{todo.repeatType == "Every week" && <RepeatFreq handleRepeatDaysChange={handleRepeatDaysChange} repeatDays={todo.repeatDays ?? []}/>} {todo.repeatType == "Every week" && <RepeatFreq handleRepeatDaysChange={handleRepeatDaysChange}
repeatDays={todo.repeatDays ?? []}/>}
</View> </View>
<View style={styles.divider}/> <View style={styles.divider}/>
@ -299,6 +313,7 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
setPoints={setPoints} setPoints={setPoints}
handleChange={handleChange} handleChange={handleChange}
/> />
</KeyboardAvoidingView>
</Dialog> </Dialog>
); );
}; };

View File

@ -1,19 +1,27 @@
import { useQuery } from "react-query"; import { useQuery } from "react-query";
import firestore from "@react-native-firebase/firestore"; import firestore from "@react-native-firebase/firestore";
import { useAuthContext } from "@/contexts/AuthContext"; import { ProfileType, useAuthContext } from "@/contexts/AuthContext";
import { IToDo } from "@/hooks/firebase/types/todoData"; import { IToDo } from "@/hooks/firebase/types/todoData";
export const useGetTodos = () => { export const useGetTodos = () => {
const { user, profileData } = useAuthContext(); const { user, profileData } = useAuthContext();
//TODO: Add role based filtering for todos
return useQuery({ return useQuery({
queryKey: ["todos", user?.uid], queryKey: ["todos", user?.uid],
queryFn: async () => { queryFn: async () => {
const snapshot = await firestore()
let snapshot;
if (profileData?.userType === ProfileType.PARENT) {
snapshot = await firestore()
.collection("Todos") .collection("Todos")
.where("familyId", "==", profileData?.familyId) .where("familyId", "==", profileData?.familyId)
.get(); .get();
} else {
snapshot = await firestore()
.collection("Todos")
.where("assignees", "array-contains", user?.uid)
.get();
}
return snapshot.docs.map((doc) => { return snapshot.docs.map((doc) => {
const data = doc.data(); const data = doc.data();

View File

@ -127,6 +127,7 @@
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
</array> </array>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>SplashScreen</string> <string>SplashScreen</string>