diff --git a/README.md b/README.md index 308e296..b849162 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,6 @@ To deploy this software on production This repository uses eslint to enforce air-bnb coding styles. # Contributing -Everyone is very welcome to contribute on the codebase of Rahat. Please reach us in [Gitter](https://gitter.im/bockies/community?utm_source=badge&utm_medium=badge&utm_content=badge) in case of any query/feedback/suggestion. +Everyone is very welcome to contribute on the codebase of Rahat. Please reach us in [Discord](https://discord.gg/AV5j2T94VR) in case of any query/feedback/suggestion. -For more information on the contributing procedure, see [Contribution](https://github.com/esatya/rahat-vendor/blob/master/CONTRIBUTING.md). +For more information on the contributing procedure, see [Contribution](https://docs.rahat.io/docs/next/Contribution-Guidelines). diff --git a/package.json b/package.json index f5a8e24..ac0954e 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", - "axios": "^0.24.0", + "axios": "^0.25.0", "bootstrap": "^4.5.1", "crypto-js": "^4.0.0", "dexie": "^3.0.3", diff --git a/src/actions/appActions.js b/src/actions/appActions.js index 694b399..3e75f1f 100644 --- a/src/actions/appActions.js +++ b/src/actions/appActions.js @@ -7,5 +7,6 @@ module.exports = { SET_SCANNED_DATA: 'set_scanned_eth_address', SET_APP_PASSCODE: 'set_app_passcode', SET_BALANCE: 'set_balance', - ADD_RECENT_TX: 'add_recent_tx' + ADD_RECENT_TX: 'add_recent_tx', + SET_LOADING: 'set_loading' }; diff --git a/src/actionsheets/sheets/ChargeDetails.js b/src/actionsheets/sheets/ChargeDetails.js index a362ac4..99c63df 100644 --- a/src/actionsheets/sheets/ChargeDetails.js +++ b/src/actionsheets/sheets/ChargeDetails.js @@ -1,4 +1,3 @@ - import { useContext } from 'react'; import { useHistory } from 'react-router-dom'; import { Form } from 'react-bootstrap'; @@ -9,6 +8,7 @@ import Swal from 'sweetalert2'; // import { AppContext } from '../../contexts/AppContext'; import { ActionSheetContext } from '../../contexts/ActionSheetContext'; import ActionSheet from './ActionSheet'; +import { checkBeneficiary } from '../../services'; export default function ChargeDetails(props) { const history = useHistory(); @@ -19,6 +19,12 @@ export default function ChargeDetails(props) { const chargeCustomer = async () => { showLoading('Charging customer account...'); + const isBeneficiary = await checkBeneficiary(data.phone); + if (!isBeneficiary.data) { + setActiveSheet('null'); + showLoading(null); + return Swal.fire('Error', `${isBeneficiary.message || 'Invalid Beneficiary'}`, 'error'); + } try { // const agency = await DataService.getDefaultAgency(); // const rahat = RahatService(agency.address, wallet); diff --git a/src/assets/contracts/rahat.json b/src/assets/contracts/Rahat.json similarity index 100% rename from src/assets/contracts/rahat.json rename to src/assets/contracts/Rahat.json diff --git a/src/assets/custom/styles.css b/src/assets/custom/styles.css index d1322f3..c4770b3 100644 --- a/src/assets/custom/styles.css +++ b/src/assets/custom/styles.css @@ -1,64 +1,67 @@ .circleSelfie { - border-radius: 50%!important; - width : 250px; - height: 250px; - object-fit: cover; - border: 5px solid #958d9e; + border-radius: 50% !important; + width: 250px; + height: 250px; + object-fit: cover; + border: 5px solid #958d9e; } .selfieWrapper { - /* width: 100%; */ - height: auto; + /* width: 100%; */ + height: auto; +} +.loading_text { + color: #e27a2a; } .idCardWrapper { - width: 100%; - height: 350px; + width: 100%; + height: 350px; } .idCardSnapper { - border-radius: 10%; - height: 350px; - object-fit: cover; - border: 3px solid #958d9e; + border-radius: 10%; + height: 350px; + object-fit: cover; + border: 3px solid #958d9e; } .button-link { - background-color: transparent; - border: none; - cursor: pointer; - text-decoration: none; - display: inline; - margin: 0; - padding: 0; + background-color: transparent; + border: none; + cursor: pointer; + text-decoration: none; + display: inline; + margin: 0; + padding: 0; } - .button-link:hover, - .button-link:focus { - text-decoration: none; - } +.button-link:hover, +.button-link:focus { + text-decoration: none; +} - .btn-shutter{ - width: 80px; - height: 80px; - /* border: 2px solid #958d9e; */ - display: flex; - justify-content: center; - align-items: center; - border-radius: 50%; - background-color: #2b7ec1; - } - .btn-shutter-icon{ - width: 40px; - height: 40px; - color: white; - } - .btn-faceChange{ - width: 50px; - height: 50px; - border-radius: 50%; - background-color: white; - display: flex; - justify-content: center; - align-items: center; - } \ No newline at end of file +.btn-shutter { + width: 80px; + height: 80px; + /* border: 2px solid #958d9e; */ + display: flex; + justify-content: center; + align-items: center; + border-radius: 50%; + background-color: #2b7ec1; +} +.btn-shutter-icon { + width: 40px; + height: 40px; + color: white; +} +.btn-faceChange { + width: 50px; + height: 50px; + border-radius: 50%; + background-color: white; + display: flex; + justify-content: center; + align-items: center; +} diff --git a/src/constants/api.js b/src/constants/api.js index fac3807..b6b2bb6 100644 --- a/src/constants/api.js +++ b/src/constants/api.js @@ -1,8 +1,11 @@ -const server_url = process.env.REACT_APP_DEFAULT_AGENCY_API; -const base_url = server_url + '/api/v1'; - -module.exports = { - REGISTER: base_url + '/vendors/register', - NFT: server_url + '/nft', - SERVER_URL: server_url -}; +const server_url = process.env.REACT_APP_DEFAULT_AGENCY_API; +const base_url = server_url + '/api/v1'; + +module.exports = { + REGISTER: base_url + '/vendors/register', + NFT: base_url + '/nft', + VENDORS: base_url + '/vendors', + SERVER_URL: server_url, + APP: base_url + '/app', + BASE_URL: base_url +}; diff --git a/src/constants/index.js b/src/constants/index.js index 444f492..e87bbf9 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -1,7 +1,7 @@ module.exports = { APP_CONSTANTS: { VERSION: 1, - PASSCODE_LENGTH: 6, + PASSCODE_LENGTH: 10, SCAN_DELAY: 600, CHARGE_TYPES: { TOKEN: 'token', @@ -9,7 +9,11 @@ module.exports = { TOKEN_RECIEVED: 'tokenRecieved', TOKEN_SENT: 'tokenSent', NFT_RECIEVED: 'nftRecieved', - NFT_SENT: 'nftSent' + NFT_SENT: 'nftSent', + REDEEMED_TOKEN: 'tokenRedeemed', + REDEEMED_PACKAGE: 'packageRedeemed', + TOKEN_TRANSFER: 'tokenTransfer', + PAKCAGE_TRANSFER: 'packageTransfer' }, DEFAULT_NFT_CHARGE: 1, SCANNER_PREVIEW_STYLE: { @@ -21,7 +25,7 @@ module.exports = { }, BACKUP: { PASSPHRASE_RULE: '"^(?=.*[a-zA-Z])(?=.*[0-9])(?=.{12,})"', - GDRIVE_FOLDERNAME: 'RumsanWalletBackups' + GDRIVE_FOLDERNAME: 'VendorBackups' }, DB: { NAME: 'db_wallet', diff --git a/src/contexts/AppContext.js b/src/contexts/AppContext.js index f5be2d1..a1c45f3 100644 --- a/src/contexts/AppContext.js +++ b/src/contexts/AppContext.js @@ -1,31 +1,50 @@ -import React, { createContext, useState, useReducer, useCallback } from 'react'; +import React, { createContext, useReducer, useCallback } from 'react'; import appReduce from '../reducers/appReducer'; import APP_ACTIONS from '../actions/appActions'; import DataService from '../services/db'; import { TokenService } from '../services/chain'; import { APP_CONSTANTS, DEFAULT_TOKEN } from '../constants'; +import { ethers } from 'ethers'; const initialState = { + contextLoading: false, address: null, agency: null, network: null, wallet: null, profile: null, - hasWallet: true, + hasWallet: false, + hasBackedUp: false, tokenBalance: 0, scannedEthAddress: '', - scannedAmount: null + scannedAmount: null, + hasSynchronized: false, + recentTx: [] }; export const AppContext = createContext(initialState); export const AppContextProvider = ({ children }) => { const [state, dispatch] = useReducer(appReduce, initialState); - const [recentTx, setRecentTx] = useState([]); + const toggleLoading = useCallback((loading = false) => { + dispatch({ type: APP_ACTIONS.SET_LOADING, data: loading }); + }, []); + const initialize_index_db = useCallback(async () => { + DataService.dbInstance + .open() + .then(async () => { + console.log('Dexie succesfully opened'); + await DataService.addDefaultAsset(DEFAULT_TOKEN.SYMBOL, DEFAULT_TOKEN.NAME); + await DataService.save('version', APP_CONSTANTS.VERSION); + }) + .catch(err => { + console.log('Cannot open dexie', err); + }); + }, []); const initApp = useCallback(async () => { - DataService.addDefaultAsset(DEFAULT_TOKEN.SYMBOL, DEFAULT_TOKEN.NAME); + toggleLoading(true); //TODO: in future check version and add action if the version is different. - DataService.save('version', APP_CONSTANTS.VERSION); + await initialize_index_db(); let data = await DataService.initAppData(); data.profile = await DataService.getProfile(); data.hasWallet = data.wallet === null ? false : true; @@ -33,22 +52,29 @@ export const AppContextProvider = ({ children }) => { localStorage.removeItem('address'); } else { let agency = await DataService.getDefaultAgency(); - if (!agency) return; - const balance = await TokenService(agency.address).getBalance(); + let balance; + try { + if (!agency) throw Error('No agency'); + const blcs = await TokenService(agency.address).getBalance(); + balance = blcs; + } catch (err) { + balance = ethers.BigNumber.from(0); + } data.balance = balance.toNumber(); data.agency = agency; } dispatch({ type: APP_ACTIONS.INIT_APP, data }); - }, [dispatch]); + toggleLoading(false); + }, [toggleLoading, initialize_index_db]); async function setAgency(agency) { if (!agency) agency = await DataService.getDefaultAgency(); dispatch({ type: APP_ACTIONS.SET_AGENCY, data: agency }); } - async function setTokenBalance(tokenBalance) { + const setTokenBalance = useCallback(tokenBalance => { dispatch({ type: APP_ACTIONS.SET_BALANCE, data: tokenBalance }); - } + }, []); function setHasWallet(hasWallet) { dispatch({ type: APP_ACTIONS.SET_HASWALLET, data: hasWallet }); @@ -66,12 +92,9 @@ export const AppContextProvider = ({ children }) => { dispatch({ type: APP_ACTIONS.SET_SCANNED_DATA, data }); } - function addRecentTx(tx) { - setRecentTx([]); - if (!Array.isArray(tx)) tx = [tx]; - const arr = [...tx, ...recentTx]; - setRecentTx(arr.slice(0, 3)); - } + const addRecentTx = useCallback(tx => { + dispatch({ type: APP_ACTIONS.ADD_RECENT_TX, data: tx }); + }, []); return ( { scannedEthAddress: state.scannedEthAddress, scannedAmount: state.scannedAmount, hasWallet: state.hasWallet, + hasBackedUp: state.hasBackedUp, + contextLoading: state.contextLoading, + hasSynchronized: state.hasSynchronized, network: state.network, wallet: state.wallet, - recentTx, + recentTx: state.recentTx, initApp, setAgency, setTokenBalance, @@ -93,7 +119,8 @@ export const AppContextProvider = ({ children }) => { setNetwork, setWallet, dispatch, - addRecentTx + addRecentTx, + toggleLoading }} > {children} diff --git a/src/hooks/useSignature.js b/src/hooks/useSignature.js new file mode 100644 index 0000000..64ff010 --- /dev/null +++ b/src/hooks/useSignature.js @@ -0,0 +1,17 @@ +import { useState, useEffect, useCallback } from 'react'; + +function useAuthSignature(wallet) { + const [sign, setSign] = useState(null); + const getSign = useCallback(async () => { + if (!wallet) return; + const data = Date.now().toString(); + let signature = await wallet.signMessage(data); + signature = `${data}.${signature}`; + setSign(signature); + }, [wallet]); + useEffect(getSign, [getSign]); + + return sign; +} + +export default useAuthSignature; diff --git a/src/modules/App.js b/src/modules/App.js index 7a49611..d5222a1 100644 --- a/src/modules/App.js +++ b/src/modules/App.js @@ -12,9 +12,11 @@ import GoogleRestore from '../modules/misc/googleRestore'; import CreateWallet from '../modules/wallet/create'; import ResetWallet from '../modules/misc/reset'; import RestoreFromMnemonic from '../modules/wallet/restoreMnemonic'; - +import BackupWallet from '../modules/setup/backupWallet'; import { AppContextProvider } from '../contexts/AppContext'; import { ActionSheetContextProvider } from '../contexts/ActionSheetContext'; +import GoogleBackup from '../modules/misc/googleBackup'; +import SyncDb from '../modules/syncDb'; function App() { return ( @@ -24,15 +26,18 @@ function App() { + + + diff --git a/src/modules/assets/index.js b/src/modules/assets/index.js index d87ee71..86a2a63 100644 --- a/src/modules/assets/index.js +++ b/src/modules/assets/index.js @@ -11,7 +11,7 @@ export default function Asset() { useEffect(() => { async function listNfts() { const nftList = await DataService.listNft(); - setNfts(nftList); + setNfts(nftList || []); } listNfts(); diff --git a/src/modules/charge/otp/Otp.js b/src/modules/charge/otp/Otp.js index a05cfb2..daaee5e 100644 --- a/src/modules/charge/otp/Otp.js +++ b/src/modules/charge/otp/Otp.js @@ -54,7 +54,7 @@ export default function Otp(props) { from: receipt.from, status: 'success' }; - + if (chargeType === CHARGE_TYPES.NFT) tx.tokenId = tokenId; await DataService.addTx(tx); chargeType === CHARGE_TYPES.NFT && (await handleStoreAssets()); chargeType === CHARGE_TYPES.TOKEN ? removeTokenAmount() : removeNFTAmount(); @@ -64,7 +64,7 @@ export default function Otp(props) { title: 'Successfully transfered', timer: 2000 }); - history.push(`/charge/${beneficiary}`); + history.push(`/tx/${tx.hash}`); } catch (e) { showLoading(null); @@ -78,6 +78,8 @@ export default function Otp(props) { timestamp: Date.now(), amount: chargeType === CHARGE_TYPES.TOKEN ? getTokenAmount() : getNFTAmount() }; + if (chargeType === CHARGE_TYPES.NFT) tx.tokenId = [tokenId]; + await DataService.addTx(tx); Swal.fire({ diff --git a/src/modules/charge/token/Token.js b/src/modules/charge/token/Token.js index f967c7f..3607982 100644 --- a/src/modules/charge/token/Token.js +++ b/src/modules/charge/token/Token.js @@ -2,7 +2,6 @@ import React, { useContext, useState, useEffect } from 'react'; import { useHistory } from 'react-router-dom'; import { IoCloseCircle, IoSendOutline } from 'react-icons/io5'; - import { AppContext } from '../../../contexts/AppContext'; import { ChargeContext } from '../../../contexts/ChargeContext'; @@ -28,7 +27,6 @@ export default function Token(props) { const [chargeAmount, setChargeAmount] = useState(null); const [packages, setPackages] = useState([]); - const handleChargeClick = async () => { try { showLoading('charging beneficiary...'); @@ -41,7 +39,6 @@ export default function Token(props) { showLoading(null); } catch (e) { showLoading(null); - } }; @@ -64,7 +61,6 @@ export default function Token(props) { const balance = totalERC1155Balance.balances[index].toNumber(); - const pkg = { tokenId: data.tokenId, name: data.name, @@ -73,7 +69,6 @@ export default function Token(props) { value: data.metadata && data.metadata.fiatValue ? data.metadata.fiatValue : '', imageUri: data.metadata && data.metadata.packageImgURI ? data.metadata.packageImgURI : '', balance - }; setPackages(packages => [...packages, pkg]); }); diff --git a/src/modules/global/SetPasscodeModal.js b/src/modules/global/SetPasscodeModal.js new file mode 100644 index 0000000..fb9fdf8 --- /dev/null +++ b/src/modules/global/SetPasscodeModal.js @@ -0,0 +1,114 @@ +import React, { useEffect, useState } from 'react'; +import ModalWrapper from './ModalWrapper'; +import DataService from '../../services/db'; + +import { APP_CONSTANTS } from '../../constants'; +const { PASSCODE_LENGTH } = APP_CONSTANTS; + +function SetPasscodeModal({ showModal, togglePasscodeModal = () => {}, handlePasscodeSave }) { + const [passcode, setPasscode] = useState(''); + const [confirmPasscode, setConfirmPasscode] = useState(''); + const [confirmBtn, showConfirmBtn] = useState(false); + const [passCodeMatch, setPasscodeMatch] = useState(true); + + const resetModal = () => { + setPasscode(''); + setConfirmPasscode(''); + showConfirmBtn(false); + }; + const handlePasscodeChange = e => { + setPasscode(e.target.value); + }; + + const handleConfirmPasscodeChange = async e => { + const { value } = e.target; + setConfirmPasscode(value); + if (value.length === PASSCODE_LENGTH) { + if (value !== passcode) { + setPasscodeMatch(false); + return; + } + showConfirmBtn(true); + } + }; + + const savePasscode = async () => { + try { + await DataService.save('temp_passcode', passcode); + handlePasscodeSave(); + } catch (err) { + console.log('Error while saving passcode'); + } + }; + useEffect(() => { + resetModal(); + return () => resetModal(); + }, []); + + return ( + { + resetModal(); + togglePasscodeModal(); + }} + onEnter={() => resetModal()} + > +
+
+

