From a8957c7ac75b0d73b6dd2f17e278fd9cfe60d172 Mon Sep 17 00:00:00 2001
From: ivic00 <102467664+ivic00@users.noreply.github.com>
Date: Wed, 12 Feb 2025 00:05:39 +0100
Subject: [PATCH] birthday through qr, deleteFam function
---
app/(unauth)/birthday_page.tsx | 117 +++++++++++
app/(unauth)/get_started.tsx | 2 +-
.../user_settings_views/MyProfile.tsx | 18 +-
firebase/functions/index.js | 80 ++++++++
hooks/firebase/useDeleteFamily.ts | 32 +++
.../react-native-big-calendar+4.15.1.patch | 194 ------------------
6 files changed, 247 insertions(+), 196 deletions(-)
create mode 100644 app/(unauth)/birthday_page.tsx
create mode 100644 hooks/firebase/useDeleteFamily.ts
delete mode 100644 patches/react-native-big-calendar+4.15.1.patch
diff --git a/app/(unauth)/birthday_page.tsx b/app/(unauth)/birthday_page.tsx
new file mode 100644
index 0000000..3800bc1
--- /dev/null
+++ b/app/(unauth)/birthday_page.tsx
@@ -0,0 +1,117 @@
+import { SafeAreaView } from "react-native-safe-area-context";
+import { Button, Text, View, DateTimePicker } from "react-native-ui-lib";
+import React, { useState } from "react";
+import { useRouter } from "expo-router";
+import { Platform, StyleSheet } from "react-native";
+import { useAuthContext } from "@/contexts/AuthContext";
+import firestore from "@react-native-firebase/firestore";
+import { useUpdateUserData } from "@/hooks/firebase/useUpdateUserData";
+
+export default function BirthdayScreen() {
+ const router = useRouter();
+ const { user } = useAuthContext();
+ const [date, setDate] = useState(new Date());
+ const { mutateAsync: updateUserData } = useUpdateUserData();
+
+ const onDateChange = (event: any, selectedDate?: Date) => {
+ const currentDate = selectedDate || date;
+ setDate(currentDate);
+ };
+
+ const handleContinue = async () => {
+ try {
+ updateUserData({
+ newUserData: {
+ birthday: date,
+ },
+ }).then(() => router.push("/(unauth)/cal_sync"));
+ } catch (error) {
+ console.error("Error saving birthday:", error);
+ }
+ };
+
+ const getMaxDate = () => {
+ const date = new Date();
+ date.setFullYear(date.getFullYear() - 3); // Minimum age: 3 years
+ return date;
+ };
+
+ const getMinDate = () => {
+ const date = new Date();
+ date.setFullYear(date.getFullYear() - 18); // Maximum age: 18 years
+ return date;
+ };
+
+ return (
+
+
+
+
+ When's your birthday?
+
+
+ We'll use this to celebrate your special day!
+
+
+
+
+ {
+ if (date) {
+ const validDate = new Date(date);
+ if (!isNaN(validDate.getTime())) {
+ setDate(validDate);
+ }
+ }
+ }}
+ style={styles.textfield}
+ textAlign="center"
+ />
+
+
+
+
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ textfield: {
+ backgroundColor: "white",
+ marginVertical: 100,
+ padding: 30,
+ height: 44,
+ borderRadius: 50,
+ fontFamily: "PlusJakartaSans_300Light",
+ fontSize: 15,
+ color: "#919191",
+ alignContent: "center",
+ },
+});
diff --git a/app/(unauth)/get_started.tsx b/app/(unauth)/get_started.tsx
index 1649995..522fd94 100644
--- a/app/(unauth)/get_started.tsx
+++ b/app/(unauth)/get_started.tsx
@@ -49,7 +49,7 @@ export default function Screen() {
const debouncedRouterReplace = useCallback(
debounce(() => {
- router.push("/(unauth)/cal_sync");
+ router.push("/(unauth)/birthday_page");
}, 300),
[]
);
diff --git a/components/pages/settings/user_settings_views/MyProfile.tsx b/components/pages/settings/user_settings_views/MyProfile.tsx
index f7aa209..cb82a07 100644
--- a/components/pages/settings/user_settings_views/MyProfile.tsx
+++ b/components/pages/settings/user_settings_views/MyProfile.tsx
@@ -26,11 +26,13 @@ import { useDeleteUser } from "@/hooks/firebase/useDeleteUser";
import { useUpdateHouseholdName } from "@/hooks/firebase/useUpdateHouseholdName";
import { useGetHouseholdName } from "@/hooks/firebase/useGetHouseholdName";
import { useGetFamilyMembers } from "@/hooks/firebase/useGetFamilyMembers";
+import { useDeleteFamily } from "@/hooks/firebase/useDeleteFamily";
const MyProfile = () => {
const { user, profileData } = useAuthContext();
const { data: familyMembers } = useGetFamilyMembers();
const [takenColors, setTakenColors] = useState([]);
+ const { mutate: deleteFamily, isLoading } = useDeleteFamily();
const { data: hhName, refetch: refetchHHName } = useGetHouseholdName(
profileData.familyId
@@ -181,6 +183,19 @@ const MyProfile = () => {
debouncedUpdateUserData(color);
};
+ const handleDeleteFamily = () => {
+ if(profileData?.familyId){
+ deleteFamily({familyId: profileData?.familyId}, {
+ onSuccess: () => {
+
+ },
+ onError: (error) => {
+ console.log("from delete fam:\n" + error);
+ }
+ })
+ }
+ }
+
const debouncedUpdateUserData = useCallback(
debounce(async (color: string) => {
try {
@@ -568,7 +583,8 @@ const MyProfile = () => {
onDismiss={handleHideDeleteDialog}
onConfirm={() => {
if(isDeleteFamily)
- console.log('put delete family func here');
+ //deletes family
+ handleDeleteFamily();
else
{
//deletes profile
diff --git a/firebase/functions/index.js b/firebase/functions/index.js
index 1bda323..9f7c2f3 100644
--- a/firebase/functions/index.js
+++ b/firebase/functions/index.js
@@ -1771,3 +1771,83 @@ exports.updateHouseholdTimestampOnEventUpdate = functions.firestore
throw error;
}
});
+
+ exports.deleteFamily = functions.https.onCall(async (data, context) => {
+ if (!context.auth) {
+ throw new functions.https.HttpsError('unauthenticated', 'User must be authenticated');
+ }
+
+ const { familyId } = data;
+
+ if (!familyId) {
+ throw new functions.https.HttpsError('invalid-argument', 'Family ID is required');
+ }
+
+ try {
+ const db = admin.firestore();
+
+ const requestingUserProfile = await db.collection('Profiles')
+ .doc(context.auth.uid)
+ .get();
+
+ if (!requestingUserProfile.exists || requestingUserProfile.data().userType !== 'parent') {
+ throw new functions.https.HttpsError('permission-denied', 'Only parents can delete families');
+ }
+
+ if (requestingUserProfile.data().familyId !== familyId) {
+ throw new functions.https.HttpsError('permission-denied', 'You can not delete other families');
+ }
+
+ const profilesSnapshot = await db.collection('Profiles')
+ .where('familyId', '==', familyId)
+ .get();
+
+ const batch = db.batch();
+ const profileIds = [];
+
+ for (const profile of profilesSnapshot.docs) {
+ const userId = profile.id;
+ profileIds.push(userId);
+
+ const collections = [
+ 'BrainDumps',
+ 'Groceries',
+ 'Todos',
+ 'Events'
+ ];
+
+ for (const collectionName of collections) {
+ const userDocsSnapshot = await db.collection(collectionName)
+ .where('creatorId', '==', userId)
+ .get();
+
+ userDocsSnapshot.docs.forEach(doc => {
+ batch.delete(doc.ref);
+ });
+ }
+
+ batch.delete(profile.ref);
+ }
+
+ const householdDoc = await db.collection('Households')
+ .doc(familyId)
+ .get();
+
+ if (householdDoc.exists) {
+ batch.delete(householdDoc.ref);
+ }
+
+ await batch.commit();
+
+ // Delete Firebase Auth accounts
+ await Promise.all(profileIds.map(userId =>
+ admin.auth().deleteUser(userId)
+ ));
+
+ return { success: true, message: 'Family deleted successfully' };
+
+ } catch (error) {
+ console.error('Error deleting family:', error);
+ throw new functions.https.HttpsError('internal', 'Error deleting family data');
+ }
+ });
\ No newline at end of file
diff --git a/hooks/firebase/useDeleteFamily.ts b/hooks/firebase/useDeleteFamily.ts
new file mode 100644
index 0000000..4348ef9
--- /dev/null
+++ b/hooks/firebase/useDeleteFamily.ts
@@ -0,0 +1,32 @@
+import { useAuthContext } from "@/contexts/AuthContext";
+import { useMutation } from "@tanstack/react-query";
+import functions from '@react-native-firebase/functions';
+import { Alert } from 'react-native';
+
+export const useDeleteFamily = () => {
+ const { user } = useAuthContext();
+
+ return useMutation({
+ mutationKey: ["deleteFamily"],
+ mutationFn: async ({ familyId }: { familyId: string }) => {
+ if (!user) {
+ throw new Error('User must be logged in');
+ }
+
+ try {
+ const deleteFamilyFunction = functions().httpsCallable('deleteFamily');
+ const result = await deleteFamilyFunction({ familyId });
+ return result.data;
+ } catch (error: any) {
+ if (error.code === 'permission-denied') {
+ Alert.alert('Error', 'Only parents can delete families');
+ } else if (error.code === 'unauthenticated') {
+ Alert.alert('Error', 'Please log in to perform this action');
+ } else {
+ Alert.alert('Error', 'Failed to delete family. Please try again.');
+ }
+ throw error;
+ }
+ }
+ });
+};
\ No newline at end of file
diff --git a/patches/react-native-big-calendar+4.15.1.patch b/patches/react-native-big-calendar+4.15.1.patch
deleted file mode 100644
index ffd45df..0000000
--- a/patches/react-native-big-calendar+4.15.1.patch
+++ /dev/null
@@ -1,194 +0,0 @@
-diff --git a/node_modules/react-native-big-calendar/build/index.js b/node_modules/react-native-big-calendar/build/index.js
-index 848ceba..f326b8e 100644
---- a/node_modules/react-native-big-calendar/build/index.js
-+++ b/node_modules/react-native-big-calendar/build/index.js
-@@ -9,6 +9,17 @@ var isoWeek = require('dayjs/plugin/isoWeek');
- var React = require('react');
- var reactNative = require('react-native');
- var calendarize = require('calendarize');
-+var {
-+ startOfDay,
-+ endOfDay,
-+ startOfWeek,
-+ isAfter,
-+ isBefore,
-+ isSameDay,
-+ differenceInDays,
-+ add,
-+ getTime
-+} = require('date-fns');
-
- function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
-
-@@ -1000,87 +1011,91 @@ function _CalendarBodyForMonthView(_a) {
- var start = _a.start, end = _a.end;
- return day.isBetween(dayjs__default["default"](start).startOf('day'), dayjs__default["default"](end).endOf('day'), null, '[)');
- });
-- }
-- else {
-- /**
-- * Better way to sort overlapping events that spans accross multiple days
-- * For example, if you want following events
-- * Event 1, start = 01/01 12:00, end = 02/01 12:00
-- * Event 2, start = 02/01 12:00, end = 03/01 12:00
-- * Event 3, start = 03/01 12:00, end = 04/01 12:00
-- *
-- * When drawing calendar in month view, event 3 should be placed at 3rd index for 03/01, because Event 2 are placed at 2nd index for 02/01 and 03/01
-- *
-- */
-- var min_1 = day.startOf('day'), max_1 = day.endOf('day');
-- //filter all events that starts from the current week until the current day, and sort them by reverse starting time
-- var filteredEvents_1 = events
-- .filter(function (_a) {
-- var start = _a.start, end = _a.end;
-- return dayjs__default["default"](end).isAfter(day.startOf('week')) && dayjs__default["default"](start).isBefore(max_1);
-- })
-- .sort(function (a, b) {
-- if (dayjs__default["default"](a.start).isSame(b.start, 'day')) {
-- var aDuration = dayjs__default["default"].duration(dayjs__default["default"](a.end).diff(dayjs__default["default"](a.start))).days();
-- var bDuration = dayjs__default["default"].duration(dayjs__default["default"](b.end).diff(dayjs__default["default"](b.start))).days();
-- return aDuration - bDuration;
-- }
-- return b.start.getTime() - a.start.getTime();
-+ } else {
-+ // Convert day once and cache all the commonly used dates/times
-+ const jsDay = day?.toDate?.() || day;
-+ const weekStart = startOfWeek(jsDay);
-+ var min_1 = startOfDay(jsDay);
-+ var max_1 = endOfDay(jsDay);
-+ const max1Time = getTime(max_1);
-+ const min1Time = getTime(min_1);
-+
-+ // Pre-process events with dates and cache timestamps
-+ const processedEvents = events.map(event => {
-+ const startDay = event.start?.toDate?.() || new Date(event.start);
-+ const endDay = event.end?.toDate?.() || new Date(event.end);
-+ return {
-+ ...event,
-+ startDay,
-+ endDay,
-+ startTime: getTime(startDay),
-+ endTime: getTime(endDay),
-+ startDayStart: startOfDay(startDay)
-+ };
- });
-- /**
-- * find the most relevant min date to filter the events
-- * in the example:
-- * 1. when rendering for 01/01, min date will be 01/01 (start of day for event 1)
-- * 2. when rendering for 02/01, min date will be 01/01 (start of day for event 1)
-- * 3. when rendering for 03/01, min date will be 01/01 (start of day for event 1)
-- * 4. when rendering for 04/01, min date will be 01/01 (start of day for event 1)
-- * 5. when rendering for 05/01, min date will be 05/01 (no event overlaps with 05/01)
-- */
-- filteredEvents_1.forEach(function (_a) {
-- var start = _a.start, end = _a.end;
-- if (dayjs__default["default"](end).isAfter(min_1) && dayjs__default["default"](start).isBefore(min_1)) {
-- min_1 = dayjs__default["default"](start).startOf('day');
-+
-+ // Filter events within the weekly range and sort by reverse start time
-+ let filteredEvents_1 = processedEvents
-+ .filter(({ startTime, endTime }) =>
-+ endTime > getTime(weekStart) && startTime < max1Time
-+ )
-+ .sort((a, b) => {
-+ if (isSameDay(a.startDay, b.startDay)) {
-+ // Pre-calculate durations since they're used in sorting
-+ const aDuration = differenceInDays(a.endDay, a.startDay);
-+ const bDuration = differenceInDays(b.endDay, b.startDay);
-+ return bDuration - aDuration;
-+ }
-+ return b.startTime - a.startTime;
-+ });
-+
-+ // Update min_1 to the earliest startDay for overlapping events
-+ for (const event of filteredEvents_1) {
-+ if (event.endTime > min1Time && event.startTime < min1Time) {
-+ min_1 = event.startDayStart;
-+ break; // We only need the first one due to the sort order
- }
-- });
-+ }
-+
-+ // Filter to keep only events that overlap the min to max range, then reverse
-+ const min1TimeUpdated = getTime(min_1);
- filteredEvents_1 = filteredEvents_1
-- .filter(function (_a) {
-- var start = _a.start, end = _a.end;
-- return dayjs__default["default"](end).endOf('day').isAfter(min_1) && dayjs__default["default"](start).isBefore(max_1);
-- })
-+ .filter(({ startTime, endDay }) =>
-+ getTime(endOfDay(endDay)) > min1TimeUpdated && startTime < max1Time
-+ )
- .reverse();
-- /**
-- * We move eligible event to the top
-- * For example, when rendering for 03/01, Event 3 should be moved to the top, since there is a gap left by Event 1
-- */
-- var finalEvents_1 = [];
-- var tmpDay_1 = day.startOf('week');
-- //re-sort events from the start of week until the calendar cell date
-- //optimize sorting of event nodes and make sure that no empty gaps are left on top of calendar cell
-- while (!tmpDay_1.isAfter(day)) {
-- filteredEvents_1.forEach(function (event) {
-- if (dayjs__default["default"](event.end).isBefore(tmpDay_1.startOf('day'))) {
-- var eventToMoveUp = filteredEvents_1.find(function (e) {
-- return dayjs__default["default"](e.start).startOf('day').isSame(tmpDay_1.startOf('day'));
-- });
-- if (eventToMoveUp != undefined) {
-- //remove eventToMoveUp from finalEvents first
-- if (finalEvents_1.indexOf(eventToMoveUp) > -1) {
-- finalEvents_1.splice(finalEvents_1.indexOf(eventToMoveUp), 1);
-- }
-- if (finalEvents_1.indexOf(event) > -1) {
-- finalEvents_1.splice(finalEvents_1.indexOf(event), 1, eventToMoveUp);
-- }
-- else {
-+
-+ // Move eligible events to the top, preventing duplicate entries
-+ const finalEvents_1 = [];
-+ const seenEvents = new Set(); // Use Set for faster lookups
-+ let tmpDay_1 = weekStart;
-+
-+ while (!isAfter(tmpDay_1, jsDay)) {
-+ const tmpDayTime = getTime(tmpDay_1);
-+
-+ for (const event of filteredEvents_1) {
-+ const eventEndDayTime = getTime(startOfDay(event.endDay));
-+
-+ if (!seenEvents.has(event)) {
-+ if (eventEndDayTime < tmpDayTime) {
-+ // Find event starting on tmpDay
-+ const eventToMoveUp = filteredEvents_1.find(e =>
-+ isSameDay(e.startDayStart, tmpDay_1)
-+ );
-+ if (eventToMoveUp && !seenEvents.has(eventToMoveUp)) {
- finalEvents_1.push(eventToMoveUp);
-+ seenEvents.add(eventToMoveUp);
- }
-+ } else {
-+ finalEvents_1.push(event);
-+ seenEvents.add(event);
- }
- }
-- else if (finalEvents_1.indexOf(event) == -1) {
-- finalEvents_1.push(event);
-- }
-- });
-- tmpDay_1 = tmpDay_1.add(1, 'day');
-+ }
-+ tmpDay_1 = add(tmpDay_1, { days: 1 });
- }
-+
- return finalEvents_1;
- }
- }, [events, sortedMonthView]);
-@@ -1311,7 +1326,7 @@ function _CalendarHeader(_a) {
- !stringHasContent(dayHeaderHighlightColor) &&
- u['mt-6'],
- ] }, date.format('D')))),
-- showAllDayEventCell ? (React__namespace.createElement(reactNative.View, { style: [
-+ showAllDayEventCell ? (React__namespace.createElement(reactNative.ScrollView, { style: [
- u['border-l'],
- { borderColor: theme.palette.gray['200'] },
- { height: cellHeight },