diff --git a/app.json b/app.json index be4bb54..1380c91 100644 --- a/app.json +++ b/app.json @@ -3,7 +3,7 @@ "name": "tethr", "slug": "expo-starter", "scheme": "tethr", - "version": "1.0.0", + "version": "1.0.3", "orientation": "portrait", "icon": "./src/assets/splash-icon.png", "userInterfaceStyle": "dark", diff --git a/package-lock.json b/package-lock.json index 9079d8c..bc6b2e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "expo-linear-gradient": "~15.0.7", "expo-linking": "~8.0.9", "expo-router": "~6.0.15", + "expo-splash-screen": "~31.0.11", "expo-status-bar": "~3.0.8", "nativewind": "^4.1.23", "prettier-plugin-tailwindcss": "^0.7.1", @@ -7641,6 +7642,18 @@ "node": ">=20.16.0" } }, + "node_modules/expo-splash-screen": { + "version": "31.0.11", + "resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-31.0.11.tgz", + "integrity": "sha512-D7MQflYn/PAN3+fACSyxHO4oxZMBezllbgFdVY8roAS1gXpCy8SS6LrGHTD0VpOPEp3X4Gn7evTnXSI9nFoI5Q==", + "license": "MIT", + "dependencies": { + "@expo/prebuild-config": "^54.0.6" + }, + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-status-bar": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-3.0.8.tgz", diff --git a/package.json b/package.json index 184f0ab..5527630 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "expo-linear-gradient": "~15.0.7", "expo-linking": "~8.0.9", "expo-router": "~6.0.15", + "expo-splash-screen": "~31.0.11", "expo-status-bar": "~3.0.8", "nativewind": "^4.1.23", "prettier-plugin-tailwindcss": "^0.7.1", diff --git a/src/app/main/_layout.tsx b/src/app/main/_layout.tsx index 062d5a4..91d4950 100644 --- a/src/app/main/_layout.tsx +++ b/src/app/main/_layout.tsx @@ -1,19 +1,55 @@ import { Tabs, useSegments } from 'expo-router'; import '../../../global.css'; -import React from 'react'; import { TouchableOpacity, View } from 'react-native'; import { StatusBar } from 'expo-status-bar'; +import React, { useState, useEffect, useCallback } from 'react'; +import { getFriendsList } from '@/controllers/getFriends'; +import { completedTasksController } from '@/controllers/completeTask'; +import { groupController } from '@/controllers/group'; +import { friendRequestObserver } from '@/controllers/observers/friendRequestObserver'; import AntDesign from '@expo/vector-icons/AntDesign'; import Feather from '@expo/vector-icons/Feather'; import Ionicons from '@expo/vector-icons/Ionicons'; export default function RootLayout() { + const [incomingRequests, setIncoming] = useState(0); const segments = useSegments(); const hideTabBar = segments.includes('camera') || segments.includes('groups') || segments.includes('tasks'); + const loadFriends = useCallback(async () => { + const incomingRes = await getFriendsList.getIncomingFriendRequestsCount(); + setIncoming(incomingRes); + }, [setIncoming]); + + const initializeTaskObservers = () => { + console.log('Initializing task observers'); + completedTasksController.initialize(); + groupController.initialize(); + }; + + useEffect(() => { + loadFriends(); + }, [loadFriends]); + + useEffect(() => { + initializeTaskObservers(); + + const unsubscribe = friendRequestObserver.subscribe((data) => { + console.log('Layout observer: reduce friends badge', data); + + if (data.action === 'accept') { + setIncoming((prev) => Math.max(0, prev - 1)); + } else if (data.action === 'reject') { + setIncoming((prev) => Math.max(0, prev - 1)); + } + }); + + return () => unsubscribe(); + }, []); + return ( @@ -68,6 +104,7 @@ export default function RootLayout() { options={{ tabBarIcon: ({ color, size }) => , tabBarButton: (props) => , + tabBarBadge: incomingRequests && incomingRequests > 0 ? incomingRequests : undefined, }} /> { const fetchGroupData = async () => { try { - const allGroups = await getAllGroups.fetchUserData(); + const allGroups = await getAllGroups.fetchUserData('Choose group'); setGroups(allGroups); setFiltered(allGroups); } catch (err) { diff --git a/src/app/main/camera/chooseTask.tsx b/src/app/main/camera/chooseTask.tsx index 003267c..57f1385 100644 --- a/src/app/main/camera/chooseTask.tsx +++ b/src/app/main/camera/chooseTask.tsx @@ -80,8 +80,10 @@ const ChooseTask = () => { - Select Task for {group_name} - + + Select Task for + {group_name} + @@ -94,7 +96,7 @@ const ChooseTask = () => { return ( @@ -108,14 +110,11 @@ const ChooseTask = () => { }, }) }> - - - {task.task_name} {task.recurring ? '(Recurring)' : ''}{' '} - {task.weekly ? '(Weekly)' : ''} {taskCompleted ? '(Completed)' : ''} - - + + {task.task_name} {task.recurring ? '(Recurring)' : ''}{' '} + {task.weekly ? '(Weekly)' : ''} {taskCompleted ? '(Completed)' : ''} + ); })} diff --git a/src/app/main/camera/takePhoto.tsx b/src/app/main/camera/takePhoto.tsx index 8e354a4..f649c5e 100644 --- a/src/app/main/camera/takePhoto.tsx +++ b/src/app/main/camera/takePhoto.tsx @@ -89,6 +89,7 @@ export default function Camera() { quality: 1, base64: true, exif: true, + skipProcessing: false, }); setPhoto(takenPhoto); @@ -164,6 +165,7 @@ export default function Camera() { zoom={zoom} ref={cameraRef} mirror={facing === 'front'} + responsiveOrientationWhenOrientationLocked /> {taskName} diff --git a/src/app/main/friends.tsx b/src/app/main/friends.tsx index aaa79b0..0b7bded 100644 --- a/src/app/main/friends.tsx +++ b/src/app/main/friends.tsx @@ -14,6 +14,7 @@ import SearchBar from '@/components/searchbar'; import { useRouter } from 'expo-router'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { getCardType } from '@/utils/cardType'; +import { friendRequestObserver } from '@/controllers/observers/friendRequestObserver'; export default function FriendsScreen() { const [friends, setFriends] = useState([]); @@ -117,10 +118,21 @@ export default function FriendsScreen() { }; const handleAcceptRequest = async (friendId: string) => { try { - setFriends((prev) => prev.filter((f) => f.userId !== friendId)); + const acceptedFriend = incomingRequests.find((f) => f.userId === friendId); + setIncoming((prev) => prev.filter((f) => f.userId !== friendId)); setOutgoing((prev) => prev.filter((f) => f.userId !== friendId)); + if (acceptedFriend) { + setFriends((prev) => [...prev, { ...acceptedFriend, buttonText: 'Remove' }]); + } + + friendRequestObserver.notify({ + action: 'accept', + friendId, + username: acceptedFriend?.username, + }); + await getFriendsList.acceptRequest(friendId); } catch (error) { console.error(error); diff --git a/src/app/main/groups/[groupId]/createTask.tsx b/src/app/main/groups/[groupId]/createTask.tsx index 983e57b..ec0f0f3 100644 --- a/src/app/main/groups/[groupId]/createTask.tsx +++ b/src/app/main/groups/[groupId]/createTask.tsx @@ -42,6 +42,11 @@ const CreateTask = () => { setLoading(false); }; + const handleTaskNameChange = (text: string) => { + const filtered = text.replace(/[^a-zA-Z0-9 ]/g, ''); + setTask(filtered); + }; + if (loading) { return ( @@ -52,7 +57,8 @@ const CreateTask = () => { } const handleCreateTask = async () => { - const result = await taskController.createTask(groupId, task, recurring, weekly); + const sanitizedTask = task.replace(/[^a-zA-Z0-9 ]/g, '').trim(); + const result = await taskController.createTask(groupId, sanitizedTask, recurring, weekly); setUploading(true); if (result.success) { @@ -108,7 +114,7 @@ const CreateTask = () => { { /> - + {groupNameState} Leave Group @@ -213,7 +213,6 @@ const GroupPage = () => { {tasks.map((task, idx) => { const taskCompleted = isCompleted(task.task_name, groupId); - console.log(task); return ( (''); const [loading, setLoading] = useState(true); @@ -35,8 +39,42 @@ export default function Index() { useEffect(() => { loadPageData(); + registerHomeObserver((data) => { + console.log('Home: Observer, adding photos to relevant states', data); + + setGroupsWithPhotos((prevGroups) => + prevGroups.map((group) => { + if (group.group_id === data.groupId) { + return { + ...group, + current_points: group.current_points + 1, + photos: [ + { + name: data.photoUri.split('/').pop() || '', + publicUrl: data.photoUri, + createdAt: data.timestamp, + username: name, + taskName: data.taskName, + }, + ...group.photos, + ], + }; + } + return group; + }) + ); + }); + + return () => unregisterHomeObserver(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + useEffect(() => { + if (!loading) { + SplashScreen.hideAsync(); + } + }, [loading]); + const loadPageData = async () => { try { setLoading(true); @@ -44,7 +82,7 @@ export default function Index() { const userName = await userController.getName(); if (userName) setName(userName); - const allGroups = await getAllGroups.fetchUserData(); + const allGroups = await getAllGroups.fetchUserData('Home'); const groupIds = allGroups.map((g) => g.group_id); diff --git a/src/components/camera/photopreview.tsx b/src/components/camera/photopreview.tsx index cadef32..e15447c 100644 --- a/src/components/camera/photopreview.tsx +++ b/src/components/camera/photopreview.tsx @@ -1,12 +1,11 @@ import { CameraCapturedPicture } from 'expo-camera'; import { TouchableOpacity, Image, View, ActivityIndicator, Text } from 'react-native'; import { router } from 'expo-router'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; +import { SaveFormat, useImageManipulator } from 'expo-image-manipulator'; import Tethr from '@/components/tethr'; import { storagePush } from '@/controllers/photoUpload'; import { userController } from '@/controllers/userInfo'; -import { completedTasksController } from '@/controllers/completeTask'; -import { groupController } from '@/controllers/group'; import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons'; @@ -26,11 +25,58 @@ const PhotoPreview = ({ weekly: boolean; }) => { const [uploading, setUploading] = useState(false); + const [correctedPhoto, setCorrectedPhoto] = useState(photo.uri); + const [processing, setProcessing] = useState(true); + const manipulator = useImageManipulator(photo.uri); - const handleUpload = async () => { + useEffect(() => { + fixOrientation(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const fixOrientation = async () => { try { - if (!photo.base64) return; + setProcessing(true); + console.log('Photo EXIF orientation:', photo.exif?.Orientation); + + let rotation = 0; + + const exifOrientation = photo.exif?.Orientation || 6; + + if (exifOrientation === 1) { + rotation = 90; + console.log('Applying 90 rotation for landscape'); + } else if (exifOrientation === 3) { + rotation = 270; + console.log('Applying 270 rotation for landscape'); + } else if (exifOrientation === 8) { + rotation = 180; + console.log('Applying 180° rotation for upsidedown'); + } else { + console.log('No rotation needed for portrait'); + } + + if (rotation !== 0) { + manipulator.rotate(rotation); + } + + const imageRef = await manipulator.renderAsync(); + + const result = await imageRef.saveAsync({ + compress: 0.35, + format: SaveFormat.JPEG, + }); + setCorrectedPhoto(result.uri); + } catch (error) { + console.error('Error fixing orientation:', error); + setCorrectedPhoto(photo.uri); + } + setProcessing(false); + }; + + const handleUpload = async () => { + try { setUploading(true); const userId = await userController.getId(); @@ -39,21 +85,21 @@ const PhotoPreview = ({ return; } - await completedTasksController.addTask(task_name, group_id, weekly); - await groupController.increaseMemberScore(userId, group_id); - - storagePush.uploadImage({ - uri: photo.uri, + await storagePush.uploadImage({ + uri: correctedPhoto, userId: userId, groupId: group_id, taskName: task_name, + weekly: weekly, }); + handleRetakePhoto(); - router.push('/'); + router.replace('/'); } catch (err: any) { console.error('Upload error:', err); + } finally { + setUploading(false); } - setUploading(false); }; return ( @@ -65,7 +111,7 @@ const PhotoPreview = ({ + disabled={uploading || processing}> - - - - {task_name} - - - {uploading && ( - + {processing ? ( + + Processing Photo! + ) : ( + <> + + + + {task_name} + + + {uploading && ( + + + Uploading Photo! + + )} + )} Post To {group_name} diff --git a/src/components/exploreUI.tsx b/src/components/exploreUI.tsx index bdfb9d6..2ac08fa 100644 --- a/src/components/exploreUI.tsx +++ b/src/components/exploreUI.tsx @@ -1,7 +1,11 @@ import { View, FlatList, ActivityIndicator, Text, RefreshControl } from 'react-native'; -import { useState, useEffect, useMemo } from 'react'; +import { useState, useEffect, useMemo, useRef } from 'react'; import { photoRetrieve, PhotoSubmission } from '@/controllers/photoRetrieve'; -import { getAllGroups } from '@/controllers/group'; +import { groupController } from '@/controllers/group'; +import { + registerExploreObserver, + unregisterExploreObserver, +} from '@/controllers/observers/uiObservers'; import Tethr from '@/components/tethr'; import Fyp from '@/components/fyp'; @@ -16,14 +20,21 @@ export default function ExploreUI() { const [refreshing, setRefreshing] = useState(false); const [photos, setPhotos] = useState([]); const [searchQuery, setSearchQuery] = useState(''); + const groupsMapRef = useRef>({}); const loadPhotos = async () => { try { setLoading(true); - const allGroups = await getAllGroups.fetchUserData(); + const allGroups = await groupController.fetchUserData('EXPLORE_SCREEN'); const groupIds = allGroups.map((g) => g.group_id); + const gMap: Record = {}; + allGroups.forEach((g) => { + gMap[g.group_id] = g.group_name; + }); + groupsMapRef.current = gMap; + if (groupIds.length > 0) { const allPhotos = await photoRetrieve.getPhotosByGroups(groupIds); const photosWithGroupNames = allPhotos.map((photo) => { @@ -53,8 +64,26 @@ export default function ExploreUI() { useEffect(() => { loadPhotos(); - }, []); + registerExploreObserver((data) => { + console.log('Explore: Observer, adding photos to relevant states...', data); + + const newPhoto: PhotoWithGroup = { + name: data.photoUri.split('/').pop() || '', + publicUrl: data.photoUri, + createdAt: data.timestamp, + groupId: data.groupId, + userId: data.userId, + username: 'You', + taskName: data.taskName, + groupName: groupsMapRef.current[data.groupId] || 'Unknown Group', + }; + + setPhotos((prev) => [newPhoto, ...prev]); + }); + + return () => unregisterExploreObserver(); + }, []); const filteredPhotos = useMemo(() => { if (!searchQuery.trim()) return photos; @@ -83,11 +112,11 @@ export default function ExploreUI() { data={filteredPhotos} keyExtractor={(item) => item.name} renderItem={({ item }) => { - console.log('Photo item:', { - groupId: item.groupId, - groupName: item.groupName, - taskName: item.taskName, - }); + // console.log('Photo item:', { + // groupId: item.groupId, + // groupName: item.groupName, + // taskName: item.taskName, + // }); return ( diff --git a/src/components/groups/circleProgress.tsx b/src/components/groups/circleProgress.tsx index 2c91c83..a4c1d33 100644 --- a/src/components/groups/circleProgress.tsx +++ b/src/components/groups/circleProgress.tsx @@ -37,7 +37,7 @@ const CircularProgress = ({ percentage }: CircularProgressProps) => { /> - {percentage}% + {percentage}% ); diff --git a/src/controllers/completeTask.ts b/src/controllers/completeTask.ts index 111f000..e8dc236 100644 --- a/src/controllers/completeTask.ts +++ b/src/controllers/completeTask.ts @@ -1,4 +1,8 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; +import { + taskCompletionObserver, + TaskCompletionData, +} from '@/controllers/observers/taskCompletionObserver'; const getWeekKey = () => { const now = new Date(); @@ -8,6 +12,16 @@ const getWeekKey = () => { }; export const completedTasksController = { + initialized: false, + initialize: () => { + if (completedTasksController.initialized) return; + completedTasksController.initialized = true; + taskCompletionObserver.subscribe(async (data: TaskCompletionData) => { + console.log('Tasks: Observer, storing completion in local data...'); + await completedTasksController.addTask(data.taskName, data.groupId, data.weekly); + }); + }, + addTask: async (taskName: string, groupId: string, weekly: boolean) => { try { const taskKey = `${groupId}-${taskName}`; diff --git a/src/controllers/getFriends.ts b/src/controllers/getFriends.ts index 6e53e23..4dbaa94 100644 --- a/src/controllers/getFriends.ts +++ b/src/controllers/getFriends.ts @@ -107,6 +107,24 @@ class GetFriendController { return requests; } + async getIncomingFriendRequestsCount(): Promise { + const { + data: { user }, + error: authError, + } = await supabase.auth.getUser(); + if (!user) throw authError; + + const userId = user.id; + const { data, error } = await supabase + .from(this.friendRequestsTableName) + .select('sender_id, users!sender_id(user_id, username)') + .eq('recipient_id', userId); + + if (error) throw error; + + return data.length; + } + async getOutgoingFriendRequests(): Promise { const { data: { user }, diff --git a/src/controllers/group.ts b/src/controllers/group.ts index 91ea39d..2a1c5ca 100644 --- a/src/controllers/group.ts +++ b/src/controllers/group.ts @@ -1,4 +1,8 @@ import { supabase } from '@/lib/supabase'; +import { + taskCompletionObserver, + TaskCompletionData, +} from '@/controllers/observers/taskCompletionObserver'; interface GroupType { group_id: string; @@ -14,6 +18,19 @@ interface LeaderboardEntry { } class GroupController { + private initialized = false; + + initialize() { + if (this.initialized) { + console.log('Reinitialization check: already done'); + return; + } + this.initialized = true; + taskCompletionObserver.subscribe(async (data: TaskCompletionData) => { + console.log('Scores: Observer, increasing relevant scores...'); + await this.increaseMemberScore(data.userId, data.groupId); + }); + } async getGroupName(groupId: string): Promise { const { data: group, error } = await supabase .from('groups') @@ -25,17 +42,16 @@ class GroupController { return group?.group_name || null; } - async fetchUserData(): Promise { + async fetchUserData(source?: string): Promise { + console.log(`fetchUserData called by: ${source || 'unknown'}`); try { const { data: sessionData, error: sessionError } = await supabase.auth.getSession(); if (sessionError) { - console.error('Error fetching session:', sessionError); return []; } const user = sessionData?.session?.user; if (!user) { - console.log('No user logged in.'); return []; } @@ -45,20 +61,17 @@ class GroupController { .eq('user_id', user.id); if (groupError) console.error('Error fetching groups:', groupError); - else { - const formattedGroups: GroupType[] = (groupData || []).map((item: any) => ({ - group_id: item.group_id, - group_name: item.groups?.group_name || 'N/A Group Name', - current_points: item.current_points, - })); - - return formattedGroups; - } + const formattedGroups: GroupType[] = (groupData || []).map((item: any) => ({ + group_id: item.group_id, + group_name: item.groups?.group_name || 'N/A Group Name', + current_points: item.current_points, + })); + + return formattedGroups; } catch (err) { console.error('Unexpected error fetching data:', err); return []; } - return []; } async getLeaderboardData(groupId: string): Promise { @@ -253,4 +266,4 @@ class GroupController { } export const groupController = new GroupController(); -export const getAllGroups = new GroupController(); +export const getAllGroups = groupController; diff --git a/src/controllers/observers/friendRequestObserver.ts b/src/controllers/observers/friendRequestObserver.ts new file mode 100644 index 0000000..2940904 --- /dev/null +++ b/src/controllers/observers/friendRequestObserver.ts @@ -0,0 +1,29 @@ +export interface FriendRequestData { + action: 'accept' | 'reject' | 'cancel'; + friendId: string; + username?: string; +} + +class FriendRequestObserver { + private observers: ((data: FriendRequestData) => void)[] = []; + + subscribe(observer: (data: FriendRequestData) => void) { + this.observers.push(observer); + return () => { + this.observers = this.observers.filter((obs) => obs !== observer); + }; + } + + notify(data: FriendRequestData) { + console.log('Notifying all friendrequest observers:', data); + this.observers.forEach((observer) => { + try { + observer(data); + } catch (error) { + console.error('Friend request observer error:', error); + } + }); + } +} + +export const friendRequestObserver = new FriendRequestObserver(); diff --git a/src/controllers/observers/taskCompletionObserver.ts b/src/controllers/observers/taskCompletionObserver.ts new file mode 100644 index 0000000..e142bdc --- /dev/null +++ b/src/controllers/observers/taskCompletionObserver.ts @@ -0,0 +1,32 @@ +class TaskCompletionObserver { + private observers: ((data: TaskCompletionData) => void)[] = []; + + subscribe(observer: (data: TaskCompletionData) => void) { + this.observers.push(observer); + return () => { + this.observers = this.observers.filter((obs) => obs !== observer); + }; + } + + notify(data: TaskCompletionData) { + console.log('Notifying all task observers:', data); + this.observers.forEach((observer) => { + try { + observer(data); + } catch (error) { + console.error('Observer error:', error); + } + }); + } +} + +export interface TaskCompletionData { + taskName: string; + groupId: string; + userId: string; + photoUri: string; + weekly: boolean; + timestamp: string; +} + +export const taskCompletionObserver = new TaskCompletionObserver(); diff --git a/src/controllers/observers/uiObservers.ts b/src/controllers/observers/uiObservers.ts new file mode 100644 index 0000000..e8608dc --- /dev/null +++ b/src/controllers/observers/uiObservers.ts @@ -0,0 +1,39 @@ +import { + taskCompletionObserver, + TaskCompletionData, +} from '@/controllers/observers/taskCompletionObserver'; + +let homeUpdateCallback: ((data: TaskCompletionData) => void) | null = null; +let exploreUpdateCallback: ((data: TaskCompletionData) => void) | null = null; + +taskCompletionObserver.subscribe((data) => { + console.log('Call to update UI:', data); + if (homeUpdateCallback) { + homeUpdateCallback(data); + } + if (exploreUpdateCallback) { + exploreUpdateCallback(data); + } +}); + +// Home screen registration +export const registerHomeObserver = (callback: (data: TaskCompletionData) => void) => { + console.log('Home observer registered'); + homeUpdateCallback = callback; +}; + +export const unregisterHomeObserver = () => { + console.log('Home observer unregistered'); + homeUpdateCallback = null; +}; + +// Explore screen registration +export const registerExploreObserver = (callback: (data: TaskCompletionData) => void) => { + console.log('Explore observer registered'); + exploreUpdateCallback = callback; +}; + +export const unregisterExploreObserver = () => { + console.log('Explore observer unregistered'); + exploreUpdateCallback = null; +}; diff --git a/src/controllers/photoUpload.ts b/src/controllers/photoUpload.ts index b736945..634461e 100644 --- a/src/controllers/photoUpload.ts +++ b/src/controllers/photoUpload.ts @@ -1,11 +1,13 @@ import { supabase } from '@/lib/supabase'; import { imageCompressor } from '@/utils/compress'; +import { taskCompletionObserver } from '@/controllers/observers/taskCompletionObserver'; export interface UploadParams { uri: string; userId: string; groupId: string; taskName: string; + weekly: boolean; } class StoragePushController { @@ -13,7 +15,7 @@ class StoragePushController { async uploadImage(params: UploadParams) { try { - const { uri, userId, groupId, taskName } = params; + const { uri, userId, groupId, taskName, weekly } = params; const compressedUri = await imageCompressor.compress(uri, { maxWidth: 1080, @@ -34,6 +36,15 @@ class StoragePushController { const { data } = supabase.storage.from(this.bucketName).getPublicUrl(fileName); + taskCompletionObserver.notify({ + taskName, + groupId, + userId, + photoUri: data.publicUrl, + weekly, + timestamp: new Date().toISOString(), + }); + return { success: true, url: data.publicUrl }; } catch (err: any) { return { success: false, error: err.message }; diff --git a/supabase/table_setup.SQL b/supabase/table_setup.SQL index 1e297cc..f8a7e93 100644 --- a/supabase/table_setup.SQL +++ b/supabase/table_setup.SQL @@ -222,8 +222,7 @@ BEGIN DELETE FROM public.tasks WHERE recurring = false AND weekly = true - AND DATE(created_at AT TIME ZONE 'America/Los_Angeles') < - DATE(NOW() AT TIME ZONE 'America/Los_Angeles') - INTERVAL '6 days'; + AND EXTRACT(DOW FROM NOW() AT TIME ZONE 'America/Los_Angeles') = 0; END; $$;