Enter your registered phone number.

+ {passcode.length < PASSCODE_LENGTH && ( + + )} + + {passcode && passcode.length === PASSCODE_LENGTH && ( + <> + +
+ {passCodeMatch === true ? ( + Please enter phone number again + ) : ( + + Confirm-Phone number didnot match the previous one . + + )} +
+ + )} + + {confirmBtn && ( +
+ +
+ )} +
+
+
+ ); +} + +export default SetPasscodeModal; diff --git a/src/modules/home/index.js b/src/modules/home/index.js index 94b2cad..62bc628 100644 --- a/src/modules/home/index.js +++ b/src/modules/home/index.js @@ -14,7 +14,7 @@ import Profile from '../settings/profile'; import Transfer from '../transfer'; import Transactions from '../transactions'; import TxDetails from '../transactions/details'; -import AllTransactions from '../transactions/allTransactions'; +import AllTransactions from '../transactions'; import GoogleBackup from '../misc/googleBackup'; import Charge from '../charge/token/index'; import OTP from '../charge/otp/index'; diff --git a/src/modules/home/main.js b/src/modules/home/main.js index ec7e6c2..d2aac0f 100644 --- a/src/modules/home/main.js +++ b/src/modules/home/main.js @@ -1,5 +1,5 @@ -import React, { useState, useContext, useRef, useEffect } from 'react'; -import { useHistory, Redirect, Link } from 'react-router-dom'; +import React, { useState, useContext, useRef, useEffect, useCallback } from 'react'; +import { useHistory, Link } from 'react-router-dom'; import { Form } from 'react-bootstrap'; import Swal from 'sweetalert2'; import { useResize } from '../../utils/react-utils'; @@ -8,20 +8,37 @@ import { AppContext } from '../../contexts/AppContext'; import TransactionList from '../transactions/list'; import DataService from '../../services/db'; import ActionSheet from '../../actionsheets/sheets/ActionSheet'; -import { TokenService } from '../../services/chain'; +import { ERC1155_Service, TokenService } from '../../services/chain'; import Loading from '../global/Loading'; import { IoArrowDownCircleOutline } from 'react-icons/io5'; +import { calculateTotalPackageBalance } from '../../services'; +import useAuthSignature from '../../hooks/useSignature'; +import { GiToken } from 'react-icons/gi'; +import { FiPackage } from 'react-icons/fi'; +import { APP_CONSTANTS } from '../../constants'; var QRCode = require('qrcode.react'); +const { CHARGE_TYPES } = APP_CONSTANTS; export default function Main() { const history = useHistory(); - const { hasWallet, wallet, tokenBalance, recentTx, setTokenBalance, addRecentTx, agency } = useContext(AppContext); - const [showPageLoader, setShowPageLoader] = useState(true); + const { hasWallet, wallet, tokenBalance, setTokenBalance, agency, hasBackedUp, contextLoading, hasSynchronized } = + useContext(AppContext); + + const authSign = useAuthSignature(wallet); const [redeemModal, setRedeemModal] = useState(false); + const [redeemTokenModal, setRedeemTokenModal] = useState(false); + const [redeemPackageModal, setRedeemPackageModal] = useState(false); + const [redeemAmount, setRedeemAmount] = useState(''); const [loading, showLoading] = useState(null); + const [selectedRedeemablePackage, setSelectedRedeemablePackage] = useState([]); + const [packageBalanceLoading, setPackageBalanceLoading] = useState(true); + const [packageBalance, setPackageBalance] = useState(null); + const [packages, setPackages] = useState(null); + const [recentTx, setRecentTx] = useState(null); + const cardBody = useRef(); const { width } = useResize(cardBody); @@ -30,23 +47,64 @@ export default function Main() { else return 280; }; - const checkVendorStatus = async () => { - //update API to only query relevant agency. - if (!wallet) return; - let data = await fetch(`${process.env.REACT_APP_DEFAULT_AGENCY_API}/vendors/${wallet.address}`).then(r => { - if (!r.ok) throw Error(r.message); - return r.json(); - }); + const checkRecentTnx = useCallback(async () => { + let txs = await DataService.listTx(); + if (txs && Array.isArray(txs)) { + const arr = txs.slice(0, 3); + setRecentTx(arr); + } + }, []); + + const getTokenBalance = useCallback(async () => { + if (!agency) return; + try { + const balance = await TokenService(agency.address).getBalance(); + setTokenBalance(balance.toNumber()); + } catch (err) { + console.log('Unable to get token Balance'); + console.log(err); + } + }, [agency, setTokenBalance]); + + const getPackageBalance = useCallback(async () => { + if (!agency) return; + if (!authSign) return; + try { + const nfts = await DataService.listNft(); + const walletAddress = await DataService.getAddress(); + // Get Token Ids from index db + const tokenIds = nfts.map(item => item?.tokenId); + if (!tokenIds?.length) { + setPackageBalanceLoading(false); + return; + } + + const tokenQtys = []; - if (!data.agencies.length) return history.push('/setup/idcard'); - let status = data.agencies[0].status; + // get token balances from contract + const address = tokenIds.map(() => walletAddress); + const blnc = await ERC1155_Service(agency?.address).getBatchBalance(address, tokenIds); - if (status !== 'active') { - let dagency = Object.assign(agency, { isApproved: false }); - await DataService.updateAgency(dagency.address, dagency); - history.push('/setup/pending'); + if (!blnc) return; + if (blnc?.length) { + blnc.forEach((item, index) => { + tokenQtys.push(item.toNumber()); + nfts[index].amount = item.toNumber(); + }); + } + + setPackages(nfts); + // get total-package-balance from Backend server + const totalNftBalance = await calculateTotalPackageBalance({ tokenIds, tokenQtys }, authSign); + + setPackageBalance(totalNftBalance); + setPackageBalanceLoading(false); + // let tokens + } catch (err) { + setPackageBalanceLoading(false); + console.log('Unable to get package balance', err); } - }; + }, [agency, authSign]); const confirmAndRedeemToken = async data => { setRedeemModal(false); @@ -68,13 +126,15 @@ export default function Main() { const redeemToken = async () => { //TODO choose a financial institute to redeem token + + setRedeemTokenModal(false); let tknService = TokenService(agency.address, wallet); showLoading('Transferring tokens to redeem. Please wait...'); let receipt = await tknService.transfer(agency.adminAddress, Number(redeemAmount)); resetFormStates(); const tx = { hash: receipt.transactionHash, - type: 'redeem', + type: CHARGE_TYPES.REDEEMED_TOKEN, timestamp: Date.now(), amount: redeemAmount, to: 'agency', @@ -82,10 +142,62 @@ export default function Main() { status: 'success' }; await DataService.addTx(tx); + await getTokenBalance(); history.push(`/tx/${receipt.transactionHash}`); - let tokenBalance = await TokenService(agency.address).getBalance(); - setTokenBalance(tokenBalance.toNumber()); }; + + const redeemPackages = async () => { + if (!selectedRedeemablePackage?.length) return; + showLoading('Transferring packages to redeem. Please wait...'); + const ids = []; + const amount = []; + try { + selectedRedeemablePackage.forEach(item => { + ids.push(item.tokenId); + amount.push(item.amount); + }); + + const trasnaction = await ERC1155_Service(agency?.address, wallet).batchRedeem( + wallet.address, + agency?.adminAddress, + ids, + amount + ); + + const tx = { + hash: trasnaction.transactionHash, + type: CHARGE_TYPES.REDEEMED_PACKAGE, + timestamp: Date.now(), + amount: amount.reduce((prevVal, curVal) => prevVal + curVal, 0), + to: 'agency', + from: wallet.address, + status: 'success', + tokenId: ids + }; + + await DataService.addTx(tx); + await DataService.batchDecrementNft(ids, amount); + setSelectedRedeemablePackage([]); + setRedeemPackageModal(false); + await getPackageBalance(); + + resetFormStates(); + await Swal.fire({ + icon: 'success', + title: 'Success', + text: 'Successfully Redeemed Pacakages' + }); + } catch (err) { + resetFormStates(); + + Swal.fire({ + title: 'Error', + text: `Couldnot redeem package`, + icon: 'error' + }); + } + }; + const updateRedeemAmount = e => { let formData = new FormData(e.target.form); let data = {}; @@ -99,53 +211,82 @@ export default function Main() { setRedeemModal(false); }; - useEffect(() => { - if (agency) - TokenService(agency.address) - .getBalance() - .then(bal => setTokenBalance(bal.toNumber())); - let timer1 = null; - (async () => { - let txs = await DataService.listTx(); - addRecentTx(txs.slice(0, 3)); - const timer = setTimeout(() => { - setShowPageLoader(false); - }, 300); - timer1 = setTimeout(async () => { - await checkVendorStatus(); - }, 3000); - return () => { - clearTimeout(timer); - clearTimeout(timer1); - }; + const getInfoState = useCallback(async () => { + if (contextLoading) return; + if (!hasWallet) return history.push('/setup'); + if (!hasBackedUp) return history.push('/wallet/backup'); + if (!hasSynchronized) return history.push('/sync'); + if (agency && !agency.isApproved) return history.push('/setup/pending'); + await checkRecentTnx(); + await getTokenBalance(); + await getPackageBalance(); + }, [ + contextLoading, + agency, + hasSynchronized, + hasWallet, + hasBackedUp, + history, + getTokenBalance, + checkRecentTnx, + getPackageBalance + ]); - })(); - return function cleanup() { - if (timer1) clearTimeout(timer1); - }; - }, []); + // Action Sheet togglers + const toggleRedeemModal = () => setRedeemModal(prev => !prev); + + const toggleTokenModal = e => { + e?.preventDefault(); + toggleRedeemModal(); + setRedeemTokenModal(prev => !prev); + }; + + const togglePackageModal = e => { + e?.preventDefault(); + setSelectedRedeemablePackage([]); + toggleRedeemModal(); + setRedeemPackageModal(prev => !prev); + }; + + // Checkbox click handler + + const onCheckBoxClick = tokenId => { + const isPresent = isChecked(tokenId); + if (isPresent) setSelectedRedeemablePackage(prev => prev.filter(elm => elm.tokenId !== tokenId)); + if (!isPresent) { + const newElm = packages.find(elm => elm.tokenId === tokenId); + setSelectedRedeemablePackage(prev => [...prev, newElm]); + } + }; - if (!hasWallet) { - return ; - } + const isChecked = tknId => { + return selectedRedeemablePackage.some(elm => elm.tokenId === tknId); + }; - if (agency && !agency.isApproved) { - return ; - } + // UseEffects + useEffect(() => { + let isMounted = true; + if (isMounted) getInfoState(); + return () => { + isMounted = false; + }; + }, [getInfoState]); return ( <> - {showPageLoader && ( + {contextLoading && (
icon
)} + {/* Redeem Token Modal Starts */} + setRedeemModal(false)} + showModal={redeemTokenModal} + onHide={e => toggleTokenModal(e)} handleSubmit={confirmAndRedeemToken} >
@@ -166,7 +307,6 @@ export default function Main() { onChange={updateRedeemAmount} required /> - {/* */} @@ -175,35 +315,123 @@ export default function Main() {
+ {/* Redeem Token Modal Ends */} + + {/* Redeem Package Modal Starts */} + togglePackageModal(e)} + handleSubmit={redeemPackages} + > +
+ {packages?.length > 0 ? ( + packages.map(p => { + return ( +
+
+ onCheckBoxClick(p.tokenId)} + /> + asset + +
+

{p.amount}

+
+ ); + }) + ) : ( +

You don't own any packages.

+ )} +
+
+ {/* Redeem Package Modal Ends */} + + {/* Redeem Modal Start */} + setRedeemModal(false)} + handleSubmit={toggleRedeemModal} + > + + + {/* Redeem Modal Ends */} +
- Balance -

