Skip to content

Commit d156d27

Browse files
feat: edit profile page
1 parent 4c6d2bd commit d156d27

File tree

7 files changed

+109
-49
lines changed

7 files changed

+109
-49
lines changed
-120 KB
Binary file not shown.
2.8 KB
Loading

src/app/(tabs)/_layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export default function TabLayout() {
4444

4545
// we only redirect when userExists is not null (we have acc checked and set the value)
4646
if (status === 'signIn' && userExists === false) {
47-
return <Redirect href="/auth/create-profile" />;
47+
return <Redirect href="/auth/create-edit-profile?mode=create" />;
4848
}
4949

5050
return (

src/app/(tabs)/settings.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* eslint-disable react/react-in-jsx-scope */
22
import { Env } from '@env';
3+
import { useRouter } from 'expo-router';
34
import { useState } from 'react';
45
import { Linking } from 'react-native';
56

@@ -11,6 +12,7 @@ import { Button, Header, ScreenContainer, ScrollView, View } from '@/ui';
1112

1213
export default function Settings() {
1314
const signOut = useAuth.use.signOut();
15+
const router = useRouter();
1416
const [isLoggingOut, setIsLoggingOut] = useState(false);
1517
const handleLogout = async () => {
1618
setIsLoggingOut(true);
@@ -22,6 +24,7 @@ export default function Settings() {
2224
setIsLoggingOut(false);
2325
}
2426
};
27+
2528
return (
2629
<ScreenContainer>
2730
<Header title="Settings" />
@@ -92,7 +95,20 @@ export default function Settings() {
9295
/>
9396
</ItemsContainer>
9497

95-
<View className="my-8">
98+
<View className="pt-4">
99+
<Button
100+
label="Edit Profile"
101+
onPress={() =>
102+
router.push({
103+
pathname: '/auth/create-edit-profile',
104+
params: { mode: 'edit' },
105+
})
106+
}
107+
variant="item"
108+
/>
109+
</View>
110+
111+
<View className="py-4">
96112
<Button
97113
label="Logout"
98114
onPress={handleLogout}
Lines changed: 87 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
1-
import { ref, uploadBytes } from '@firebase/storage';
1+
import { getDownloadURL, ref, uploadBytes } from '@firebase/storage';
22
import { zodResolver } from '@hookform/resolvers/zod';
33
import * as ImagePicker from 'expo-image-picker';
4-
import { useRouter } from 'expo-router';
4+
import { useLocalSearchParams, useRouter } from 'expo-router';
55
import {
66
collection,
77
doc,
88
getDocs,
99
query,
10+
serverTimestamp,
1011
setDoc,
12+
updateDoc,
1113
where,
1214
} from 'firebase/firestore';
1315
import { Upload } from 'lucide-react-native';
14-
import { useState } from 'react';
16+
import { useEffect, useState } from 'react';
1517
import { useForm } from 'react-hook-form';
1618
import { Image, KeyboardAvoidingView } from 'react-native';
1719
import * as z from 'zod';
1820

1921
import { db, storage } from '@/api';
2022
import { useAuth } from '@/core';
2123
import { Button, ControlledInput, Header, ScreenContainer, View } from '@/ui';
24+
2225
const profileSchema = z.object({
2326
displayName: z.string().min(2, 'Display name is required'),
2427
username: z.string().min(2, 'Username is required'),
@@ -29,10 +32,30 @@ type ProfileFormType = z.infer<typeof profileSchema>;
2932
export default function CreateProfile() {
3033
const router = useRouter();
3134
const currentUid = useAuth.getState().user?.uid;
32-
const defaultProfilePic = require('/assets/images/default_profile_pic.jpg');
35+
const defaultProfilePic = require('/assets/images/default_profile_pic.png');
3336
const [profileImage, setProfileImage] = useState<string | null>(null);
3437
const [isUploading, setIsUploading] = useState(false);
35-
const [isCreatingProfile, setIsCreatingProfile] = useState(false);
38+
const [isSubmiting, setIsSubmiting] = useState(false);
39+
40+
const { mode } = useLocalSearchParams<{
41+
mode: 'edit' | 'create';
42+
}>();
43+
44+
useEffect(() => {
45+
const fetchProfilePic = async () => {
46+
if (mode !== 'edit' || !currentUid) return;
47+
48+
try {
49+
const profilePicRef = ref(storage, `profilePics/${currentUid}`);
50+
const url = await getDownloadURL(profilePicRef);
51+
setProfileImage(url);
52+
} catch (error) {
53+
console.log('No profile image found, using default.');
54+
}
55+
};
56+
57+
fetchProfilePic();
58+
}, [mode, currentUid]);
3659

3760
const { handleSubmit, control, setError } = useForm<ProfileFormType>({
3861
resolver: zodResolver(profileSchema),
@@ -77,9 +100,9 @@ export default function CreateProfile() {
77100
}
78101
};
79102

80-
const onSubmit = async (data: ProfileFormType) => {
81-
setIsCreatingProfile(true);
103+
const createProfile = async (data: ProfileFormType) => {
82104
if (!currentUid) return;
105+
setIsSubmiting(true);
83106
try {
84107
// check if username taken
85108
const usernameQuery = query(
@@ -93,7 +116,7 @@ export default function CreateProfile() {
93116
type: 'custom',
94117
message: 'This username is already taken',
95118
});
96-
setIsCreatingProfile(false);
119+
setIsSubmiting(false);
97120
return;
98121
}
99122

@@ -103,21 +126,64 @@ export default function CreateProfile() {
103126
await setDoc(userDocRef, {
104127
displayName: data.displayName,
105128
username: data.username,
106-
createdAt: new Date().toLocaleDateString('en-CA'),
129+
createdAt: serverTimestamp(),
107130
});
108131

109132
// upload profile pic to storage
110133
await uploadProfileImage();
111134

112135
router.push('/');
113136
} catch (error) {
114-
console.error('Error updating profile', error);
137+
console.error('Error creating profile', error);
115138
setError('root', {
116139
type: 'custom',
117140
message: 'Something went wrong creating profile',
118141
});
119142
} finally {
120-
setIsCreatingProfile(false);
143+
setIsSubmiting(false);
144+
}
145+
};
146+
147+
const editProfile = async (data: ProfileFormType) => {
148+
if (!currentUid) return;
149+
setIsSubmiting(true);
150+
try {
151+
// check if username taken
152+
const usernameQuery = query(
153+
collection(db, 'users'),
154+
where('username', '==', data.username),
155+
);
156+
const usernameSnapshot = await getDocs(usernameQuery);
157+
158+
if (!usernameSnapshot.empty) {
159+
setError('username', {
160+
type: 'custom',
161+
message: 'This username is already taken',
162+
});
163+
setIsSubmiting(false);
164+
return;
165+
}
166+
167+
// update user document in firestore
168+
const userDocRef = doc(db, 'users', currentUid);
169+
170+
await updateDoc(userDocRef, {
171+
displayName: data.displayName,
172+
username: data.username
173+
});
174+
175+
// update profile picture in storage
176+
await uploadProfileImage();
177+
178+
router.push('/');
179+
} catch (error) {
180+
console.error('Error updating profile', error);
181+
setError('root', {
182+
type: 'custom',
183+
message: 'Something went wrong updating profile',
184+
});
185+
} finally {
186+
setIsSubmiting(false);
121187
}
122188
};
123189

@@ -129,7 +195,9 @@ export default function CreateProfile() {
129195
keyboardVerticalOffset={10}
130196
>
131197
<View className="flex flex-1 flex-col gap-4">
132-
<Header title="Create Profile" />
198+
<Header
199+
title={mode === 'create' ? 'Create Profile' : 'Edit Profile'}
200+
/>
133201

134202
<View className="flex flex-row items-center gap-4 px-2">
135203
<Image
@@ -158,9 +226,13 @@ export default function CreateProfile() {
158226
/>
159227

160228
<Button
161-
label="Complete Profile"
162-
loading={isCreatingProfile}
163-
onPress={handleSubmit(onSubmit)}
229+
label={mode === 'create' ? 'Complete Profile' : 'Save Changes'}
230+
loading={isSubmiting}
231+
onPress={
232+
mode === 'create'
233+
? handleSubmit(createProfile)
234+
: handleSubmit(editProfile)
235+
}
164236
/>
165237
</View>
166238
</KeyboardAvoidingView>

src/app/auth/sign-up.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ export default function SignUp() {
3030
setIsLoading(true);
3131
try {
3232
await signUp(data.email, data.password);
33-
router.push('/auth/create-profile');
33+
router.push({
34+
pathname: '/auth/create-edit-profile',
35+
params: { mode: 'create' },
36+
});
3437
} catch (error: any) {
3538
console.error('Login Error:', error);
3639
setError('password', { message: 'Invalid email or password' });

src/app/settings.tsx/edit-profile.tsx

Lines changed: 0 additions & 31 deletions
This file was deleted.

0 commit comments

Comments
 (0)