Application Android et IOS pour l'amicale des élèves https://play.google.com/store/apps/details?id=fr.amicaleinsat.application
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ProfileScreen.tsx 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. /*
  2. * Copyright (c) 2019 - 2020 Arnaud Vergnet.
  3. *
  4. * This file is part of Campus INSAT.
  5. *
  6. * Campus INSAT is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Campus INSAT is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
  18. */
  19. import * as React from 'react';
  20. import { FlatList, StyleSheet, View } from 'react-native';
  21. import {
  22. Avatar,
  23. Button,
  24. Card,
  25. Divider,
  26. List,
  27. Paragraph,
  28. withTheme,
  29. } from 'react-native-paper';
  30. import i18n from 'i18n-js';
  31. import { StackNavigationProp } from '@react-navigation/stack';
  32. import LogoutDialog from '../../components/Amicale/LogoutDialog';
  33. import MaterialHeaderButtons, {
  34. Item,
  35. } from '../../components/Overrides/CustomHeaderButton';
  36. import CardList from '../../components/Lists/CardList/CardList';
  37. import Mascot, { MASCOT_STYLE } from '../../components/Mascot/Mascot';
  38. import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
  39. import GENERAL_STYLES from '../../constants/Styles';
  40. import Urls from '../../constants/Urls';
  41. import RequestScreen from '../../components/Screens/RequestScreen';
  42. import ConnectionManager from '../../managers/ConnectionManager';
  43. import {
  44. getAmicaleServices,
  45. ServiceItemType,
  46. SERVICES_KEY,
  47. } from '../../utils/Services';
  48. type PropsType = {
  49. navigation: StackNavigationProp<any>;
  50. theme: ReactNativePaper.Theme;
  51. };
  52. type StateType = {
  53. dialogVisible: boolean;
  54. };
  55. type ClubType = {
  56. id: number;
  57. name: string;
  58. is_manager: boolean;
  59. };
  60. type ProfileDataType = {
  61. first_name: string;
  62. last_name: string;
  63. email: string;
  64. birthday: string;
  65. phone: string;
  66. branch: string;
  67. link: string;
  68. validity: boolean;
  69. clubs: Array<ClubType>;
  70. };
  71. const styles = StyleSheet.create({
  72. card: {
  73. margin: 10,
  74. },
  75. icon: {
  76. backgroundColor: 'transparent',
  77. },
  78. editButton: {
  79. marginLeft: 'auto',
  80. },
  81. mascot: {
  82. width: 60,
  83. },
  84. title: {
  85. marginLeft: 10,
  86. },
  87. });
  88. class ProfileScreen extends React.Component<PropsType, StateType> {
  89. data: ProfileDataType | undefined;
  90. flatListData: Array<{ id: string }>;
  91. amicaleDataset: Array<ServiceItemType>;
  92. constructor(props: PropsType) {
  93. super(props);
  94. this.data = undefined;
  95. this.flatListData = [{ id: '0' }, { id: '1' }, { id: '2' }, { id: '3' }];
  96. this.amicaleDataset = getAmicaleServices(props.navigation.navigate, [
  97. SERVICES_KEY.PROFILE,
  98. ]);
  99. this.state = {
  100. dialogVisible: false,
  101. };
  102. }
  103. componentDidMount() {
  104. const { navigation } = this.props;
  105. navigation.setOptions({
  106. headerRight: this.getHeaderButton,
  107. });
  108. }
  109. /**
  110. * Gets the logout header button
  111. *
  112. * @returns {*}
  113. */
  114. getHeaderButton = () => (
  115. <MaterialHeaderButtons>
  116. <Item
  117. title="logout"
  118. iconName="logout"
  119. onPress={this.showDisconnectDialog}
  120. />
  121. </MaterialHeaderButtons>
  122. );
  123. /**
  124. * Gets the main screen component with the fetched data
  125. *
  126. * @param data The data fetched from the server
  127. * @returns {*}
  128. */
  129. getScreen = (data: ProfileDataType | undefined) => {
  130. const { dialogVisible } = this.state;
  131. if (data) {
  132. this.data = data;
  133. return (
  134. <View style={GENERAL_STYLES.flex}>
  135. <CollapsibleFlatList
  136. renderItem={this.getRenderItem}
  137. data={this.flatListData}
  138. />
  139. <LogoutDialog
  140. visible={dialogVisible}
  141. onDismiss={this.hideDisconnectDialog}
  142. />
  143. </View>
  144. );
  145. } else {
  146. return <View />;
  147. }
  148. };
  149. getRenderItem = ({ item }: { item: { id: string } }) => {
  150. switch (item.id) {
  151. case '0':
  152. return this.getWelcomeCard();
  153. case '1':
  154. return this.getPersonalCard();
  155. case '2':
  156. return this.getClubCard();
  157. default:
  158. return this.getMembershipCar();
  159. }
  160. };
  161. /**
  162. * Gets the list of services available with the Amicale account
  163. *
  164. * @returns {*}
  165. */
  166. getServicesList() {
  167. return <CardList dataset={this.amicaleDataset} isHorizontal />;
  168. }
  169. /**
  170. * Gets a card welcoming the user to his account
  171. *
  172. * @returns {*}
  173. */
  174. getWelcomeCard() {
  175. const { navigation } = this.props;
  176. return (
  177. <Card style={styles.card}>
  178. <Card.Title
  179. title={i18n.t('screens.profile.welcomeTitle', {
  180. name: this.data?.first_name,
  181. })}
  182. left={() => (
  183. <Mascot
  184. style={styles.mascot}
  185. emotion={MASCOT_STYLE.COOL}
  186. animated
  187. entryAnimation={{
  188. animation: 'bounceIn',
  189. duration: 1000,
  190. }}
  191. />
  192. )}
  193. titleStyle={styles.title}
  194. />
  195. <Card.Content>
  196. <Divider />
  197. <Paragraph>{i18n.t('screens.profile.welcomeDescription')}</Paragraph>
  198. {this.getServicesList()}
  199. <Paragraph>{i18n.t('screens.profile.welcomeFeedback')}</Paragraph>
  200. <Divider />
  201. <Card.Actions>
  202. <Button
  203. icon="bug"
  204. mode="contained"
  205. onPress={() => {
  206. navigation.navigate('feedback');
  207. }}
  208. style={styles.editButton}
  209. >
  210. {i18n.t('screens.feedback.homeButtonTitle')}
  211. </Button>
  212. </Card.Actions>
  213. </Card.Content>
  214. </Card>
  215. );
  216. }
  217. /**
  218. * Gets the given field value.
  219. * If the field does not have a value, returns a placeholder text
  220. *
  221. * @param field The field to get the value from
  222. * @return {*}
  223. */
  224. static getFieldValue(field?: string): string {
  225. return field ? field : i18n.t('screens.profile.noData');
  226. }
  227. /**
  228. * Gets a list item showing personal information
  229. *
  230. * @param field The field to display
  231. * @param icon The icon to use
  232. * @return {*}
  233. */
  234. getPersonalListItem(field: string | undefined, icon: string) {
  235. const { theme } = this.props;
  236. const title = field != null ? ProfileScreen.getFieldValue(field) : ':(';
  237. const subtitle = field != null ? '' : ProfileScreen.getFieldValue(field);
  238. return (
  239. <List.Item
  240. title={title}
  241. description={subtitle}
  242. left={(props) => (
  243. <List.Icon
  244. style={props.style}
  245. icon={icon}
  246. color={field != null ? props.color : theme.colors.textDisabled}
  247. />
  248. )}
  249. />
  250. );
  251. }
  252. /**
  253. * Gets a card containing user personal information
  254. *
  255. * @return {*}
  256. */
  257. getPersonalCard() {
  258. const { theme, navigation } = this.props;
  259. return (
  260. <Card style={styles.card}>
  261. <Card.Title
  262. title={`${this.data?.first_name} ${this.data?.last_name}`}
  263. subtitle={this.data?.email}
  264. left={(iconProps) => (
  265. <Avatar.Icon
  266. size={iconProps.size}
  267. icon="account"
  268. color={theme.colors.primary}
  269. style={styles.icon}
  270. />
  271. )}
  272. />
  273. <Card.Content>
  274. <Divider />
  275. <List.Section>
  276. <List.Subheader>
  277. {i18n.t('screens.profile.personalInformation')}
  278. </List.Subheader>
  279. {this.getPersonalListItem(this.data?.birthday, 'cake-variant')}
  280. {this.getPersonalListItem(this.data?.phone, 'phone')}
  281. {this.getPersonalListItem(this.data?.email, 'email')}
  282. {this.getPersonalListItem(this.data?.branch, 'school')}
  283. </List.Section>
  284. <Divider />
  285. <Card.Actions>
  286. <Button
  287. icon="account-edit"
  288. mode="contained"
  289. onPress={() => {
  290. navigation.navigate('website', {
  291. host: Urls.websites.amicale,
  292. path: this.data?.link,
  293. title: i18n.t('screens.websites.amicale'),
  294. });
  295. }}
  296. style={styles.editButton}
  297. >
  298. {i18n.t('screens.profile.editInformation')}
  299. </Button>
  300. </Card.Actions>
  301. </Card.Content>
  302. </Card>
  303. );
  304. }
  305. /**
  306. * Gets a cars containing clubs the user is part of
  307. *
  308. * @return {*}
  309. */
  310. getClubCard() {
  311. const { theme } = this.props;
  312. return (
  313. <Card style={styles.card}>
  314. <Card.Title
  315. title={i18n.t('screens.profile.clubs')}
  316. subtitle={i18n.t('screens.profile.clubsSubtitle')}
  317. left={(iconProps) => (
  318. <Avatar.Icon
  319. size={iconProps.size}
  320. icon="account-group"
  321. color={theme.colors.primary}
  322. style={styles.icon}
  323. />
  324. )}
  325. />
  326. <Card.Content>
  327. <Divider />
  328. {this.getClubList(this.data?.clubs)}
  329. </Card.Content>
  330. </Card>
  331. );
  332. }
  333. /**
  334. * Gets a card showing if the user has payed his membership
  335. *
  336. * @return {*}
  337. */
  338. getMembershipCar() {
  339. const { theme } = this.props;
  340. return (
  341. <Card style={styles.card}>
  342. <Card.Title
  343. title={i18n.t('screens.profile.membership')}
  344. subtitle={i18n.t('screens.profile.membershipSubtitle')}
  345. left={(iconProps) => (
  346. <Avatar.Icon
  347. size={iconProps.size}
  348. icon="credit-card"
  349. color={theme.colors.primary}
  350. style={styles.icon}
  351. />
  352. )}
  353. />
  354. <Card.Content>
  355. <List.Section>
  356. {this.getMembershipItem(this.data?.validity === true)}
  357. </List.Section>
  358. </Card.Content>
  359. </Card>
  360. );
  361. }
  362. /**
  363. * Gets the item showing if the user has payed his membership
  364. *
  365. * @return {*}
  366. */
  367. getMembershipItem(state: boolean) {
  368. const { theme } = this.props;
  369. return (
  370. <List.Item
  371. title={
  372. state
  373. ? i18n.t('screens.profile.membershipPayed')
  374. : i18n.t('screens.profile.membershipNotPayed')
  375. }
  376. left={(props) => (
  377. <List.Icon
  378. style={props.style}
  379. color={state ? theme.colors.success : theme.colors.danger}
  380. icon={state ? 'check' : 'close'}
  381. />
  382. )}
  383. />
  384. );
  385. }
  386. /**
  387. * Gets a list item for the club list
  388. *
  389. * @param item The club to render
  390. * @return {*}
  391. */
  392. getClubListItem = ({ item }: { item: ClubType }) => {
  393. const { theme } = this.props;
  394. const onPress = () => {
  395. this.openClubDetailsScreen(item.id);
  396. };
  397. let description = i18n.t('screens.profile.isMember');
  398. let icon = (props: {
  399. color: string;
  400. style: {
  401. marginLeft: number;
  402. marginRight: number;
  403. marginVertical?: number;
  404. };
  405. }) => (
  406. <List.Icon color={props.color} style={props.style} icon="chevron-right" />
  407. );
  408. if (item.is_manager) {
  409. description = i18n.t('screens.profile.isManager');
  410. icon = (props) => (
  411. <List.Icon
  412. style={props.style}
  413. icon="star"
  414. color={theme.colors.primary}
  415. />
  416. );
  417. }
  418. return (
  419. <List.Item
  420. title={item.name}
  421. description={description}
  422. left={icon}
  423. onPress={onPress}
  424. />
  425. );
  426. };
  427. /**
  428. * Renders the list of clubs the user is part of
  429. *
  430. * @param list The club list
  431. * @return {*}
  432. */
  433. getClubList(list: Array<ClubType> | undefined) {
  434. if (!list) {
  435. return null;
  436. }
  437. list.sort(this.sortClubList);
  438. return (
  439. <FlatList
  440. renderItem={this.getClubListItem}
  441. keyExtractor={this.clubKeyExtractor}
  442. data={list}
  443. />
  444. );
  445. }
  446. clubKeyExtractor = (item: ClubType): string => item.name;
  447. sortClubList = (a: ClubType): number => (a.is_manager ? -1 : 1);
  448. showDisconnectDialog = () => {
  449. this.setState({ dialogVisible: true });
  450. };
  451. hideDisconnectDialog = () => {
  452. this.setState({ dialogVisible: false });
  453. };
  454. /**
  455. * Opens the club details screen for the club of given ID
  456. * @param id The club's id to open
  457. */
  458. openClubDetailsScreen(id: number) {
  459. const { navigation } = this.props;
  460. navigation.navigate('club-information', { clubId: id });
  461. }
  462. render() {
  463. return (
  464. <RequestScreen<ProfileDataType>
  465. request={() =>
  466. ConnectionManager.getInstance().authenticatedRequest('user/profile')
  467. }
  468. render={this.getScreen}
  469. />
  470. );
  471. }
  472. }
  473. export default withTheme(ProfileScreen);