edit-personal-info.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. import React, { useState } from 'react';
  2. import { ScrollView, View } from 'react-native';
  3. import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
  4. import { Formik } from 'formik';
  5. import * as yup from 'yup';
  6. import { CommonActions, useNavigation } from '@react-navigation/native';
  7. import { useQueryClient } from '@tanstack/react-query';
  8. import { Image } from 'expo-image';
  9. import {
  10. usePostGetProfileQuery,
  11. usePostSetProfileMutation,
  12. userQueryKeys,
  13. type PostSetProfileData
  14. } from '@api/user';
  15. import { useGetRegionsWithFlagQuery } from '@api/regions';
  16. import { API_HOST } from '../../../../constants';
  17. import {
  18. AvatarPicker,
  19. BigText,
  20. Header,
  21. Input,
  22. PageWrapper,
  23. Button,
  24. Loading,
  25. WarningModal
  26. } from '../../../../components';
  27. import { InputDatePicker } from '../../../../components/Calendar/InputDatePicker';
  28. import { ModalFlatList } from '../../../../components/FlatList/modal-flatlist';
  29. import { storage, StoreType } from '../../../../storage';
  30. import { Colors } from '../../../../theme';
  31. import FacebookIcon from '../../../../../assets/icons/facebook.svg';
  32. import InstagramIcon from '../../../../../assets/icons/instagram.svg';
  33. import XIcon from '../../../../../assets/icons/x(twitter).svg';
  34. import YoutubeIcon from '../../../../../assets/icons/youtube.svg';
  35. import GlobeIcon from '../../../../../assets/icons/bottom-navigation/globe.svg';
  36. import LinkIcon from '../../../../../assets/icons/link.svg';
  37. import { ButtonVariants } from 'src/types/components';
  38. import { NAVIGATION_PAGES } from 'src/types';
  39. import { useDeleteUserMutation } from '@api/app';
  40. import { useNotification } from 'src/contexts/NotificationContext';
  41. import { useMessagesStore } from 'src/stores/unreadMessagesStore';
  42. const ProfileSchema = yup.object({
  43. username: yup.string().optional(),
  44. email: yup.string().email().optional(),
  45. first_name: yup.string().optional(),
  46. last_name: yup.string().optional(),
  47. date_of_birth: yup.string().optional(),
  48. homebase: yup.number().optional(),
  49. homebase2: yup.number().nullable().optional(),
  50. bio: yup.string().optional(),
  51. f: yup.string().optional(),
  52. t: yup.string().optional(),
  53. i: yup.string().optional(),
  54. y: yup.string().optional(),
  55. www: yup.string().optional(),
  56. other: yup.string().optional()
  57. });
  58. export const EditPersonalInfo = () => {
  59. const token = storage.get('token', StoreType.STRING) as string;
  60. const uid = storage.get('uid', StoreType.STRING) as string;
  61. const { mutate: deleteUser } = useDeleteUserMutation();
  62. const { mutate: updateProfile, data: updateResponse, reset } = usePostSetProfileMutation();
  63. const navigation = useNavigation();
  64. const queryClient = useQueryClient();
  65. const { data, error } = usePostGetProfileQuery(String(token), true);
  66. const { updateNotificationStatus } = useNotification();
  67. const updateUnreadMessagesCount = useMessagesStore((state) => state.updateUnreadMessagesCount);
  68. const regions = useGetRegionsWithFlagQuery(true);
  69. const [modalInfo, setModalInfo] = useState({
  70. visible: false,
  71. type: 'confirm',
  72. message: '',
  73. action: () => {}
  74. });
  75. const openModal = (type: string, message: string, action: any) => {
  76. setModalInfo({
  77. visible: true,
  78. type,
  79. message,
  80. action
  81. });
  82. };
  83. const closeModal = () => {
  84. setModalInfo({ ...modalInfo, visible: false });
  85. };
  86. if (!data) return <Loading />;
  87. const originRegion = regions.data?.data.find((region) => region.id === data.homebase);
  88. const secondOrigin = regions.data?.data.find((region) => region.id === data.homebase2);
  89. const handleLogout = () => {
  90. storage.remove('token');
  91. storage.remove('uid');
  92. storage.remove('currentUserData');
  93. storage.remove('visitedTilesUrl');
  94. storage.remove('filterSettings');
  95. updateNotificationStatus();
  96. updateUnreadMessagesCount();
  97. navigation.dispatch(
  98. CommonActions.reset({
  99. index: 1,
  100. routes: [{ name: NAVIGATION_PAGES.WELCOME }]
  101. })
  102. );
  103. };
  104. const handleDeleteAccount = () => {
  105. deleteUser({ token }, { onSuccess: handleLogout });
  106. };
  107. return (
  108. <PageWrapper>
  109. <ScrollView showsVerticalScrollIndicator={false}>
  110. <Header label={'Edit Personal Info'} />
  111. <KeyboardAwareScrollView>
  112. <Formik
  113. validationSchema={ProfileSchema}
  114. initialValues={{
  115. username: data.username,
  116. email: data.email,
  117. first_name: data.first_name,
  118. last_name: data.last_name,
  119. date_of_birth: data.date_of_birth,
  120. homebase: data.homebase,
  121. homebase2: data.homebase2,
  122. bio: data.bio.toString(),
  123. f: data.links.f!.link,
  124. i: data.links.i!.link,
  125. t: data.links.t!.link,
  126. y: data.links.y!.link,
  127. www: data.links.www!.link,
  128. other: data.links.other!.link,
  129. photo: {
  130. type: '',
  131. uri: '',
  132. name: ''
  133. }
  134. }}
  135. onSubmit={async (values) => {
  136. const profileData: PostSetProfileData = {
  137. token: String(token),
  138. user: {
  139. username: values.username,
  140. email: values.email,
  141. first_name: values.first_name,
  142. last_name: values.last_name,
  143. date_of_birth: values.date_of_birth,
  144. homebase: values.homebase,
  145. bio: values.bio,
  146. f: values.f,
  147. i: values.i,
  148. t: values.t,
  149. y: values.y,
  150. www: values.www,
  151. other: values.other
  152. }
  153. };
  154. if (values.homebase2) {
  155. profileData.user!.homebase2 = values.homebase2;
  156. }
  157. if (values.photo.uri) {
  158. profileData.photo = {
  159. type: values.photo.type,
  160. uri: values.photo.uri,
  161. name: values.photo.uri.split('/').pop()!
  162. };
  163. }
  164. updateProfile(profileData, {
  165. onSuccess: () => {
  166. queryClient.invalidateQueries({
  167. queryKey: userQueryKeys.getProfileData(),
  168. refetchType: 'all'
  169. });
  170. queryClient.invalidateQueries({
  171. queryKey: userQueryKeys.getProfileInfoData(+uid),
  172. refetchType: 'all'
  173. });
  174. Image.clearDiskCache();
  175. Image.clearMemoryCache();
  176. navigation.goBack();
  177. }
  178. });
  179. }}
  180. >
  181. {(props) => (
  182. <View style={{ gap: 10 }}>
  183. <View style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
  184. <AvatarPicker
  185. defaultAvatar={API_HOST + '/img/avatars/' + data.avatar}
  186. selectedAvatar={(asset) => props.setFieldValue('photo', asset)}
  187. />
  188. </View>
  189. <BigText>Account</BigText>
  190. <Input
  191. editable={false}
  192. header={"Username (can't edit)"}
  193. placeholder={'Text'}
  194. inputMode={'text'}
  195. onChange={props.handleChange('username')}
  196. value={props.values.username}
  197. onBlur={props.handleBlur('username')}
  198. formikError={props.touched.username && props.errors.username}
  199. />
  200. <Input
  201. editable={false}
  202. header={"Email address (can't edit)"}
  203. placeholder={'Email'}
  204. inputMode={'email'}
  205. onChange={props.handleChange('email')}
  206. value={props.values.email}
  207. onBlur={props.handleBlur('email')}
  208. formikError={props.touched.email && props.errors.email}
  209. />
  210. <BigText>General Info</BigText>
  211. <Input
  212. header={'First name'}
  213. placeholder={'Text'}
  214. inputMode={'text'}
  215. onChange={props.handleChange('first_name')}
  216. value={props.values.first_name}
  217. onBlur={props.handleBlur('first_name')}
  218. formikError={props.touched.first_name && props.errors.first_name}
  219. />
  220. <Input
  221. header={'Last name'}
  222. placeholder={'Text'}
  223. inputMode={'text'}
  224. onChange={props.handleChange('last_name')}
  225. value={props.values.last_name}
  226. onBlur={props.handleBlur('last_name')}
  227. formikError={props.touched.last_name && props.errors.last_name}
  228. />
  229. <InputDatePicker
  230. headerTitle={'Date of birth'}
  231. defaultDate={new Date(data.date_of_birth)}
  232. selectedDate={(date) => props.setFieldValue('date_of_birth', date)}
  233. formikError={props.touched.date_of_birth && props.errors.date_of_birth}
  234. />
  235. <ModalFlatList
  236. headerTitle={'Region of origin'}
  237. defaultObject={{ name: originRegion?.name }}
  238. selectedObject={(data) => props.setFieldValue('homebase', data.id)}
  239. />
  240. <ModalFlatList
  241. headerTitle={'Second region'}
  242. defaultObject={{ name: secondOrigin?.name }}
  243. selectedObject={(data) => props.setFieldValue('homebase2', data.id)}
  244. />
  245. <Input
  246. multiline={true}
  247. header={'Bio'}
  248. placeholder={'Text'}
  249. inputMode={'text'}
  250. onChange={props.handleChange('bio')}
  251. value={props.values.bio}
  252. onBlur={props.handleBlur('bio')}
  253. formikError={props.touched.bio && props.errors.bio}
  254. />
  255. <BigText>Links</BigText>
  256. <Input
  257. icon={<FacebookIcon fill={Colors.LIGHT_GRAY} />}
  258. placeholder={'https://www.facebook.com'}
  259. inputMode={'text'}
  260. onChange={props.handleChange('f')}
  261. value={props.values.f as unknown as string}
  262. onBlur={props.handleBlur('f')}
  263. formikError={props.touched.f && props.errors.f}
  264. />
  265. <Input
  266. icon={<InstagramIcon fill={Colors.LIGHT_GRAY} />}
  267. placeholder={'https://www.instagram.com'}
  268. inputMode={'text'}
  269. onChange={props.handleChange('i')}
  270. value={props.values.i as unknown as string}
  271. onBlur={props.handleBlur('i')}
  272. formikError={props.touched.i && props.errors.i}
  273. />
  274. <Input
  275. icon={<XIcon fill={Colors.LIGHT_GRAY} />}
  276. placeholder={'https://www.twitter.com'}
  277. inputMode={'text'}
  278. onChange={props.handleChange('t')}
  279. value={props.values.t as unknown as string}
  280. onBlur={props.handleBlur('t')}
  281. formikError={props.touched.t && props.errors.t}
  282. />
  283. <Input
  284. icon={<YoutubeIcon fill={Colors.LIGHT_GRAY} />}
  285. placeholder={'https://www.youtube.com'}
  286. inputMode={'text'}
  287. onChange={props.handleChange('y')}
  288. value={props.values.y as unknown as string}
  289. onBlur={props.handleBlur('y')}
  290. formikError={props.touched.y && props.errors.y}
  291. />
  292. <Input
  293. icon={<GlobeIcon fill={Colors.LIGHT_GRAY} />}
  294. placeholder={'My Website'}
  295. inputMode={'text'}
  296. onChange={props.handleChange('www')}
  297. value={props.values.www as unknown as string}
  298. onBlur={props.handleBlur('www')}
  299. formikError={props.touched.www && props.errors.www}
  300. />
  301. <Input
  302. icon={<LinkIcon fill={Colors.LIGHT_GRAY} />}
  303. placeholder={'Other link'}
  304. inputMode={'text'}
  305. onChange={props.handleChange('other')}
  306. value={props.values.other as unknown as string}
  307. onBlur={props.handleBlur('other')}
  308. formikError={props.touched.other && props.errors.other}
  309. />
  310. <View style={{ marginTop: 15, marginBottom: 15, gap: 8 }}>
  311. <Button onPress={props.handleSubmit}>Save</Button>
  312. <Button
  313. variant={ButtonVariants.OPACITY}
  314. containerStyles={{ backgroundColor: Colors.WHITE, borderColor: Colors.RED }}
  315. textStyles={{ color: Colors.RED }}
  316. onPress={() =>
  317. openModal('confirm', 'Are you sure you want to logout?', handleLogout)
  318. }
  319. >
  320. Log out
  321. </Button>
  322. <Button
  323. variant={ButtonVariants.FILL}
  324. containerStyles={{ backgroundColor: Colors.RED }}
  325. onPress={() =>
  326. openModal(
  327. 'confirm',
  328. 'Are you sure you want to delete your account?',
  329. handleDeleteAccount
  330. )
  331. }
  332. >
  333. Delete account
  334. </Button>
  335. </View>
  336. </View>
  337. )}
  338. </Formik>
  339. </KeyboardAwareScrollView>
  340. </ScrollView>
  341. <WarningModal
  342. isVisible={modalInfo.visible}
  343. onClose={closeModal}
  344. type={modalInfo.type}
  345. message={modalInfo.message}
  346. action={() => {
  347. modalInfo.action();
  348. closeModal();
  349. }}
  350. />
  351. </PageWrapper>
  352. );
  353. };