Skip to content

Commit a896d6c

Browse files
feat: habit completion pic in storage (#163)
- `uploadImageToStorage` util function - fixed bugs in edit profile - allow you to use your username - default form values
1 parent ae0b57c commit a896d6c

File tree

4 files changed

+63
-26
lines changed

4 files changed

+63
-26
lines changed

src/api/common/firebase-utils.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { getDownloadURL, ref, uploadBytes } from 'firebase/storage';
2+
3+
import { storage } from '@/api';
4+
5+
export const uploadImageToStorage = async (
6+
uri: string, // local path from expo-image-picker
7+
path: string,
8+
): Promise<string> => {
9+
const response = await fetch(uri);
10+
const blob = await response.blob();
11+
12+
const imageRef = ref(storage, path);
13+
await uploadBytes(imageRef, blob);
14+
15+
return getDownloadURL(imageRef);
16+
};

src/api/habits/use-modify-entry.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { createMutation } from 'react-query-kit';
33
import { queryClient } from '../common';
44
import { type UserIdT } from '../users/types';
55
import { modifyHabitEntry } from './firebase-mutations';
6-
import { type AllCompletionsT, type HabitEntryT, type HabitIdT } from './types';
6+
import { type HabitEntryT, type HabitIdT } from './types';
77

8-
type Response = AllCompletionsT;
8+
type Response = void;
99
type Variables = {
1010
userId: UserIdT;
1111
habitId: HabitIdT;
@@ -21,7 +21,6 @@ export const useModifyHabitEntry = createMutation<Response, Variables, Error>({
2121
variables.date,
2222
variables.modifiedEntry,
2323
);
24-
return {} as AllCompletionsT; // Return type is required but not used
2524
},
2625
onSuccess: (_, variables) => {
2726
queryClient.invalidateQueries({

src/app/auth/create-edit-profile.tsx

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getDownloadURL, ref, uploadBytes } from '@firebase/storage';
1+
import { getDownloadURL, ref } from '@firebase/storage';
22
import { zodResolver } from '@hookform/resolvers/zod';
33
import * as ImagePicker from 'expo-image-picker';
44
import { useLocalSearchParams, useRouter } from 'expo-router';
@@ -18,7 +18,8 @@ import { useForm } from 'react-hook-form';
1818
import { Image, KeyboardAvoidingView } from 'react-native';
1919
import * as z from 'zod';
2020

21-
import { db, getUserById, storage, type UserT } from '@/api';
21+
import { db, getUserById, storage } from '@/api';
22+
import { uploadImageToStorage } from '@/api/common/firebase-utils';
2223
import { getCurrentUserId } from '@/core';
2324
import { Button, ControlledInput, Header, ScreenContainer, View } from '@/ui';
2425

@@ -34,14 +35,21 @@ export default function CreateProfile() {
3435
const currentUid = getCurrentUserId();
3536
const defaultProfilePic = require('/assets/images/default_profile_pic.png');
3637
const [profileImage, setProfileImage] = useState<string | null>(null);
37-
const [currentUserData, setCurrentUserData] = useState<UserT | null>(null);
3838
const [isUploading, setIsUploading] = useState(false);
3939
const [isSubmiting, setIsSubmiting] = useState(false);
4040

4141
const { mode } = useLocalSearchParams<{
4242
mode: 'edit' | 'create';
4343
}>();
4444

45+
const { handleSubmit, control, setError, reset } = useForm<ProfileFormType>({
46+
resolver: zodResolver(profileSchema),
47+
defaultValues: {
48+
displayName: '',
49+
username: '',
50+
},
51+
});
52+
4553
useEffect(() => {
4654
const fetchProfilePic = async () => {
4755
if (mode !== 'edit') return;
@@ -60,23 +68,18 @@ export default function CreateProfile() {
6068
try {
6169
const userData = await getUserById(currentUid);
6270
if (userData == null) return;
63-
setCurrentUserData(userData);
71+
reset({
72+
displayName: userData.displayName,
73+
username: userData.username,
74+
});
6475
} catch (error) {
6576
console.log('No user Data found.');
6677
}
6778
};
6879

6980
fetchProfilePic();
7081
getCurrentUserData();
71-
}, [mode, currentUid]);
72-
73-
const { handleSubmit, control, setError } = useForm<ProfileFormType>({
74-
resolver: zodResolver(profileSchema),
75-
defaultValues: {
76-
displayName: '',
77-
username: '',
78-
},
79-
});
82+
}, [mode, currentUid, reset]);
8083

8184
const handleImageUpload = async () => {
8285
setIsUploading(true);
@@ -102,11 +105,8 @@ export default function CreateProfile() {
102105
if (!profileImage) return;
103106

104107
try {
105-
const response = await fetch(profileImage);
106-
const blob = await response.blob();
107-
const storageRef = ref(storage, `profilePics/${currentUid}`);
108-
await uploadBytes(storageRef, blob);
109-
return;
108+
const filePath = `profilePics/${currentUid}`;
109+
await uploadImageToStorage(profileImage, filePath);
110110
} catch (error) {
111111
console.error('Failed to upload profile image:', error);
112112
return;
@@ -166,7 +166,12 @@ export default function CreateProfile() {
166166
);
167167
const usernameSnapshot = await getDocs(usernameQuery);
168168

169-
if (!usernameSnapshot.empty) {
169+
// Check if any other user has this username
170+
const usernameTakenByAnotherUser = usernameSnapshot.docs.some(
171+
(doc) => doc.id !== currentUid,
172+
);
173+
174+
if (usernameTakenByAnotherUser) {
170175
setError('username', {
171176
type: 'custom',
172177
message: 'This username is already taken',
@@ -229,13 +234,11 @@ export default function CreateProfile() {
229234
control={control}
230235
name="displayName"
231236
label="Display Name"
232-
defaultValue={currentUserData ? currentUserData.displayName : ''}
233237
/>
234238
<ControlledInput
235239
control={control}
236240
name="username"
237241
label="Unique Username"
238-
defaultValue={currentUserData ? currentUserData.username : ''}
239242
/>
240243

241244
<Button

src/components/modify-habit-entry/modify-entry-modal.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
type HabitEntryT,
1010
type HabitT,
1111
} from '@/api';
12+
import { uploadImageToStorage } from '@/api/common/firebase-utils';
1213
import { useModifyHabitEntry } from '@/api/habits/use-modify-entry';
1314
import { getCurrentUserId } from '@/core';
1415
import { colors, Header, Modal, type ModalMethods, Text, View } from '@/ui';
@@ -46,6 +47,7 @@ export function ModifyEntryModal({
4647
control: noteControl,
4748
handleSubmit: handleNoteSubmit,
4849
watch,
50+
setError,
4951
reset: resetForm,
5052
} = useForm<NoteFormType>({
5153
resolver: zodResolver(noteSchema),
@@ -86,15 +88,32 @@ export function ModifyEntryModal({
8688
};
8789

8890
const handleSave = handleNoteSubmit(async (data) => {
91+
let imageUrl: string | undefined = undefined;
92+
const myId = getCurrentUserId();
93+
if (image && image !== '') {
94+
// upload image to storage, store the url in firestore
95+
try {
96+
const filePath = `habitCompletions/${habit.id}_${myId}_${selectedCompletion.date}`;
97+
imageUrl = await uploadImageToStorage(image, filePath);
98+
} catch (error) {
99+
console.error('Habit Completion Image upload failed:', error);
100+
setError('note', {
101+
type: 'custom',
102+
message: 'Image upload failed. Please try again.',
103+
});
104+
return;
105+
}
106+
}
107+
89108
const modifiedEntry: HabitEntryT = {
90109
numberOfCompletions: numberOfCompletions,
91110
note: data.note !== '' ? data.note : undefined,
92-
image: image !== '' ? image : undefined,
111+
image: imageUrl,
93112
};
94113

95114
await modifyEntry.mutateAsync({
96115
habitId: habit.id,
97-
userId: getCurrentUserId(),
116+
userId: myId,
98117
date: selectedCompletion.date,
99118
modifiedEntry: modifiedEntry,
100119
});

0 commit comments

Comments
 (0)