{tokenBalance}

+ Token Balance +

{tokenBalance || 0}

+
+
+ Package Balance + +

+ NRS {packageBalance?.grandTotal || 0} +

-
- {wallet && ( - - )}
+ {wallet && ( + + )}
@@ -234,7 +462,7 @@ export default function Main() { paddingTop: '0px' }} > - +
diff --git a/src/modules/layouts/LockedFooter.js b/src/modules/layouts/LockedFooter.js index e9674b0..7ae5f7d 100644 --- a/src/modules/layouts/LockedFooter.js +++ b/src/modules/layouts/LockedFooter.js @@ -21,8 +21,8 @@ export default function LockedFooter() { let encryptedWallet = await DataService.getWallet(); const wallet = await Wallet.loadFromJson(profile.phone, encryptedWallet); setWallet(wallet); - history.push('/'); setLoadingModal(false); + history.push('/'); }; //setTimeout(handleUnlockClick, 1000); diff --git a/src/modules/layouts/UnlockedFooter.js b/src/modules/layouts/UnlockedFooter.js index 9aab339..122cfbb 100644 --- a/src/modules/layouts/UnlockedFooter.js +++ b/src/modules/layouts/UnlockedFooter.js @@ -50,4 +50,3 @@ export default function UnlockedFooter() { ); } - diff --git a/src/modules/misc/googleBackup.js b/src/modules/misc/googleBackup.js index bb44a9e..686caa0 100644 --- a/src/modules/misc/googleBackup.js +++ b/src/modules/misc/googleBackup.js @@ -1,40 +1,46 @@ -import React, { useState, useEffect, useContext, useRef } from 'react'; +import React, { useState, useEffect, useContext, useRef, useCallback } from 'react'; import { Link, useHistory } from 'react-router-dom'; import { gapi } from 'gapi-script'; -import { IoChevronBackOutline, IoHomeOutline, IoCloseCircle } from 'react-icons/io5'; -import EthCrypto from 'eth-crypto'; - +import { IoChevronBackOutline, IoHomeOutline } from 'react-icons/io5'; import { BACKUP } from '../../constants'; import { AppContext } from '../../contexts/AppContext'; import DataService from '../../services/db'; import UserImg from '../../assets/images/user.svg'; - import { GFile, GFolder } from '../../utils/google'; import Swal from 'sweetalert2'; +import Wallet from '../../utils/blockchain/wallet'; const DISCOVERY_DOCS = ['https://www.googleapis.com/discovery/v1/apis/drive/v3/rest']; const GOOGLE_REDIRECT_URL = process.env.REACT_APP_GOOGLE_REDIRECT_URL; + const CLIENT_ID = process.env.REACT_APP_GOOGLE_CLIENT_ID; export default function GoogleBackup() { const history = useHistory(); - const Actions = [ - { - hash: '#choose-account', - label: 'Please choose Google account. Please click the switch account button to change account.' - }, - { - hash: '#process', - label: 'Your is being backed up in Google Drive.' - }, - { - hash: '#enter-passphrase', - label: 'Please enter backup passphrase. It must be at least 10 characters long with one number and alphabet. Button will appear after you type 12 characters.
PLEASE NOTE: THIS IS DIFFERENT THAN YOUR 6-DIGIT PASSCODE.' - } - ]; + const Actions = useCallback( + () => [ + { + hash: '#choose-account', + label: 'Please choose Google account. Please click the switch account button to change account.' + }, + { + hash: '#process', + label: 'Your is being backed up in Google Drive.' + }, + { + hash: '#enter-passphrase', + label: 'Please enter backup passphrase. It must be at least 10 characters long with one number and alphabet. Button will appear after you type 12 characters.
PLEASE NOTE: THIS IS DIFFERENT THAN YOUR 6-DIGIT PASSCODE.' + } + ], + [] + ); + + // const { toggleFooter } = useContext(AppContext); + const [wallet, setWallet] = useState(null); + const [encWallet, setEncWallet] = useState(null); - const { wallet } = useContext(AppContext); + const [isFetchingWallet, setFetchingWallet] = useState(true); const passphraseRef = useRef(null); const [errorMsg, setErrorMsg] = useState(null); const [gUser, setGUser] = useState({ @@ -43,18 +49,20 @@ export default function GoogleBackup() { email: null, image: UserImg }); - const [passphrase, setPassphrase] = useState(''); - const [passphraseStrength, setPassphraseStrength] = useState('None'); + const [progress, setProgress] = useState({ message: 'Processing...', percent: 0, showHome: false }); const [currentAction, setCurrentAction] = useState({}); - const changeAction = hash => { - setErrorMsg(null); - let selectedAction = Actions.find(a => a.hash === hash); - if (!selectedAction) setCurrentAction(Actions.find(a => a.hash === '#choose-account')); - else setCurrentAction(selectedAction); - }; + const changeAction = useCallback( + hash => { + setErrorMsg(null); + let selectedAction = Actions().find(a => a.hash === hash); + if (!selectedAction) setCurrentAction(Actions().find(a => a.hash === '#choose-account')); + else setCurrentAction(selectedAction); + }, + [Actions] + ); const loadGapiClient = () => { history.listen(location => { @@ -63,8 +71,21 @@ export default function GoogleBackup() { changeAction(history.location.hash); gapi.load('client:auth2', initClient); }; + const updateSigninStatus = useCallback(isSignedIn => { + let user = null; + if (isSignedIn) { + user = gapi.auth2.getAuthInstance().currentUser.get(); + const profile = user.getBasicProfile(); + setGUser({ + id: profile.getId(), + name: profile.getName(), + email: profile.getEmail(), + image: profile.getImageUrl() + }); + } else user = handleUserSignIn(); + }, []); - const initClient = () => { + const initClient = useCallback(() => { gapi.client .init({ clientId: CLIENT_ID, @@ -76,51 +97,47 @@ export default function GoogleBackup() { .then(function () { gapi.auth2.getAuthInstance().isSignedIn.listen(updateSigninStatus); updateSigninStatus(gapi.auth2.getAuthInstance().isSignedIn.get()); - }); - }; - - const updateSigninStatus = isSignedIn => { - let user = null; - if (isSignedIn) { - user = gapi.auth2.getAuthInstance().currentUser.get(); - const profile = user.getBasicProfile(); - setGUser({ - id: profile.getId(), - name: profile.getName(), - email: profile.getEmail(), - image: profile.getImageUrl() - }); - } else user = handleUserSignIn(); - }; + }) + .catch(err => console.log('Error===>', err)); + }, [updateSigninStatus]); const handleUserSignIn = () => { return gapi.auth2.getAuthInstance().signIn(); }; - const checkAndSetPassphrase = value => { - setErrorMsg(null); - var strongRegex = new RegExp('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{10,})'); - var mediumRegex = new RegExp( - '^(((?=.*[a-z])(?=.*[A-Z]))|((?=.*[a-z])(?=.*[0-9]))|((?=.*[A-Z])(?=.*[0-9])))(?=.{6,})' - ); + // const checkAndSetPassphrase = value => { + // setErrorMsg(null); + // var strongRegex = new RegExp('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{10,})'); + // var mediumRegex = new RegExp( + // '^(((?=.*[a-z])(?=.*[A-Z]))|((?=.*[a-z])(?=.*[0-9]))|((?=.*[A-Z])(?=.*[0-9])))(?=.{6,})' + // ); - if (strongRegex.test(value)) { - setPassphraseStrength('Strong'); - } else if (mediumRegex.test(value)) { - setPassphraseStrength('Medium'); - } else { - setPassphraseStrength('Weak'); - } - setPassphrase(value); - }; + // if (strongRegex.test(value)) { + // setPassphraseStrength('Strong'); + // } else if (mediumRegex.test(value)) { + // setPassphraseStrength('Medium'); + // } else { + // setPassphraseStrength('Weak'); + // } + // setPassphrase(value); + // }; + + const getWallet = useCallback(async () => { + const profile = await DataService.get('profile'); + const encryptedWallet = await DataService.getWallet(); + setEncWallet(encryptedWallet); + const wlt = await Wallet.loadFromJson(profile.phone, encryptedWallet); + setWallet(wlt); + setFetchingWallet(false); + }, []); - const prepareBackupData = async password => { + const prepareBackupData = async () => { let backupData = { name: 'rumsan-wallet', type: 'ethersjs' }; let data = await DataService.list(); data.forEach(d => { backupData[d.name] = d.data; }); - backupData.wallet = await wallet.encrypt(password.toString()); + backupData.wallet = encWallet; delete backupData.backup_googleFile; delete backupData.backup_wallet; backupData.documents = await DataService.listDocuments(); @@ -136,20 +153,10 @@ export default function GoogleBackup() { setErrorMsg(null); try { history.push('#process'); - const gFolder = new GFolder(gapi); const gFile = new GFile(gapi); - setProgress({ ...progress, percent: 5, message: 'Preparing data to backup...' }); - let backupData = await prepareBackupData(passphrase); - //encrypt and store backup passphrase - const encrypted = await EthCrypto.encryptWithPublicKey( - EthCrypto.publicKeyByPrivateKey(wallet.privateKey), - passphrase - ); - const encryptedPassphrase = EthCrypto.cipher.stringify(encrypted); - await DataService.save('backup_passphrase', encryptedPassphrase); - + let backupData = await prepareBackupData(); setProgress({ ...progress, percent: 30, message: 'Checking if previous backup exists...' }); const folder = await gFolder.ensureExists(BACKUP.GDRIVE_FOLDERNAME); setProgress({ ...progress, percent: 50, message: 'Backing up encrypted wallet to Google Drive...' }); @@ -162,6 +169,8 @@ export default function GoogleBackup() { }); await DataService.save('backup_googleFile', newFile.id); await DataService.save('backup_wallet', backupData.wallet); + await DataService.saveHasBackedUp(true); + setProgress({ ...progress, percent: 80, message: 'Cleaning up and finalizing...' }); if (file.exists) await gFile.deleteFile(file.firstFile.id); setProgress({ @@ -170,38 +179,23 @@ export default function GoogleBackup() { showHome: true, message: 'Wallet backed up successfully. Backup file named ' + wallet.address + ' has been created.' }); + + Swal.fire({ + icon: 'success', + title: 'Successful', + text: 'Successfully backed up wallet in your google drive .' + }).then(() => window.location.replace('/')); } catch (e) { - console.log(e.message); setPassphrase(''); - passphraseRef.current.focus(); + passphraseRef.current?.focus(); + await DataService.saveHasBackedUp(false); + setErrorMsg('Backup passphrase is incorrect. Please try again.'); } }; const confirmBackup = async () => { - var passPhraseRegex = new RegExp('^(?=.*[a-zA-Z])(?=.*[0-9])(?=.{10,})'); - if (!passPhraseRegex.test(passphrase)) { - Swal.fire( - 'Passphrase incorrect', - 'Passphrase must be 10 characters and have at least one number.', - 'error' - ); - return; - } - - const isConfirm = await Swal.fire({ - title: 'Important', - icon: 'warning', - html: `You MUST write this passphrase and store it safely. It is used to encrypt your wallet. If you forget it there is no way to retrieve your wallet. It will be gone forever.
Your passphrase is: ${passphrase}`, - showCancelButton: true, - confirmButtonColor: '#3085d6', - cancelButtonColor: '#d33', - confirmButtonText: 'Yes, I have written it down', - cancelButtonText: 'No, I want choose another' - }); - if (isConfirm.value) { - backupWallet(); - } + backupWallet(); }; const handleBackButton = e => { @@ -209,7 +203,15 @@ export default function GoogleBackup() { history.goBack(); }; - useEffect(loadGapiClient, []); + useEffect(loadGapiClient, [changeAction, history, initClient]); + useEffect(getWallet, [getWallet]); + // useEffect(() => { + // toggleFooter(true); + + // return () => { + // toggleFooter(false); + // }; + // }, [toggleFooter]); return (
@@ -259,7 +261,11 @@ export default function GoogleBackup() {
{gUser.id && (
-
@@ -267,9 +273,8 @@ export default function GoogleBackup() { )} - {currentAction.hash === '#enter-passphrase' && ( + {/* {currentAction.hash === '#enter-passphrase' && (
- {/* {!selectedWallet.id && } */}
@@ -307,7 +312,7 @@ export default function GoogleBackup() { )}
- )} + )} */} {currentAction.hash === '#process' && (
diff --git a/src/modules/misc/googleRestore.js b/src/modules/misc/googleRestore.js index f2da36b..b4a5fa3 100644 --- a/src/modules/misc/googleRestore.js +++ b/src/modules/misc/googleRestore.js @@ -1,16 +1,17 @@ -import React, { useState, useEffect, useContext } from 'react'; -import { Link, useHistory, Redirect } from 'react-router-dom'; +import React, { useState, useEffect, useCallback, useContext } from 'react'; +import { Link, useHistory } from 'react-router-dom'; import { gapi } from 'gapi-script'; -import { IoChevronBackOutline, IoHomeOutline, IoCloseCircle } from 'react-icons/io5'; - +import { IoChevronBackOutline, IoHomeOutline } from 'react-icons/io5'; import Wallet from '../../utils/blockchain/wallet'; import UserImg from '../../assets/images/user.svg'; -import { AppContext } from '../../contexts/AppContext'; import DataService from '../../services/db'; import { BACKUP } from '../../constants'; import { GFile, GFolder } from '../../utils/google'; import Loading from '../global/Loading'; import Swal from 'sweetalert2'; +import SetPasscodeModal from '../global/SetPasscodeModal'; +import { AppContext } from '../../contexts/AppContext'; +import { getVendorByWallet } from '../../services'; //const { PASSCODE_LENGTH } = APP_CONSTANTS; @@ -21,23 +22,29 @@ const CLIENT_ID = process.env.REACT_APP_GOOGLE_CLIENT_ID; export default function GoogleRestore() { const history = useHistory(); let currentWallet = null; + const { setWallet } = useContext(AppContext); + const Actions = useCallback( + () => [ + { + hash: '#choose-account', + label: 'Please choose Google account. Please click the switch account button to change account.' + }, + { + hash: '#choose-wallet', + label: 'Please select the wallet you wish to restore.' + }, + { + hash: '#enter-passphrase', + label: 'Please enter backup passphrase.' + } + ], + [] + ); - const Actions = [ - { - hash: '#choose-account', - label: 'Please choose Google account. Please click the switch account button to change account.' - }, - { - hash: '#choose-wallet', - label: 'Please select the wallet you wish to restore.' - }, - { - hash: '#enter-passphrase', - label: 'Please enter backup passphrase.' - } - ]; + const [passcodeModal, setPasscodeModal] = useState(false); + const [passcodeIncorrect, setPasscodeInorrect] = useState(false); + const togglePasscodeModal = () => setPasscodeModal(prev => !prev); - const { setWallet } = useContext(AppContext); const [loading, setLoading] = useState(null); const [errorMsg, setErrorMsg] = useState(null); const [gUser, setGUser] = useState({ @@ -46,18 +53,20 @@ export default function GoogleRestore() { email: null, image: UserImg }); - const [passphrase, setPassphrase] = useState(''); const [walletList, setWalletList] = useState([]); const [selectedWallet, setSelectedWallet] = useState({}); const [currentAction, setCurrentAction] = useState({}); - const changeAction = hash => { - setErrorMsg(null); - let selectedAction = Actions.find(a => a.hash === hash); - if (!selectedAction) setCurrentAction(Actions.find(a => a.hash === '#choose-account')); - else setCurrentAction(selectedAction); - }; + const changeAction = useCallback( + hash => { + setErrorMsg(null); + let selectedAction = Actions().find(a => a.hash === hash); + if (!selectedAction) setCurrentAction(Actions().find(a => a.hash === '#choose-account')); + else setCurrentAction(selectedAction); + }, + [Actions] + ); const loadGapiClient = () => { history.listen(location => { @@ -67,7 +76,21 @@ export default function GoogleRestore() { gapi.load('client:auth2', initClient); }; - const initClient = () => { + const updateSigninStatus = useCallback(async isSignedIn => { + let user = null; + if (isSignedIn) { + user = gapi.auth2.getAuthInstance().currentUser.get(); + const profile = user.getBasicProfile(); + setGUser({ + id: profile.getId(), + name: profile.getName(), + email: profile.getEmail(), + image: profile.getImageUrl() + }); + } else user = handleUserSignIn(); + }, []); + + const initClient = useCallback(() => { gapi.client .init({ clientId: CLIENT_ID, @@ -90,26 +113,17 @@ export default function GoogleRestore() { history.push('/'); }); }); - }; - - const updateSigninStatus = isSignedIn => { - let user = null; - if (isSignedIn) { - user = gapi.auth2.getAuthInstance().currentUser.get(); - const profile = user.getBasicProfile(); - setGUser({ - id: profile.getId(), - name: profile.getName(), - email: profile.getEmail(), - image: profile.getImageUrl() - }); - } else user = handleUserSignIn(); - }; + }, [history, updateSigninStatus]); const handleUserSignIn = () => { return gapi.auth2.getAuthInstance().signIn(); }; + const handlePasscodeSave = () => { + setErrorMsg(null); + togglePasscodeModal(); + restoreWallet(); + }; const fetchWalletList = async () => { let existingWallet = await DataService.getWallet(); if (existingWallet) { @@ -141,7 +155,7 @@ export default function GoogleRestore() { currentWallet = JSON.parse(walletData); if (!currentWallet.name) throw Error('Not a valid wallet. Please select another wallet.'); setSelectedWallet(Object.assign(selectedWallet, { data: currentWallet })); - history.push('#enter-passphrase'); + restoreWallet(); } catch (e) { setErrorMsg( e.message === 'Not a valid wallet. Please select another wallet.' @@ -153,39 +167,64 @@ export default function GoogleRestore() { setLoading(null); }; - const restoreWallet = async () => { - setErrorMsg(null); - setLoading('Unlocking and restoring wallet.'); + const unlockWallet = async () => { try { const passcode = await DataService.get('temp_passcode'); - const wallet = await Wallet.loadFromJson(passphrase, selectedWallet.data.wallet); - const passcodeWallet = await wallet.encrypt(passcode); + const wallet = await Wallet.loadFromJson(passcode, selectedWallet?.data?.wallet); + return wallet; + } catch (err) { + setErrorMsg('Backup passphrase is incorrect. Please try again.'); + setPasscodeInorrect(true); + } + }; + + const checkWalletRegistered = async walletAddress => { + try { + setLoading('Checking if wallet is registered'); + const walletReg = await getVendorByWallet(walletAddress); + return walletReg; + } catch (err) { + setErrorMsg('Wallet not registered, Redirecting to home screen.'); await DataService.clearAll(); - await DataService.saveWallet(passcodeWallet); + + setTimeout(() => { + history.push('/'); + }, [2500]); + } + }; + + const restoreWallet = async () => { + setErrorMsg(null); + setLoading('Unlocking and restoring wallet.'); + const wallet = await unlockWallet(); + const walletReged = await checkWalletRegistered(wallet?.address); + if (walletReged) { + await DataService.saveWallet(selectedWallet?.data?.wallet); + await DataService.saveHasBackedUp(true); await DataService.saveAddress(wallet.address); - await DataService.save('backup_wallet', selectedWallet.data.wallet); - if (selectedWallet.data.documents) await DataService.saveDocuments(selectedWallet.data.documents); - if (selectedWallet.data.assets) await DataService.addMultiAssets(selectedWallet.data.assets); setWallet(wallet); - await DataService.remove('temp_passcode'); - history.push('/'); - } catch (e) { - console.log(e); - setPassphrase(''); - setErrorMsg('Backup passphrase is incorrect. Please try again.'); + history.push('/sync'); } + setLoading(null); }; const showInfo = msg => { if (loading) return
Loading...
; return ( -
- {/* {isLoading && ( + <> +
+ {/* {isLoading && ( )} */} - {msg} -
+ {msg} +
+
+ + Go Back + +
+ ); }; @@ -194,10 +233,15 @@ export default function GoogleRestore() { history.goBack(); }; - useEffect(loadGapiClient, []); + useEffect(loadGapiClient, [changeAction, history, initClient]); return (
+
@@ -225,7 +269,7 @@ export default function GoogleRestore() {
- avatar + avatar

{gUser.name}

@@ -302,50 +346,22 @@ export default function GoogleRestore() {
)} - - {currentAction.hash === '#enter-passphrase' && ( -
- {!selectedWallet.id && } -
-
-
-
- setPassphrase(e.target.value)} - className="form-control pwd" - placeholder="Backup Passpharse" - /> - - - -
-
-
-
- -
- {passphrase.length > 4 && ( - - )} -
-
- )}
{errorMsg && ( + <> +
+ + Error: {errorMsg} + +
+ + )} + {passcodeIncorrect && (
- - Error: {errorMsg} - +
)}
diff --git a/src/modules/package/packageList.js b/src/modules/package/packageList.js index 8c7dc17..7bbd6b7 100644 --- a/src/modules/package/packageList.js +++ b/src/modules/package/packageList.js @@ -3,7 +3,6 @@ import React, { useState, useEffect } from 'react'; import { Link } from 'react-router-dom'; const PackageList = ({ limit, packages = [], beneficiary }) => { - const [pkg, setPkg] = useState([]); useEffect(() => { @@ -12,7 +11,6 @@ const PackageList = ({ limit, packages = [], beneficiary }) => { // let pkgs = packages.length ? packages : await DataService.listNft(); if (limit) pkgs = pkgs.slice(0, limit); - setPkg(pkgs); })(); }, [packages, limit]); @@ -44,14 +42,12 @@ const PackageList = ({ limit, packages = [], beneficiary }) => { {p.name} {p.balance} -
); })} - ); }; diff --git a/src/modules/setup/backupWallet.js b/src/modules/setup/backupWallet.js new file mode 100644 index 0000000..ced4a33 --- /dev/null +++ b/src/modules/setup/backupWallet.js @@ -0,0 +1,2 @@ +import BackupWallet from '../wallet/backup'; +export default BackupWallet; diff --git a/src/modules/setup/idCard.js b/src/modules/setup/idCard.js index aeff7dc..034ff18 100644 --- a/src/modules/setup/idCard.js +++ b/src/modules/setup/idCard.js @@ -8,6 +8,7 @@ import Loading from '../global/Loading'; import Wallet from '../../utils/blockchain/wallet'; import { AppContext } from '../../contexts/AppContext'; import DataService from '../../services/db'; +import api from '../../constants/api'; export default function Main() { const history = useHistory(); @@ -30,7 +31,7 @@ export default function Main() { }; const registerWithAgency = async data => { - let appData = await fetch(`${process.env.REACT_APP_DEFAULT_AGENCY_API}/app/settings`).then(r => { + let appData = await fetch(`${api.APP}/settings`).then(r => { if (!r.ok) throw Error(r.message); return r.json(); }); @@ -50,7 +51,7 @@ export default function Main() { setAgency(agencyData); if (!data.email) delete data.email; - await fetch(`${process.env.REACT_APP_DEFAULT_AGENCY_API}/vendors/register`, { + await fetch(`${api.VENDORS}/register`, { method: 'post', headers: { Accept: 'application/json', @@ -97,11 +98,13 @@ export default function Main() { govt_id_image: previewImage }); await DataService.saveWallet(encryptedWallet); - DataService.saveAddress(wallet.address); + await DataService.saveAddress(wallet.address); + await DataService.setSynchronized(true); + setWallet(wallet); setHasWallet(true); showLoading(null); - history.push('/pending'); + history.push('/wallet/backup'); } } catch (err) { console.log(err); diff --git a/src/modules/setup/index.js b/src/modules/setup/index.js index 97e3a15..a63167f 100644 --- a/src/modules/setup/index.js +++ b/src/modules/setup/index.js @@ -1,19 +1,43 @@ -import React from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { Link, useHistory } from 'react-router-dom'; -import { IoLogoGoogle, IoWalletOutline } from 'react-icons/io5'; - +import { IoWalletOutline } from 'react-icons/io5'; +import { FaGoogleDrive, FaKey } from 'react-icons/fa'; +import PackageJson from '../../../package.json'; +import PasscodeModal from '../global/SetPasscodeModal'; import DataService from '../../services/db'; +const RESTORE_METHOD = { + GOOGLE: 'google', + MNEMOMICS: 'mnemonics' +}; + +const RESTORE_LINK = { + [RESTORE_METHOD.GOOGLE]: '/google/restore', + [RESTORE_METHOD.MNEMOMICS]: '/mnemonic/restore' +}; + export default function Main() { const history = useHistory(); - const hasWallet = async () => { + const [restorePage, setRestorePage] = useState(false); + const [showModal, setModal] = useState(false); + const [restoreMethod, setRestoreMethod] = useState(null); + const togglePasscodeModal = () => setModal(prev => !prev); + + const toggleRestore = () => setRestorePage(prev => !prev); + const setRestoreMthd = mthd => setRestoreMethod(mthd); + const handlePasscodeSave = () => { + togglePasscodeModal(); + restoreMethod && history.push(RESTORE_LINK[restoreMethod]); + }; + + const hasWallet = useCallback(async () => { const wallet = await DataService.getWallet(); if (wallet != null) { history.push('/'); } - }; + }, [history]); - hasWallet(); + useEffect(hasWallet, [hasWallet]); return ( <> @@ -29,7 +53,13 @@ export default function Main() { को माध्यमबाट तपाइले राहत लिनेसंग पैसा लिन सक्नुहुने छ।

आउनुहोस तपाईंलाई Register गरौ। तपाईंको Google Login गर्नुहोस।

-
+ + + {/*
Register as Vendor - {/* - - Register using Google - */} +
*/} + {!restorePage && ( +
+ + + Register as Vendor + +
+
+ Already registered? + +
+
+
+ )} + + {restorePage && ( +
+ + + +
+
+ +
+
+
+ )} + +
+ Version: {PackageJson.version}
diff --git a/src/modules/setup/pendingApproval.js b/src/modules/setup/pendingApproval.js index d073901..8bc9391 100644 --- a/src/modules/setup/pendingApproval.js +++ b/src/modules/setup/pendingApproval.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { useHistory } from 'react-router-dom'; import DataService from '../../services/db'; @@ -7,7 +7,7 @@ export default function Main() { const history = useHistory(); const [agencyName, setAgencyName] = useState('the agency'); - const checkForApproval = async () => { + const checkForApproval = useCallback(async () => { let wallet = await DataService.getWallet(); if (!wallet) history.push('/setup'); wallet = JSON.parse(wallet); @@ -23,7 +23,7 @@ export default function Main() { try { let data = await checkApproval(wallet.address); - if (!data.agencies.length) return history.push('/setup/idcard'); + if (!data?.agencies.length) return history.push('/setup/idcard'); let status = data.agencies[0].status; if (status === 'active') { dagency.isApproved = true; @@ -31,23 +31,14 @@ export default function Main() { return history.push('/'); } } catch (e) { - throw Error(r.message); + console.log({ e }); } - }; + }, [history]); //eslint-disable-next-line useEffect(() => { - (async () => { - await checkForApproval(); - // const timer = setInterval(async () => { - // await checkForApproval(); - // }, 20000); - return () => { - //clearInterval(timer); - }; - })(); - return function cleanup() {}; - }, []); + checkForApproval(); + }, [checkForApproval]); return ( <> diff --git a/src/modules/setup/selfie.js b/src/modules/setup/selfie.js index 471b968..fb51b63 100644 --- a/src/modules/setup/selfie.js +++ b/src/modules/setup/selfie.js @@ -82,11 +82,13 @@ export default function Main() {
)} diff --git a/src/modules/syncDb/index.js b/src/modules/syncDb/index.js new file mode 100644 index 0000000..666d52b --- /dev/null +++ b/src/modules/syncDb/index.js @@ -0,0 +1,142 @@ +import React, { useContext, useEffect, useCallback, useState } from 'react'; +import { IoChevronBackOutline } from 'react-icons/io5'; +import { useHistory, Link } from 'react-router-dom'; +import { AppContext } from '../../contexts/AppContext'; +import DataService from '../../services/db'; +import Wallet from '../../utils/blockchain/wallet'; +import { getDefautAgency, getVendorByWallet } from '../../services'; + +function SyncDb() { + const { wallet, setWallet, hasSynchronized } = useContext(AppContext); + const [loadingMsg, setLoadingMsg] = useState({ message: 'Processing...', percent: 0 }); + const [errorMsg, setErrorMsg] = useState(null); + const history = useHistory(); + + const showError = msg => { + setLoadingMsg(null); + setErrorMsg(msg); + }; + + const setSynchronized = useCallback(async val => DataService.setSynchronized(val), []); + + const syncAgency = useCallback(async () => { + try { + setLoadingMsg({ percent: 40, message: 'Syncing Agency details...' }); + const agncy = await DataService.getDefaultAgency(); + if (!agncy) { + const defaultAgency = await getDefautAgency(); + await DataService.addAgency(defaultAgency); + } + } catch (err) { + console.log('Unable to add agency', err); + showError('Unable to sync agency properly'); + } + }, []); + + const syncProfile = useCallback(async () => { + try { + setLoadingMsg({ percent: 80, message: 'Syncing Profile details...' }); + if (!wallet) return; + const vendorProfile = await getVendorByWallet(wallet?.address); + const { name, phone, address, email, photo, govt_id_image } = vendorProfile; + + await DataService.saveProfile({ name, phone, address, email }); + photo && photo.length && (await DataService.saveProfileImage(photo[0])); + govt_id_image && (await DataService.saveProfileIdCard(govt_id_image)); + setLoadingMsg({ percent: 100, message: 'Succesfully synced...', showHome: true }); + await setSynchronized(true); + } catch (err) { + await setSynchronized(false); + console.log('Could not sync profile', err); + showError('Unable to sync profile properly'); + } + }, [wallet, setSynchronized]); + + const unlockWallet = useCallback(async () => { + try { + setLoadingMsg({ percent: 5, message: 'Unlocking Wallet...' }); + if (wallet) return; + const passcode = await DataService.get('temp_passcode'); + const encWallet = await DataService.getWallet(); + + if (!encWallet) return history.push('/setup'); + const wlt = await Wallet.loadFromJson(passcode, encWallet); + setWallet(wlt); + } catch (err) { + console.log('error at walllet', err); + setErrorMsg('Couldnot validate wallet properly.'); + } + }, [wallet, setWallet, history]); + + const handleBackButton = e => { + history.goBack(); + }; + + const initializeSync = useCallback(async () => { + if (hasSynchronized) return history.push('/'); + await unlockWallet(); + await syncAgency(); + await syncProfile(); + await DataService.remove('temp_passcode'); + }, [unlockWallet, syncAgency, syncProfile, history, hasSynchronized]); + + useEffect(() => { + let hasMounted = true; + if (hasMounted) initializeSync(); + + return () => { + hasMounted = false; + }; + }, [initializeSync]); + + return ( +
+
+
+ +
+
Sync Your Data
+
+
+
+

{loadingMsg?.message}

+
+
+
+
+
+
+ +
+
+ {loadingMsg?.showHome && ( + + Go to Home + + )} + {/*
*/} +
+ + {errorMsg && ( +
+ + Error: {errorMsg} + +
+ )} +
+
+
+ ); +} + +export default SyncDb; diff --git a/src/modules/transactions/allTransactions.js b/src/modules/transactions/allTransactions.js index 31ac6d0..c3b8a23 100644 --- a/src/modules/transactions/allTransactions.js +++ b/src/modules/transactions/allTransactions.js @@ -52,6 +52,22 @@ export default function AllTransactions() {
); } + if (t.type === CHARGE_TYPES.REDEEMED_PACKAGE) { + t.name = 'NFT Redeemed'; + t.icon = ( +
+ +
+ ); + } + if (t.type === CHARGE_TYPES.REDEEMED_TOKEN) { + t.name = 'Token Redeemed'; + t.icon = ( +
+ +
+ ); + } if (t.type === CHARGE_TYPES.TOKEN_RECIEVED) { t.name = 'Token Recieved'; t.icon = ( @@ -162,4 +178,3 @@ export default function AllTransactions() { ); } - diff --git a/src/modules/transactions/details.js b/src/modules/transactions/details.js index df152c6..86b88fc 100644 --- a/src/modules/transactions/details.js +++ b/src/modules/transactions/details.js @@ -1,64 +1,140 @@ -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import Moment from 'react-moment'; import { IoArrowDownOutline, IoArrowForwardOutline } from 'react-icons/io5'; -import { GiReceiveMoney, GiMoneyStack } from 'react-icons/gi'; +import { GiTwoCoins } from 'react-icons/gi'; import { BiError } from 'react-icons/bi'; import AppHeader from '../layouts/AppHeader'; import DataService from '../../services/db'; +import { APP_CONSTANTS } from '../../constants'; + +const { CHARGE_TYPES } = APP_CONSTANTS; export default function Main(props) { const hash = props.match.params.hash; - const [tx, setTx] = useState({}); + const [tokenDetails, setTokenDetails] = useState(null); + + const reorganizeTxn = useCallback(txn => { + if (!txn) return txn; + let t = txn; + if (t.type === 'issued') { + t.name = `Token sent to:`; + t.phone = `${t.to}`; + t.icon = ( +
+ +
+ ); + } + + if (t.type === 'send') { + t.name = 'Send Tokens'; + t.icon = ( +
+ +
+ ); + } + if (t.type === 'receive') { + t.name = 'Received Tokens'; + t.icon = ( +
+ +
+ ); + } + if (t.type === CHARGE_TYPES.REDEEMED_PACKAGE) { + t.name = 'NFT Redeemed'; + t.icon = ( +
+ +
+ ); + } + if (t.type === CHARGE_TYPES.REDEEMED_TOKEN) { + t.name = 'Token Redeemed'; + t.icon = ( +
+ +
+ ); + } + if (t.type === CHARGE_TYPES.TOKEN_RECIEVED) { + t.name = 'Token Recieved'; + t.icon = ( +
+ +
+ ); + } + if (t.type === CHARGE_TYPES.TOKEN_SENT) { + t.name = 'Token Sent'; + t.icon = ( +
+ +
+ ); + } + if (t.type === CHARGE_TYPES.NFT_RECIEVED) { + t.name = 'NFT Recieved'; + t.icon = ( +
+ +
+ ); + } + if (t.type === CHARGE_TYPES.NFT_SENT) { + t.name = 'NFT Sent'; + t.icon = ( +
+ +
+ ); + } + + if (t.status === 'error' || t.status === 'fail') { + t.icon = ( +
+ +
+ ); + } + + t.hash = `${t.hash.slice(0, 10)}....`; + t.to = `${t.to.slice(0, 10)}....`; + return t; + }, []); + + const getTokenDetails = useCallback(async tokenId => { + if (!tokenId) return; + const details = await DataService.getNft(tokenId); + if (!details) return; + setTokenDetails(details); + }, []); + + const getTxnDetails = useCallback(async () => { + if (!hash) return; + + let tnx = await DataService.getTx(hash); + if (!tnx) return; + + tnx = reorganizeTxn(tnx); + + await getTokenDetails(tnx?.tokenId); + + setTx(tnx); + }, [hash, reorganizeTxn, getTokenDetails]); useEffect(() => { - (async () => { - const t = await DataService.getTx(hash); - if (t.type === 'charge') { - t.name = `Charge to ${t.from}`; - t.icon = ( -
- -
- ); - } - if (t.type === 'send') { - t.name = 'Send Tokens'; - t.icon = ( -
- -
- ); - } - if (t.type === 'receive') { - t.name = 'Received Tokens'; - t.icon = ( -
- -
- ); - } - if (t.type === 'redeem') { - t.name = 'Redeem Tokens'; - t.icon = ( -
- -
- ); - } - if (t.status === 'error' || t.status === 'fail') { - t.icon = ( -
- -
- ); - } - t.hash = `${t.hash.slice(0, 10)}....`; - t.to = `${t.to.slice(0, 10)}....`; - setTx(t); - })(); - }, [hash]); + let isMounted = true; + if (isMounted) getTxnDetails(); + + return () => { + isMounted = false; + setTx({}); + }; + }, [getTxnDetails]); return ( <> @@ -98,6 +174,28 @@ export default function Main(props) { Amount

{tx.amount}

+ {tokenDetails && ( + <> +
  • + Token Name +

    {tokenDetails?.name}

    +
  • +
  • + Token Description +

    {tokenDetails?.description}

    +
  • +
  • + Token Image + asset +
  • + + )}
    diff --git a/src/modules/transactions/list.js b/src/modules/transactions/list.js index 71189d4..8e68925 100644 --- a/src/modules/transactions/list.js +++ b/src/modules/transactions/list.js @@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'; import { Link } from 'react-router-dom'; import { IoArrowDownOutline, IoArrowForwardOutline } from 'react-icons/io5'; import { GiTwoCoins } from 'react-icons/gi'; +import { AiOutlineSend } from 'react-icons/ai'; import { BiError } from 'react-icons/bi'; import Moment from 'react-moment'; @@ -58,6 +59,22 @@ const TxList = ({ limit, transactions = [] }) => {
    ); } + if (t.type === CHARGE_TYPES.TOKEN_TRANSFER) { + t.name = 'Token Transferred'; + t.icon = ( +
    + +
    + ); + } + if (t.type === CHARGE_TYPES.PAKCAGE_TRANSFER) { + t.name = 'Package Transferred'; + t.icon = ( +
    + +
    + ); + } if (t.type === CHARGE_TYPES.TOKEN_SENT) { t.name = 'Token Sent'; t.icon = ( @@ -74,6 +91,22 @@ const TxList = ({ limit, transactions = [] }) => {
    ); } + if (t.type === CHARGE_TYPES.REDEEMED_PACKAGE) { + t.name = 'NFT Redeemed'; + t.icon = ( +
    + +
    + ); + } + if (t.type === CHARGE_TYPES.REDEEMED_TOKEN) { + t.name = 'Token Redeemed'; + t.icon = ( +
    + +
    + ); + } if (t.type === CHARGE_TYPES.NFT_SENT) { t.name = 'NFT Sent'; t.icon = ( diff --git a/src/modules/transfer/component/Packages.js b/src/modules/transfer/component/Packages.js new file mode 100644 index 0000000..8cdd79d --- /dev/null +++ b/src/modules/transfer/component/Packages.js @@ -0,0 +1,69 @@ +import React, { useEffect, useState, useCallback } from 'react'; +import DataService from '../../../services/db'; + +function Packages(props) { + const { selectedRedeemablePackage, setSelectedRedeemablePackage } = props; + const [packages, setPackages] = useState(null); + + const onCheckBoxClick = tokenId => { + const isPresent = isChecked(tokenId); + if (isPresent) setSelectedRedeemablePackage(prev => prev.filter(elm => elm.tokenId !== tokenId)); + if (!isPresent) { + const newElm = packages.find(elm => elm.tokenId === tokenId); + setSelectedRedeemablePackage(prev => [...prev, newElm]); + } + }; + + const isChecked = tknId => { + return selectedRedeemablePackage.some(elm => elm.tokenId === tknId); + }; + + const getSavedPackages = useCallback(async () => { + const list = await DataService.listNft(); + + list && list.length && setPackages(list); + }, []); + + useEffect(() => { + let isMounted = true; + if (isMounted) getSavedPackages(); + + return () => { + isMounted = false; + }; + }, [getSavedPackages]); + + return ( +
    + {packages?.length > 0 ? ( + packages.map(p => { + return ( +
    +
    + onCheckBoxClick(p.tokenId)} + /> + asset + +
    +

    {p.amount}

    +
    + ); + }) + ) : ( +

    You don't own any packages.

    + )} +
    + ); +} + +export default Packages; diff --git a/src/modules/transfer/index.js b/src/modules/transfer/index.js index 31a65d5..960db2e 100644 --- a/src/modules/transfer/index.js +++ b/src/modules/transfer/index.js @@ -10,21 +10,27 @@ import Loading from '../global/Loading'; import AppHeader from '../layouts/AppHeader'; import ModalWrapper from '../global/ModalWrapper'; import { APP_CONSTANTS } from '../../constants'; -import { TokenService } from '../../services/chain'; -import { isOffline } from '../../utils'; +import { ERC1155_Service, TokenService } from '../../services/chain'; +import { isOffline, isValidAddress } from '../../utils'; import DataService from '../../services/db'; - -const { SCAN_DELAY, SCANNER_PREVIEW_STYLE, SCANNER_CAM_STYLE } = APP_CONSTANTS; - +import Packages from './component/Packages'; +const { SCAN_DELAY, SCANNER_PREVIEW_STYLE, CHARGE_TYPES } = APP_CONSTANTS; +const TAB = { + TOKEN: 'token', + PACKAGE: 'packgae' +}; export default function Index(props) { const { agency, wallet, setTokenBalance } = useContext(AppContext); let history = useHistory(); let toAddress = props.match.params.address; - const [sendAmount, setSendAmount] = useState(''); const [sendToAddress, setSendToAddress] = useState(''); const [loading, showLoading] = useState(null); const [scanModal, setScanModal] = useState(false); + const [selectedTab, setSelectedTab] = useState(TAB.TOKEN); + const [selectedRedeemablePackage, setSelectedRedeemablePackage] = useState([]); + const [isAddressValid, setAddressValid] = useState(true); + const changeTab = tab => setSelectedTab(tab); const handleScanModalToggle = () => setScanModal(!scanModal); @@ -99,7 +105,7 @@ export default function Index(props) { resetFormStates(); DataService.addTx({ hash: receipt.transactionHash, - type: 'send', + type: CHARGE_TYPES.TOKEN_TRANSFER, timestamp: Date.now(), amount: data.sendAmount, to: data.sendToAddress, @@ -116,7 +122,73 @@ export default function Index(props) { } }; - const handleSendClick = () => { + const checkAddress = () => { + const isValid = isValidAddress(sendToAddress); + setAddressValid(isValid); + return isValid; + }; + + const transferPackages = async () => { + if (!selectedRedeemablePackage?.length) return; + showLoading('Transferring packages. Please wait...'); + const ids = []; + const amount = []; + try { + selectedRedeemablePackage.forEach(item => { + ids.push(item.tokenId); + amount.push(item.amount); + }); + + const trasnaction = await ERC1155_Service(agency?.address, wallet).batchRedeem( + wallet.address, + sendToAddress, + ids, + amount + ); + + const tx = { + hash: trasnaction.transactionHash, + type: CHARGE_TYPES.PAKCAGE_TRANSFER, + timestamp: Date.now(), + amount: amount.reduce((prevVal, curVal) => prevVal + curVal, 0), + to: sendToAddress, + from: wallet.address, + status: 'success', + tokenId: ids + }; + await DataService.addTx(tx); + await DataService.batchDecrementNft(ids, amount); + setSelectedRedeemablePackage([]); + showLoading(null); + await Swal.fire({ + icon: 'success', + title: 'Success', + text: 'Successfully Redeemed Pacakages' + }); + } catch (err) { + console.log({ err }); + showLoading(null); + + Swal.fire({ + title: 'Error', + text: `Couldnot redeem package`, + icon: 'error' + }); + } + }; + + const confirmSendPackage = () => { + return Swal.fire({ + title: 'Warning', + icon: 'warning', + text: 'All amount of selected packages will be transferred.', + showCancelButton: true + }); + }; + + const handleTokenSendClick = () => { + const isValid = checkAddress(); + if (!isValid) return; if (isOffline('Cannot transfer while you are offline. Please connect to the Internet and try again.')) return; if (!sendAmount || !sendToAddress) { @@ -125,6 +197,14 @@ export default function Index(props) { confirmAndSend({ sendAmount, sendToAddress }); }; + const handlePackageSendClick = async () => { + const isValid = checkAddress(); + if (!isValid) return; + if (isOffline('Cannot transfer while you are offline. Please connect to the Internet and try again.')) return; + const isConfirm = await confirmSendPackage(); + if (isConfirm.value) transferPackages(); + }; + useEffect(() => { (async () => { if (toAddress) setSendToAddress(toAddress); @@ -168,89 +248,163 @@ export default function Index(props) { return ( <> -
    - +
    +
    + +
    -
    -
    -
    -
    -
    -
    - -
    - - - - -
    - +
    +
    +
    + +
    + + + + +
    + +
    +
    + {!isAddressValid &&

    Invalid Address.

    } +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    + + + + +
    +
    + + Important: Please double check the address and amount before + sending. Transactions cannot be reversed. + +
    +
    + +
    -
    -
    -
    - - + - - - + +
    + + Important: Please double check the address and packages before + sending. Transactions cannot be reversed. + +
    +
    + +
    -
    - - Important: Please double check the address and amount before sending. - Transactions cannot be reversed. - -
    - -
    -
    - +
    diff --git a/src/modules/wallet/backup/index.js b/src/modules/wallet/backup/index.js index b7943c6..896727c 100644 --- a/src/modules/wallet/backup/index.js +++ b/src/modules/wallet/backup/index.js @@ -1,40 +1,27 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import Swal from 'sweetalert2'; - import AppHeader from '../../layouts/AppHeader'; import ModalWrapper from '../../global/ModalWrapper'; -import { APP_CONSTANTS } from '../../../constants'; import Wallet from '../../../utils/blockchain/wallet'; import Loading from '../../global/Loading'; import BackupInfo from './info'; - -const { PASSCODE_LENGTH } = APP_CONSTANTS; +import DataService from '../../../services/db'; export default function Index() { const [passcode, setPasscode] = useState(''); const [loading, setLoading] = useState(false); const [phrases, setPhrases] = useState([]); - - const [passcodeModal, setPasscodeModal] = useState(false); + const [isQueryingDb, setQuerying] = useState(true); const [phraseModal, setPhraseModal] = useState(false); const togglePasscodeModal = () => { - setPasscodeModal(!passcodeModal); + fetchPhrasesAndLoad(passcode); }; const togglePhraseModal = () => { setPhraseModal(!phraseModal); }; - const handlePasscodeChange = e => { - const { value } = e.target; - setPasscode(e.target.value); - if (value.length === PASSCODE_LENGTH) { - togglePasscodeModal(); - return fetchPhrasesAndLoad(value); - } - }; - const fetchPhrasesAndLoad = async passcode => { try { setLoading(true); @@ -50,38 +37,67 @@ export default function Index() { } }; - const resetStates = () => { + const resetStates = async () => { setLoading(false); - setPasscode(''); - togglePhraseModal(); + + const { isConfirmed, isDismissed, isDenied } = await Swal.fire({ + title: 'Warning!', + text: 'Have you really backed up your passphrase ?', + icon: 'warning', + showDenyButton: true, + confirmButtonText: 'Yes', + denyButtonText: 'Cancel' + }); + + try { + if (isConfirmed) { + togglePhraseModal(); + await DataService.saveHasBackedUp(true); + window.location.replace('/'); + } + if (isDenied || isDismissed) { + togglePhraseModal(); + throw Error('You should backup this passphrase'); + } + } catch (err) { + await DataService.saveHasBackedUp(false); + Swal.fire({ + title: 'Warning!', + text: err?.message, + icon: 'warning', + confirmButtonText: 'Ok' + }); + } }; const handlePhraseSaveClick = () => { resetStates(); }; + const getPhone = useCallback(async () => { + try { + const { phone } = await DataService.getProfile(); + if (!phone) throw Error('Unable to get passcode'); + setPasscode(phone); + } catch (err) { + console.log({ err }); + throw err; + } + }, []); + + useEffect(() => { + getPhone() + .then(() => setQuerying(false)) + .catch(() => { + setQuerying(true); + Swal.fire('ERROR', 'Couldnot get necessary data', 'error').then(() => window.location.replace('/')); + }); + return () => setQuerying(true); + }, [getPhone]); + return ( <> - -
    -
    -

    Choose a {PASSCODE_LENGTH}-digit passcode.

    - - -
    -
    -
    {/* 12 words phrase modal */} 0 ? phrases.map((word, ind) => { return ( -
    +
    @@ -118,7 +134,7 @@ export default function Index() { - + ); } diff --git a/src/modules/wallet/restoreMnemonic.js b/src/modules/wallet/restoreMnemonic.js index 282f9f8..1a636d8 100644 --- a/src/modules/wallet/restoreMnemonic.js +++ b/src/modules/wallet/restoreMnemonic.js @@ -1,8 +1,8 @@ import React, { useContext, useState, createRef } from 'react'; import { useHistory } from 'react-router-dom'; import Swal from 'sweetalert2'; - import { AppContext } from '../../contexts/AppContext'; +import { getVendorByWallet } from '../../services'; import DataService from '../../services/db'; import Wallet from '../../utils/blockchain/wallet'; import Loading from '../global/Loading'; @@ -12,7 +12,7 @@ export default function RestoreMnemonic() { let history = useHistory(); const { setWallet } = useContext(AppContext); const [loading, setLoading] = useState(false); - + const [errorMsg, setErrorMsg] = useState(null); const wordRefs = React.useRef([]); if (wordRefs.current.length !== wordCount) { wordRefs.current = Array(wordCount) @@ -25,23 +25,23 @@ export default function RestoreMnemonic() { window.location.replace('/'); }; - const confirmBackup = async () => { - const isConfirm = await Swal.fire({ - title: 'Success', - icon: 'success', - html: `Would you like to backup your wallet in Google Drive`, - showCancelButton: true, - confirmButtonColor: '#3085d6', - cancelButtonColor: '#d33', - confirmButtonText: 'Yes, Backup', - cancelButtonText: 'No, Take to homepage' - }); - if (isConfirm.value) { - history.push('/google/backup'); - } else { - history.push('/'); - } - }; + // const confirmBackup = async () => { + // const isConfirm = await Swal.fire({ + // title: 'Success', + // icon: 'success', + // html: `Would you like to backup your wallet in Google Drive`, + // showCancelButton: true, + // confirmButtonColor: '#3085d6', + // cancelButtonColor: '#d33', + // confirmButtonText: 'Yes, Backup', + // cancelButtonText: 'No, Take to homepage' + // }); + // if (isConfirm.value) { + // history.push('/google/backup'); + // } else { + // history.push('/'); + // } + // }; const handlePaste = e => { e.preventDefault(); @@ -77,18 +77,45 @@ export default function RestoreMnemonic() { rows.push(column); } + const checkWalletRegistered = async (walletAddress, passcode) => { + try { + setLoading('Checking if wallet is registered'); + const walletReg = await getVendorByWallet(walletAddress); + const { phone } = walletReg; + if (passcode !== phone) { + setErrorMsg('Incorrect phone number. Redirecting to home screen.'); + setTimeout(() => { + history.push('/'); + }, [1500]); + } + + setLoading(false); + return walletReg; + } catch (err) { + setErrorMsg('Wallet not registered, Redirecting to home screen.'); + await DataService.clearAll(); + setTimeout(() => { + history.push('/'); + }, [1500]); + } + }; + const restoreWallet = async mnemonic => { try { setLoading(true); let passcode = await DataService.get('temp_passcode'); const res = await Wallet.create(passcode, mnemonic); const { wallet, encryptedWallet } = res; - setWallet(wallet); - DataService.saveAddress(wallet.address); - await DataService.saveWallet(encryptedWallet); - DataService.remove('temp_passcode'); - setLoading(false); - return confirmBackup(); + const walletReged = await checkWalletRegistered(wallet?.address, passcode); + + if (walletReged) { + setWallet(wallet); + await DataService.saveAddress(wallet.address); + await DataService.saveWallet(encryptedWallet); + await DataService.saveHasBackedUp(true); + setLoading(false); + history.push('/sync'); + } } catch (err) { Swal.fire('ERROR', err.message, 'error'); setLoading(false); @@ -129,6 +156,15 @@ export default function RestoreMnemonic() { })}
    + {errorMsg && ( + <> +
    + + Error: {errorMsg} + +
    + + )}