dappros/ethora

View on GitHub
client-reactnative/src/Screens/Profile/ProfileScreen.tsx

Summary

Maintainability
F
3 days
Test Coverage
/*
Copyright 2019-2021 (c) Dappros Ltd, registered in England & Wales, registration number 11455432. All rights reserved.
You may not use this file except in compliance with the License.
You may obtain a copy of the License at https://github.com/dappros/ethora/blob/main/LICENSE.
*/

import React, {useState, useEffect} from 'react';
import {
  SafeAreaView,
  Text,
  View,
  TouchableOpacity,
  Linking,
} from 'react-native';
import {
  widthPercentageToDP as wp,
  heightPercentageToDP as hp,
} from 'react-native-responsive-screen';

import {Avatar, HStack, VStack} from 'native-base';

import {observer} from 'mobx-react-lite';
import Hyperlink from 'react-native-hyperlink';

import {useNavigation} from '@react-navigation/native';
import AntIcon from 'react-native-vector-icons/AntDesign';

import DocumentPicker from 'react-native-document-picker';
import {commonColors, textStyles} from '../../../docs/config';
import ProfileModal from '../../components/Modals/Profile/ProfileModal';
import {QRModal} from '../../components/Modals/QR/QRModal';
import TransactionsList from '../../components/Nft/NftTransactionList';
import {ProfileTabs} from '../../components/Profile/ProfileTabs';
import SecondaryHeader from '../../components/SecondaryHeader/SecondaryHeader';
import {showToast} from '../../components/Toast/toast';
import {httpUploadPut} from '../../config/apiService';
import {changeUserData, fileUpload} from '../../config/routesConstants';
import {pattern1, pattern2} from '../../helpers/chat/chatLinkpattern';
import openChatFromChatLink from '../../helpers/chat/openChatFromChatLink';
import {generateProfileLink} from '../../helpers/generateProfileLink';
import parseChatLink from '../../helpers/parseChatLink';
import {useStores} from '../../stores/context';
import {updateVCard} from '../../xmpp/stanzas';
import {uploadFiles} from '../../helpers/uploadFiles';

const {primaryColor, primaryDarkColor} = commonColors;

