Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed assets/images/default_profile_pic.jpg
Binary file not shown.
Binary file added assets/images/default_profile_pic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/app/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default function TabLayout() {

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

return (
Expand Down
18 changes: 17 additions & 1 deletion src/app/(tabs)/settings.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable react/react-in-jsx-scope */
import { Env } from '@env';
import { useRouter } from 'expo-router';
import { useState } from 'react';
import { Linking } from 'react-native';

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

export default function Settings() {
const signOut = useAuth.use.signOut();
const router = useRouter();
const [isLoggingOut, setIsLoggingOut] = useState(false);
const handleLogout = async () => {
setIsLoggingOut(true);
Expand All @@ -22,6 +24,7 @@ export default function Settings() {
setIsLoggingOut(false);
}
};

return (
<ScreenContainer>
<Header title="Settings" />
Expand Down Expand Up @@ -92,7 +95,20 @@ export default function Settings() {
/>
</ItemsContainer>

<View className="my-8">
<View className="pt-4">
<Button
label="Edit Profile"
onPress={() =>
router.push({
pathname: '/auth/create-edit-profile',
params: { mode: 'edit' },
})
}
variant="item"
/>
</View>

<View className="py-4">
<Button
label="Logout"
onPress={handleLogout}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
import { ref, uploadBytes } from '@firebase/storage';
import { getDownloadURL, ref, uploadBytes } from '@firebase/storage';
import { zodResolver } from '@hookform/resolvers/zod';
import * as ImagePicker from 'expo-image-picker';
import { useRouter } from 'expo-router';
import { useLocalSearchParams, useRouter } from 'expo-router';
import {
collection,
doc,
getDocs,
query,
serverTimestamp,
setDoc,
updateDoc,
where,
} from 'firebase/firestore';
import { Upload } from 'lucide-react-native';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { Image, KeyboardAvoidingView } from 'react-native';
import * as z from 'zod';

import { db, storage } from '@/api';
import { useAuth } from '@/core';
import { Button, ControlledInput, Header, ScreenContainer, View } from '@/ui';

const profileSchema = z.object({
displayName: z.string().min(2, 'Display name is required'),
username: z.string().min(2, 'Username is required'),
Expand All @@ -29,10 +32,30 @@ type ProfileFormType = z.infer<typeof profileSchema>;
export default function CreateProfile() {
const router = useRouter();
const currentUid = useAuth.getState().user?.uid;
const defaultProfilePic = require('/assets/images/default_profile_pic.jpg');
const defaultProfilePic = require('/assets/images/default_profile_pic.png');
const [profileImage, setProfileImage] = useState<string | null>(null);
const [isUploading, setIsUploading] = useState(false);
const [isCreatingProfile, setIsCreatingProfile] = useState(false);
const [isSubmiting, setIsSubmiting] = useState(false);

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

useEffect(() => {
const fetchProfilePic = async () => {
if (mode !== 'edit' || !currentUid) return;

try {
const profilePicRef = ref(storage, `profilePics/${currentUid}`);
const url = await getDownloadURL(profilePicRef);
setProfileImage(url);
} catch (error) {
console.log('No profile image found, using default.');
}
};

fetchProfilePic();
}, [mode, currentUid]);

const { handleSubmit, control, setError } = useForm<ProfileFormType>({
resolver: zodResolver(profileSchema),
Expand Down Expand Up @@ -77,9 +100,9 @@ export default function CreateProfile() {
}
};

const onSubmit = async (data: ProfileFormType) => {
setIsCreatingProfile(true);
const createProfile = async (data: ProfileFormType) => {
if (!currentUid) return;
setIsSubmiting(true);
try {
// check if username taken
const usernameQuery = query(
Expand All @@ -93,7 +116,7 @@ export default function CreateProfile() {
type: 'custom',
message: 'This username is already taken',
});
setIsCreatingProfile(false);
setIsSubmiting(false);
return;
}

Expand All @@ -103,21 +126,64 @@ export default function CreateProfile() {
await setDoc(userDocRef, {
displayName: data.displayName,
username: data.username,
createdAt: new Date().toLocaleDateString('en-CA'),
createdAt: serverTimestamp(),
});

// upload profile pic to storage
await uploadProfileImage();

router.push('/');
} catch (error) {
console.error('Error updating profile', error);
console.error('Error creating profile', error);
setError('root', {
type: 'custom',
message: 'Something went wrong creating profile',
});
} finally {
setIsCreatingProfile(false);
setIsSubmiting(false);
}
};

const editProfile = async (data: ProfileFormType) => {
if (!currentUid) return;
setIsSubmiting(true);
try {
// check if username taken
const usernameQuery = query(
collection(db, 'users'),
where('username', '==', data.username),
);
const usernameSnapshot = await getDocs(usernameQuery);

if (!usernameSnapshot.empty) {
setError('username', {
type: 'custom',
message: 'This username is already taken',
});
setIsSubmiting(false);
return;
}

// update user document in firestore
const userDocRef = doc(db, 'users', currentUid);

await updateDoc(userDocRef, {
displayName: data.displayName,
username: data.username
});

// update profile picture in storage
await uploadProfileImage();

router.push('/');
} catch (error) {
console.error('Error updating profile', error);
setError('root', {
type: 'custom',
message: 'Something went wrong updating profile',
});
} finally {
setIsSubmiting(false);
}
};

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

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

<Button
label="Complete Profile"
loading={isCreatingProfile}
onPress={handleSubmit(onSubmit)}
label={mode === 'create' ? 'Complete Profile' : 'Save Changes'}
loading={isSubmiting}
onPress={
mode === 'create'
? handleSubmit(createProfile)
: handleSubmit(editProfile)
}
/>
</View>
</KeyboardAvoidingView>
Expand Down
5 changes: 4 additions & 1 deletion src/app/auth/sign-up.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ export default function SignUp() {
setIsLoading(true);
try {
await signUp(data.email, data.password);
router.push('/auth/create-profile');
router.push({
pathname: '/auth/create-edit-profile',
params: { mode: 'create' },
});
} catch (error: any) {
console.error('Login Error:', error);
setError('password', { message: 'Invalid email or password' });
Expand Down
31 changes: 0 additions & 31 deletions src/app/settings.tsx/edit-profile.tsx

This file was deleted.