@@ -46,29 +46,27 @@ const EditProfileScreen = () => {
4646 const { user, updateUser } = useUserStore ( ( state ) => state ) ;
4747 const [ isLoading , setLoading ] = useState ( false ) ;
4848 const [ error , setError ] = useState < string | null > ( null ) ;
49- const [ isError , setIsError ] = useState < boolean > ( false ) ;
5049 const [ websiteError , setWebsiteError ] = useState < string | null > ( null ) ;
5150 const [ birthDateError , setBirthDateError ] = useState < string | null > ( null ) ;
5251
53- const [ birthDate , setBirthDate ] = useState < string | null > ( user . birthDate || null ) ;
52+ const [ birthDate , setBirthDate ] = useState < string | null > ( user ? .birthDate || null ) ;
5453 const [ birthDateInput , setBirthDateInput ] = useState < string > (
55- formatBirthDate ( user . birthDate ) || ''
54+ formatBirthDate ( user ? .birthDate ?? null ) || ''
5655 ) ;
5756 const [ showDatePicker , setShowDatePicker ] = useState < boolean > ( false ) ;
5857
59- const [ avatarUrl , setAvatarUrl ] = useState < string | null > ( user . avatarUrl || null ) ;
60- const [ bannerUrl , setBannerUrl ] = useState < string | null > ( user . bannerUrl || null ) ;
58+ const [ avatarUrl , setAvatarUrl ] = useState < string | null > ( user ? .avatarUrl || null ) ;
59+ const [ bannerUrl , setBannerUrl ] = useState < string | null > ( user ? .bannerUrl || null ) ;
6160 const [ isEditingBanner , setIsEditingBanner ] = useState < boolean > ( false ) ;
6261 const [ menuVisible , setMenuVisible ] = useState < boolean > ( false ) ;
6362
6463 const [ formDataChanged , setFormDataChanged ] = useState < boolean > ( false ) ;
6564
6665 const [ formData , setFormData ] = useState ( {
67- displayName : user . displayName || '' ,
68- bio : user . bio || '' ,
69- location : user . location || '' ,
70- websiteUrl : user . websiteUrl || '' ,
71- birthDate : user . birthDate || null ,
66+ displayName : user ?. displayName || '' ,
67+ bio : user ?. bio || '' ,
68+ location : user ?. location || '' ,
69+ websiteUrl : user ?. websiteUrl || '' ,
7270 } ) ;
7371
7472 const openDatePicker = ( ) => {
@@ -94,13 +92,15 @@ const EditProfileScreen = () => {
9492 return ;
9593 }
9694 // format as YYYY-MM-DD
97- const newBirthDate = selectedDate . toISOString ( ) . split ( 'T' ) [ 0 ] ;
95+ const newBirthDate = selectedDate . toISOString ( ) . split ( 'T' ) [ 0 ] . trim ( ) ;
9896 setBirthDateInput ( newBirthDateInput ) ;
9997 // update in date picker
10098 setBirthDate ( newBirthDate ) ;
10199 // update form data
102- setFormData ( ( prev ) => ( { ...prev , birthDate } ) ) ;
100+ // console.log('selected date:', newBirthDate);
101+ setFormData ( ( prev ) => ( { ...prev , birthDate : newBirthDate || '' } ) ) ;
103102 setFormDataChanged ( true ) ;
103+ setBirthDateError ( null ) ;
104104 }
105105 } ;
106106
@@ -189,39 +189,34 @@ const EditProfileScreen = () => {
189189 else setWebsiteError ( null ) ;
190190 } ;
191191
192- useEffect ( ( ) => {
193- if ( websiteError || birthDateError || error ) setIsError ( true ) ;
194- else setIsError ( false ) ;
195- } , [ websiteError , birthDateError , error ] ) ;
196-
197192 const handleFormSubmit = useCallback ( async ( ) => {
198- if ( isError ) return ;
193+ /// clear previous errors
194+ setError ( null ) ;
199195
200196 try {
201- // clear any previous errors
202- setError ( null ) ;
203- setIsError ( false ) ;
204197 setLoading ( true ) ;
205198
206- // to be implemented with mock later: updates to profile media
207-
208- const request : UpdateProfileRequest = {
209- displayName : formData . displayName || '' , //fallback
210- bio : formData . bio === '' ? null : formData . bio ,
211- location : formData . location === '' ? null : formData . location ,
212- websiteUrl : formData . websiteUrl === '' ? null : formData . websiteUrl ,
213- birthDate : formData . birthDate === '' ? null : formData . birthDate ,
214- } ;
215-
216199 if ( formDataChanged ) {
200+ // to be implemented with mock later: updates to profile media
201+
202+ const request : UpdateProfileRequest = {
203+ displayName : formData . displayName . trim ( ) || '' , //fallback
204+ bio : formData . bio . trim ( ) === '' ? null : formData . bio ,
205+ location : formData . location . trim ( ) === '' ? null : formData . location ,
206+ websiteUrl : formData . websiteUrl . trim ( ) === '' ? null : formData . websiteUrl ,
207+ birthDate : birthDate === '' ? null : birthDate ,
208+ } ;
209+
210+ // console.log(request);
211+ // console.log(user);
217212 // call update profile
218213 await updateUserProfile ( request ) ;
219214
220215 // update user store
221216 updateUser ( { ...request , avatarUrl, bannerUrl } ) ;
222217
223218 // update react query cache for profile screen
224- queryClient . setQueryData ( [ 'profile' , user . username ] , ( user : UserProfile ) => ( {
219+ queryClient . setQueryData ( [ 'profile' , user ? .username ] , ( user : UserProfile ) => ( {
225220 ...user ,
226221 ...request ,
227222 birthDate : birthDate ,
@@ -236,8 +231,14 @@ const EditProfileScreen = () => {
236231 } catch {
237232 const errorMessage = 'Failed to update profile' ;
238233 setError ( errorMessage ) ;
234+ setFormDataChanged ( false ) ;
239235 } finally {
240236 setLoading ( false ) ;
237+ setError ( null ) ;
238+ setFormDataChanged ( false ) ;
239+ setLoading ( false ) ;
240+ setWebsiteError ( null ) ;
241+ setBirthDateError ( null ) ;
241242 setFormDataChanged ( false ) ;
242243 }
243244 } , [
@@ -250,9 +251,15 @@ const EditProfileScreen = () => {
250251 bannerUrl ,
251252 formDataChanged ,
252253 queryClient ,
253- isError ,
254254 ] ) ;
255255
256+ const isSaveDisabled =
257+ isLoading ||
258+ formData . displayName . trim ( ) . length === 0 ||
259+ ! ! websiteError ||
260+ ! ! birthDateError ||
261+ ! formDataChanged ;
262+
256263 useEffect ( ( ) => {
257264 navigation . setOptions ( {
258265 headerRight : ( ) => (
@@ -263,12 +270,23 @@ const EditProfileScreen = () => {
263270 onPress = { ( ) => {
264271 handleFormSubmit ( ) ;
265272 } }
266- disabled = { isLoading || formData . displayName ?. length === 0 || isError }
273+ disabled = { isSaveDisabled }
267274 testID = "save-profile-button"
268275 />
269276 ) ,
270277 } ) ;
271- } , [ navigation , handleFormSubmit , formData , isLoading , isError ] ) ;
278+ } , [ navigation , handleFormSubmit , formData , isSaveDisabled ] ) ;
279+
280+ useEffect ( ( ) => {
281+ const unsubscribe = navigation . addListener ( 'beforeRemove' , ( e ) => {
282+ if ( ! formDataChanged ) return ;
283+
284+ e . preventDefault ( ) ;
285+ // Show warning modal
286+ setMenuVisible ( true ) ;
287+ } ) ;
288+ return unsubscribe ;
289+ } , [ navigation , formDataChanged ] ) ;
272290
273291 if ( isLoading ) {
274292 return (
@@ -325,7 +343,7 @@ const EditProfileScreen = () => {
325343 value = { formData . displayName }
326344 label = "Name"
327345 onChangeText = { ( value ) => {
328- setFormData ( ( prev ) => ( { ...prev , [ 'displayName' ] : value . trim ( ) } ) ) ;
346+ setFormData ( ( prev ) => ( { ...prev , [ 'displayName' ] : value } ) ) ;
329347 setFormDataChanged ( true ) ;
330348 } }
331349 autoCapitalize = "none"
@@ -335,7 +353,7 @@ const EditProfileScreen = () => {
335353 value = { formData . bio }
336354 label = "Bio"
337355 onChangeText = { ( value ) => {
338- setFormData ( ( prev ) => ( { ...prev , [ 'bio' ] : value . trim ( ) } ) ) ;
356+ setFormData ( ( prev ) => ( { ...prev , [ 'bio' ] : value } ) ) ;
339357 setFormDataChanged ( true ) ;
340358 } }
341359 autoCapitalize = "none"
@@ -415,6 +433,45 @@ const EditProfileScreen = () => {
415433 </ Pressable >
416434 </ Modal >
417435
436+ { /* show warning modal when navigating away with unsaved changes */ }
437+ < Modal
438+ transparent
439+ visible = { menuVisible }
440+ animationType = "fade"
441+ onRequestClose = { ( ) => setMenuVisible ( false ) }
442+ >
443+ < Pressable onPress = { ( ) => setMenuVisible ( false ) } style = { styles . overlay } >
444+ < View style = { [ styles . dropdown , styles . dropDownWarning ] } >
445+ < Text
446+ className = "text-bold text-lg my-2"
447+ style = { [ styles . dropdownText , styles . dropdownWarningTitleText ] }
448+ >
449+ Edit Profile?
450+ </ Text >
451+ < Text style = { styles . dropdownText } > Discard changes?</ Text >
452+ < View className = "flex-row justify-end gap-4 mt-4" >
453+ < Pressable
454+ style = { styles . dropdownItem }
455+ onPress = { ( ) => {
456+ setMenuVisible ( false ) ;
457+ } }
458+ >
459+ < Text style = { [ styles . dropdownText , styles . dropdownWarningText ] } > Cancel</ Text >
460+ </ Pressable >
461+
462+ < Pressable
463+ style = { styles . dropdownItem }
464+ onPress = { ( ) => {
465+ navigation . goBack ( ) ;
466+ } }
467+ >
468+ < Text style = { [ styles . dropdownText , styles . dropdownWarningText ] } > Discard</ Text >
469+ </ Pressable >
470+ </ View >
471+ </ View >
472+ </ Pressable >
473+ </ Modal >
474+
418475 { Platform . OS === 'ios' && showDatePicker && (
419476 < Modal transparent animationType = "slide" visible = { showDatePicker } >
420477 < View className = "flex-1 justify-end" >
@@ -512,5 +569,15 @@ function getStyles(theme: 'dark' | 'light') {
512569 errorContainer : {
513570 color : colors [ theme ] . destructive ,
514571 } ,
572+ dropdownWarningText : {
573+ fontWeight : 'bold' ,
574+ } ,
575+ dropdownWarningTitleText : {
576+ fontWeight : 'bold' ,
577+ fontSize : 18 ,
578+ } ,
579+ dropDownWarning : {
580+ paddingHorizontal : 20 ,
581+ } ,
515582 } ) ;
516583}
0 commit comments