export const ProfileScreen = observer(() => {
  const {loginStore, walletStore, chatStore, apiStore} = useStores();

  const navigation = useNavigation();

  const {setOffset, setTotal, clearPaginationData, transactions} = walletStore;

  const {userAvatar, userDescription, initialData, updateUserDisplayName} =
    loginStore;

  const {firstName, lastName, walletAddress} = initialData;

  const coinData = walletStore.balance;

  const [activeTab, setActiveTab] = useState(0);
  const [activeAssetTab, setActiveAssetTab] = useState(1);

  const [itemsBalance, setItemsBalance] = useState(0);

  const [modalType, setModalType] = useState<'name' | 'description' | ''>('');
  const [modalVisible, setModalVisible] = useState(false);

  const [qrModalVisible, setQrModalVisible] = useState(false);

  const [isDescriptionEditable, setIsDescriptionEditable] = useState(false);
  const [descriptionLocal, setDescriptionLocal] = useState(userDescription);
  const [firstNameLocal, setFirstNameLocal] = useState(firstName);
  const [lastNameLocal, setLastNameLocal] = useState(lastName);
  const userAvatarLocal = userAvatar;
  const [uploadedAvatar, setUploadedAvatar] = useState({
    _id: '',
    createdAt: '',
    expiresAt: 0,
    filename: '',
    isVisible: true,
    location: '',
    locationPreview: '',
    mimetype: '',
    originalname: '',
    ownerKey: '',
    size: 0,
    updatedAt: '',
    userId: '',
  });

  useEffect(() => {
    setOffset(0);
    setTotal(0);
    walletStore.fetchOwnTransactions(walletAddress, walletStore.limit, 0);
    walletStore.fetchWalletBalance(loginStore.userToken, true);
    walletStore.getDocuments(walletAddress);

    return () => {
      clearPaginationData();
    };
  }, []);
  useEffect(() => {
    setDescriptionLocal(userDescription);
  }, [userDescription]);

  const calculateAssetsCount = () => {
    setItemsBalance(
      walletStore.nftItems.reduce(
        (acc, item) => (acc += parseFloat(item.balance.toString())),
        0,
      ),
    );
  };

  useEffect(() => {
    calculateAssetsCount();
    return () => {};
  }, [walletStore.nftItems, coinData]);

  const onNamePressed = () => {
    setModalType('name');
    setModalVisible(true);
  };

  const onDescriptionPressed = () => {
    setIsDescriptionEditable(true);
    setModalType('description');
    setModalVisible(true);
  };

  //changes the user description locally
  const onDescriptionChange = (text: string) => {
    setDescriptionLocal(text);
  };

  //when user clicks on the backdrop of the modal
  const onBackdropPress = () => {
    setFirstNameLocal(firstName);
    setLastNameLocal(lastName);
    setDescriptionLocal(userDescription);
    setIsDescriptionEditable(!isDescriptionEditable);
    setModalVisible(false);
  };

  //changes the user's profile name locally
  const onNameChange = (type: 'firstName' | 'lastName', text: string) => {
    type === 'firstName' ? setFirstNameLocal(text) : setLastNameLocal(text);
  };

  const setDescription = async () => {
    if (userAvatarLocal || descriptionLocal) {
      updateVCard(
        userAvatarLocal,
        descriptionLocal,
        firstName + ' ' + lastName,
        chatStore.xmpp,
      );
    }

    if (!descriptionLocal) {
      updateVCard(userAvatarLocal, 'No description', null, chatStore.xmpp);
    }
    const formData = new FormData();
    formData.append('description', descriptionLocal);
    await httpUploadPut(
      changeUserData,
      formData,
      loginStore.userToken,
      console.log,
    );
    setIsDescriptionEditable(false);
    setModalVisible(false);
  };

  const setNewName = () => {
    //call api to dapp server to change username
    //save in async storage
    //and then change in mobx store
    if (firstNameLocal) {
      const bodyData = {
        firstName: firstNameLocal,
        lastName: lastNameLocal,
      };
      updateVCard(
        userAvatarLocal,
        descriptionLocal,
        firstNameLocal + ' ' + lastNameLocal,
        chatStore.xmpp,
      );
      updateUserDisplayName(bodyData);
    } else {
      setFirstNameLocal(firstName);
      showToast('error', 'Error', 'First name is required', 'top');
    }
    setModalVisible(false);
  };

  const handleChatLinks = (url: string) => {
    const chatJid = parseChatLink(url);

    //argument url can be a chatlink or simple link
    //first check if url is a chat link if yes then open chatlink else open the link via browser
    if (pattern1.test(url) || pattern2.test(url)) {
      openChatFromChatLink(chatJid, walletAddress, navigation, chatStore.xmpp);
    } else {
      Linking.openURL(url);
    }
  };

  const QRPressed = () => {
    // const xmppId =
    //   loginStore.initialData.xmppUsername + '@' + apiStore.xmppDomains.DOMAIN;
    // const profileLink = `=profileLink&firstName=${firstName}&lastName=${lastName}&walletAddress=${walletAddress}&xmppId=${xmppId}`;
    setQrModalVisible(true);
  };

  const sendFiles = async (data: any) => {
    try {
      const url = fileUpload;
      const response = await uploadFiles(data, loginStore.userToken, url);
      const file = response.results[0];
      setUploadedAvatar(file);
      const formData = new FormData();
      formData.append('description', descriptionLocal);
      formData.append('file', file.location);
      await httpUploadPut(
        changeUserData,
        formData,
        loginStore.userToken,
        console.log,
      );
      updateVCard(file.location, descriptionLocal, null, chatStore.xmpp);
    } catch (error) {
      console.log(error);
    }
  };
  //change user avatar
  const onAvatarPress = async () => {
    try {
      const res = await DocumentPicker.pickSingle({
        type: [DocumentPicker.types.images],
      });
      const formData = new FormData();
      formData.append('files', {
        name: res.name,
        type: res.type,
        uri: res.uri,
      });
      sendFiles(formData);
    } catch (error) {
      console.log(error);
    }
  };
  const onTransactionNumberPress = () => {
    setActiveTab(1);
  };
  // shows profile tabs which contain documents, items... or transactions
  const loadTabContent = () => {
    if (activeTab === 0) {
      return (
        <ProfileTabs
          activeAssetTab={activeAssetTab}
          setActiveAssetTab={setActiveAssetTab}
          documents={walletStore.documents}
          collections={walletStore.collections}
          coinsItems={coinData}
          userWalletAddress={walletAddress}
          nftItems={walletStore.nftItems}
          itemsBalance={itemsBalance}
        />
      );
    }

    if (activeTab === 1) {
      return (
        <View style={{paddingBottom: hp('46%')}}>
          <TransactionsList
            transactions={transactions}
            walletAddress={walletAddress}
            onEndReached={() => {
              if (transactions.length < walletStore.total) {
                walletStore.fetchOwnTransactions(
                  walletAddress,
                  walletStore.limit,
                  walletStore.offset,
                );
              }
            }}
          />
        </View>
      );
    }
  };
  return (
    <SafeAreaView style={{flex: 1, backgroundColor: 'white'}}>
      <View style={{backgroundColor: primaryDarkColor, flex: 1}}>
        <SecondaryHeader
          title={"User's profile"}
          isQR
          onQRPressed={QRPressed}
          onBackPress={() =>
            activeTab === 1 ? setActiveTab(0) : navigation.goBack()
          }
        />

        <View style={{zIndex: +1, alignItems: 'center'}}>
          <HStack
            width={hp('10.46%')}
            height={hp('10.46%')}
            position={'absolute'}
            justifyContent={'center'}
            alignItems={'center'}
            bgColor={primaryColor}
            borderRadius={hp('10.46%') / 2}>
            <TouchableOpacity
              onPress={onAvatarPress}
              accessibilityLabel="Photo">
              <Avatar
                bg={commonColors.primaryColor}
                size={'xl'}
                source={
                  uploadedAvatar.location || loginStore.userAvatar
                    ? {
                        uri: uploadedAvatar.location || loginStore.userAvatar,
                      }
                    : undefined
                }>
                {firstNameLocal[0] + lastNameLocal[0]}
              </Avatar>
            </TouchableOpacity>
          </HStack>
        </View>
        <VStack
          marginTop={hp('5.5%')}
          bgColor={'#FBFBFB'}
          style={{
            shadowColor: '#000',
            shadowOffset: {
              width: 0,
              height: 5,
            },
            shadowOpacity: 0.9,
            shadowRadius: 6.27,

            elevation: 5,
          }}
          borderTopLeftRadius={30}
          borderTopRightRadius={30}
          height={hp('75%')}>
          <View
            style={{
              alignItems: 'center',
              marginTop: hp('5.54%'),
              backgroundColor: 'white',
            }}>
            <HStack alignItems={'center'}>
              <TouchableOpacity onPress={onNamePressed} style={{marginLeft: 5}}>
                <Text
                  style={{
                    fontSize: hp('2.216%'),
                    fontFamily: textStyles.mediumFont,
                    color: '#000000',
                  }}>
                  {firstNameLocal} {lastNameLocal}
                </Text>
              </TouchableOpacity>
              <TouchableOpacity
                accessibilityLabel="Your Transactions"
                onPress={onTransactionNumberPress}
                style={{marginLeft: 5}}>
                <Text
                  style={{
                    fontSize: hp('2.216%'),
                    fontFamily: textStyles.mediumFont,
                    color: commonColors.primaryColor,
                  }}>
                  (
                  <Text
                    style={{
                      fontSize: hp('2.216%'),
                      fontFamily: textStyles.mediumFont,
                      color: commonColors.primaryColor,
                      textDecorationLine: 'underline',
                    }}>
                    {walletStore.total}
                  </Text>
                  )
                </Text>
              </TouchableOpacity>
            </HStack>
            <HStack paddingX={wp('4%')}>
              <Hyperlink
                onPress={url => handleChatLinks(url)}
                linkStyle={{
                  color: '#2980b9',
                  fontSize: hp('1.8%'),
                  textDecorationLine: 'underline',
                }}>
                <Text
                  accessibilityLabel="Bio"
                  style={{
                    fontSize: hp('2.23%'),
                    fontFamily: textStyles.regularFont,
                    textAlign: 'center',
                    color: primaryColor,
                  }}>
                  {descriptionLocal && !isDescriptionEditable
                    ? descriptionLocal
                    : 'Add your description'}
                </Text>
                <TouchableOpacity
                  accessibilityLabel="Edit bio"
                  onPress={onDescriptionPressed}
                  style={{alignItems: 'center', margin: 10}}>
                  <AntIcon
                    name="edit"
                    color={commonColors.primaryColor}
                    size={hp('2%')}
                  />
                </TouchableOpacity>
              </Hyperlink>
            </HStack>
          </View>

          <View style={{backgroundColor: 'white'}}>{loadTabContent()}</View>
        </VStack>
      </View>
      <ProfileModal
        description={descriptionLocal}
        firstName={firstNameLocal}
        lastName={lastNameLocal}
        isDescriptionEditable={isDescriptionEditable}
        isVisible={modalVisible}
        modalType={modalType}
        onBackdropPress={onBackdropPress}
        onDescriptionChange={onDescriptionChange}
        onNameChange={onNameChange}
        setDescription={setDescription}
        setNewName={setNewName}
      />
      <QRModal
        open={qrModalVisible}
        onClose={() => setQrModalVisible(false)}
        title={'Profile'}
        link={generateProfileLink({
          firstName: firstName,
          lastName: lastName,
          walletAddress: walletAddress,
          xmppId:
            loginStore.initialData.xmppUsername +
            '@' +
            apiStore.xmppDomains.DOMAIN,
        })}
      />
    </SafeAreaView>
  );
});