mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React, {useState} from 'react'
2import * as Toast from '../util/Toast'
3import {
4 ActivityIndicator,
5 StyleSheet,
6 TouchableOpacity,
7 View,
8} from 'react-native'
9import LinearGradient from 'react-native-linear-gradient'
10import {BottomSheetScrollView, BottomSheetTextInput} from '@gorhom/bottom-sheet'
11import {Image as PickedImage} from 'react-native-image-crop-picker'
12import {Text} from '../util/text/Text'
13import {ErrorMessage} from '../util/error/ErrorMessage'
14import {useStores} from '../../../state'
15import {ProfileViewModel} from '../../../state/models/profile-view'
16import {s, colors, gradients} from '../../lib/styles'
17import {
18 enforceLen,
19 MAX_DISPLAY_NAME,
20 MAX_DESCRIPTION,
21} from '../../../lib/strings'
22import {isNetworkError} from '../../../lib/errors'
23import {compressIfNeeded} from '../../../lib/images'
24import {UserBanner} from '../util/UserBanner'
25import {UserAvatar} from '../util/UserAvatar'
26
27export const snapPoints = ['80%']
28
29export function Component({
30 profileView,
31 onUpdate,
32}: {
33 profileView: ProfileViewModel
34 onUpdate?: () => void
35}) {
36 const store = useStores()
37 const [error, setError] = useState<string>('')
38 const [isProcessing, setProcessing] = useState<boolean>(false)
39 const [displayName, setDisplayName] = useState<string>(
40 profileView.displayName || '',
41 )
42 const [description, setDescription] = useState<string>(
43 profileView.description || '',
44 )
45 const [userBanner, setUserBanner] = useState<string | undefined>(
46 profileView.banner,
47 )
48 const [userAvatar, setUserAvatar] = useState<string | undefined>(
49 profileView.avatar,
50 )
51 const [newUserBanner, setNewUserBanner] = useState<PickedImage | undefined>()
52 const [newUserAvatar, setNewUserAvatar] = useState<PickedImage | undefined>()
53 const onPressCancel = () => {
54 store.shell.closeModal()
55 }
56 const onSelectNewAvatar = async (img: PickedImage) => {
57 try {
58 const finalImg = await compressIfNeeded(img, 300000)
59 setNewUserAvatar(finalImg)
60 setUserAvatar(finalImg.path)
61 } catch (e: any) {
62 setError(e.message || e.toString())
63 }
64 }
65 const onSelectNewBanner = async (img: PickedImage) => {
66 try {
67 const finalImg = await compressIfNeeded(img, 500000)
68 setNewUserBanner(finalImg)
69 setUserBanner(finalImg.path)
70 } catch (e: any) {
71 setError(e.message || e.toString())
72 }
73 }
74 const onPressSave = async () => {
75 setProcessing(true)
76 if (error) {
77 setError('')
78 }
79 try {
80 await profileView.updateProfile(
81 {
82 displayName,
83 description,
84 },
85 newUserAvatar,
86 newUserBanner,
87 )
88 Toast.show('Profile updated')
89 onUpdate?.()
90 store.shell.closeModal()
91 } catch (e: any) {
92 if (isNetworkError(e)) {
93 setError(
94 'Failed to save your profile. Check your internet connection and try again.',
95 )
96 } else {
97 setError(e.message)
98 }
99 }
100 setProcessing(false)
101 }
102
103 return (
104 <View style={s.flex1}>
105 <BottomSheetScrollView style={styles.inner}>
106 <Text style={styles.title}>Edit my profile</Text>
107 <View style={styles.photos}>
108 <UserBanner
109 banner={userBanner}
110 onSelectNewBanner={onSelectNewBanner}
111 handle={profileView.handle}
112 />
113 <View style={styles.avi}>
114 <UserAvatar
115 size={80}
116 avatar={userAvatar}
117 handle={profileView.handle}
118 onSelectNewAvatar={onSelectNewAvatar}
119 displayName={profileView.displayName}
120 />
121 </View>
122 </View>
123 {error !== '' && (
124 <View style={styles.errorContainer}>
125 <ErrorMessage message={error} />
126 </View>
127 )}
128 <View>
129 <Text style={styles.label}>Display Name</Text>
130 <BottomSheetTextInput
131 style={styles.textInput}
132 placeholder="e.g. Alice Roberts"
133 placeholderTextColor={colors.gray4}
134 value={displayName}
135 onChangeText={v => setDisplayName(enforceLen(v, MAX_DISPLAY_NAME))}
136 />
137 </View>
138 <View style={s.pb10}>
139 <Text style={styles.label}>Description</Text>
140 <BottomSheetTextInput
141 style={[styles.textArea]}
142 placeholder="e.g. Artist, dog-lover, and memelord."
143 placeholderTextColor={colors.gray4}
144 multiline
145 value={description}
146 onChangeText={v => setDescription(enforceLen(v, MAX_DESCRIPTION))}
147 />
148 </View>
149 {isProcessing ? (
150 <View style={[styles.btn, s.mt10, {backgroundColor: colors.gray2}]}>
151 <ActivityIndicator />
152 </View>
153 ) : (
154 <TouchableOpacity style={s.mt10} onPress={onPressSave}>
155 <LinearGradient
156 colors={[gradients.blueLight.start, gradients.blueLight.end]}
157 start={{x: 0, y: 0}}
158 end={{x: 1, y: 1}}
159 style={[styles.btn]}>
160 <Text style={[s.white, s.bold]}>Save Changes</Text>
161 </LinearGradient>
162 </TouchableOpacity>
163 )}
164 <TouchableOpacity style={s.mt5} onPress={onPressCancel}>
165 <View style={[styles.btn]}>
166 <Text style={[s.black, s.bold]}>Cancel</Text>
167 </View>
168 </TouchableOpacity>
169 </BottomSheetScrollView>
170 </View>
171 )
172}
173
174const styles = StyleSheet.create({
175 inner: {
176 padding: 14,
177 },
178 title: {
179 textAlign: 'center',
180 fontWeight: 'bold',
181 fontSize: 24,
182 marginBottom: 18,
183 },
184 label: {
185 fontWeight: 'bold',
186 paddingHorizontal: 4,
187 paddingBottom: 4,
188 marginTop: 20,
189 },
190 textInput: {
191 borderWidth: 1,
192 borderColor: colors.gray3,
193 borderRadius: 6,
194 paddingHorizontal: 14,
195 paddingVertical: 10,
196 fontSize: 16,
197 color: colors.black,
198 },
199 textArea: {
200 borderWidth: 1,
201 borderColor: colors.gray3,
202 borderRadius: 6,
203 paddingHorizontal: 12,
204 paddingTop: 10,
205 fontSize: 16,
206 color: colors.black,
207 height: 100,
208 textAlignVertical: 'top',
209 },
210 btn: {
211 flexDirection: 'row',
212 alignItems: 'center',
213 justifyContent: 'center',
214 width: '100%',
215 borderRadius: 32,
216 padding: 10,
217 marginBottom: 10,
218 },
219 avi: {
220 position: 'absolute',
221 top: 80,
222 left: 10,
223 width: 84,
224 height: 84,
225 borderWidth: 2,
226 borderRadius: 42,
227 borderColor: colors.white,
228 backgroundColor: colors.white,
229 },
230 photos: {
231 marginBottom: 36,
232 marginHorizontal: -14,
233 },
234 errorContainer: {marginTop: 20},
235})