import { PayloadAction } from '@reduxjs/toolkit' import * as userApi from '../../apis/userApi' import { GOOGLE_TRACKING_ACTIONS, GOOGLE_TRACKING_CATEGORIES } from '../../constants/googleTracking' import CustomRouter from '../../CustomRouter' import { connectorLocalStorageKey } from '../../hooks/useAuth' import { userKeys } from '../../hooks/user/userKeys' import { call, put, select, takeLatest } from 'redux-saga/effects' import { bigNumberToDecimal } from '../../utils/bigNumber' // import { getDAPPTokenContract, getKOBTokenContract } from 'utils/contractHelpers' import { CustomError, showToastError } from '../../utils/errorHelper' import { trackEventWithGA } from '../../utils/googleTracking' import { simpleRpcProvider } from '../../utils/providers' import { getUserData, removeUserData, setInfoRegisterAccount, setUserData } from '../../utils/storage' import { toastSuccess } from '../../utils/uiHelper' import { getPublicAddress, getSignature } from '../../utils/walletHelper' import { ConnectorNames } from '../../utils/web3React' import { userActions as actions } from '.' import { selectUserData } from './selectors' import { ActiveAccountProps, CheckEmailProps, ConnectWalletProps, IncreaseAllowanceProps, LoginWithDappProps, RefreshQRCodeProps, ResendEmailActiveProps, ResetPasswordProps, SendEmailForgotPasswordProps, SignUpWithDappProps, UpdateProfileProps, } from './types' import { queryClient } from '../../pages/_app' export function* getAdjustedBalances() { const { publicAddress } = yield select(selectUserData) try { let balanceList = [] // const DAPPTokenContract = getDAPPTokenContract() // const KOBTokenContract = getKOBTokenContract() // const bnbBalance = yield simpleRpcProvider.getBalance(publicAddress) // const DAPPBalance = yield call(DAPPTokenContract.balanceOf, publicAddress) // const KOBBalance = yield call(KOBTokenContract.balanceOf, publicAddress) // balanceList = [ // { // adjustedBalance: parseFloat(bigNumberToDecimal(bnbBalance.toString())), // balance: bnbBalance.toString(), // ...tokenList.BLOCKCHAIN, // }, // // { // // adjustedBalance: parseFloat(bigNumberToDecimal(DAPPBalance.toString())), // // balance: DAPPBalance.toString(), // // decimal: '18', // // isTokenBEP20: true, // // tokenAddress: DAPPTokenContract.address, // // tokenName: 'Billionaire Plus Token', // // tokenSymbol: 'DAPP', // // }, // // { // // adjustedBalance: parseFloat(bigNumberToDecimal(KOBBalance.toString())), // // balance: KOBBalance.toString(), // // decimal: '18', // // isTokenBEP20: true, // // tokenAddress: KOBTokenContract.address, // // tokenName: 'King of Business Token', // // tokenSymbol: 'KOB', // // }, // ] // const response = yield call(userApi.getAdjustedBalances) // const { // isSuccess, // data: { items = [] }, // } = response // if (isSuccess) { // const balanceItem = items.find((item) => item.tokenSymbol === 'IKOB') // if (balanceItem) { // balanceList.push(balanceItem) // } // } yield put(actions.getAdjustedBalancesSuccess(balanceList)) } catch (error) { yield put(actions.getAdjustedBalancesFailed()) showToastError(error) } } export function* loginWithMetaMask(action: PayloadAction) { const { payload: publicAddress } = action let response = null let category = GOOGLE_TRACKING_CATEGORIES.LOGIN_WITH_METAMASK try { response = yield call(userApi.checkExistPublicAddress, { publicAddress }) } catch (error) { response = yield call(userApi.register, { publicAddress }) category = GOOGLE_TRACKING_CATEGORIES.REGISTER_WITH_METAMASK } finally { const { isSuccess, data } = response if (isSuccess) { const nonce = data.nonce const signature = yield call(getSignature, publicAddress, nonce) if (signature) { response = yield call(userApi.loginWithMetaMask, { publicAddress, signature }) if (response.isSuccess) { const userData = response.data.result setUserData(JSON.stringify(userData)) toastSuccess({ title: 'Success', message: 'Login successfully.', }) trackEventWithGA(category, GOOGLE_TRACKING_ACTIONS.ACCOUNT_ACTION, true) queryClient.setQueryData(userKeys.currentUser(), () => userData) yield put(actions.loginWithMetaMaskSuccess(userData)) return } } else { yield put(actions.loginWithMetaMaskFailed()) } } trackEventWithGA(category, GOOGLE_TRACKING_ACTIONS.ACCOUNT_ACTION, false) } } export function* loginWithDapp(action: PayloadAction) { const { payload } = action const { email, password } = payload let response = null let isActionSuccess = true try { response = yield call(userApi.loginWithDapp, { email, password, isWebLogin: true }) if (response.isSuccess) { const userData = response.data const { publicAddress } = userData const accountMetamask = yield call(getPublicAddress) if (!accountMetamask) { return yield put(actions.loginWithDappFailed()) } else if (!publicAddress) { setInfoRegisterAccount({ email, password, repeatPassword: password, initialStep: 2 }) throw new CustomError( 'Your account is not connected to any Metamask wallet address. Please connect your Metamask address before login.', ) } else if (accountMetamask !== publicAddress) { throw new CustomError('Your account dont match with your Metamask address.') } localStorage.setItem(connectorLocalStorageKey, ConnectorNames.Injected) yield put(actions.loginWithDappSuccess(userData)) setUserData(JSON.stringify(userData)) toastSuccess({ title: 'Success', message: 'Login successfully.', }) } else { throw new CustomError('Login failed.') } } catch (error) { isActionSuccess = false yield put(actions.loginWithDappFailed()) showToastError(error) if ((error as any)?.data?.message === 'You are not activated, please check email!') { setInfoRegisterAccount({ email, password, repeatPassword: password, initialStep: 1 }) CustomRouter.push('/auth/register') } } finally { trackEventWithGA( GOOGLE_TRACKING_CATEGORIES.LOGIN_WITH_DAPP_ACCOUNT, GOOGLE_TRACKING_ACTIONS.ACCOUNT_ACTION, isActionSuccess, ) } } export function* connectWallet(action: PayloadAction) { try { const { email, password } = action.payload const publicAddress = yield call(getPublicAddress) let userData = null const resp = yield call(userApi.loginWithDapp, { email, password }) const { isSuccess, data } = resp if (isSuccess) { userData = data } let config = { headers: { authorization: `Bearer ${userData.accessToken}`, }, } const nonce = userData.nonce const signature = yield call(getSignature, publicAddress, nonce) if (signature) { const response = yield call(userApi.connectWallet, { publicAddress, signature, config }) if (response.isSuccess) { yield put(actions.connectWalletSuccess()) toastSuccess({ title: 'Success', message: 'Connect wallet successfully.', }) return } else { throw new CustomError('Connect wallet failed!') } } else { throw new CustomError('You need to sign the message to be able to connect with Metamask.') } } catch (error) { yield put(actions.connectWalletFailed()) showToastError(error) } } export function* signUpWithDapp(action: PayloadAction) { const { payload } = action const { email, password, parentCode } = payload let isActionSuccess = true try { yield call(userApi.signUpWithDapp, { email, password, parentCode }) yield put(actions.signUpWithDappSuccess()) toastSuccess({ title: 'Success', message: `We just sent the verification code to ${email}. Please check it!`, }) } catch (error) { isActionSuccess = false yield put(actions.signUpWithDappFailed()) showToastError(error) } finally { trackEventWithGA( GOOGLE_TRACKING_CATEGORIES.REGISTER_WITH_DAPP_ACCOUNT, GOOGLE_TRACKING_ACTIONS.ACCOUNT_ACTION, isActionSuccess, ) } } export function* resendEmailActive(action: PayloadAction) { const { payload } = action const { email } = payload try { yield call(userApi.resendEmailActive, { email }) yield put(actions.resendEmailActiveSuccess()) toastSuccess({ title: 'Success', message: `We just sent the verification code to ${email}. Please check it!`, }) } catch (error) { yield put(actions.resendEmailActiveFailed()) showToastError(error) } } export function* activeAccount(action: PayloadAction) { const { payload } = action const { email, code } = payload let isActionSuccess = true try { yield call(userApi.activeAccount, { email, code }) yield put(actions.activeAccountSuccess()) toastSuccess({ title: 'Success', message: 'Account has been activated!!!', }) } catch (error) { isActionSuccess = false yield put(actions.activeAccountFailed()) showToastError(error) } finally { trackEventWithGA(GOOGLE_TRACKING_CATEGORIES.ACTIVE_ACCOUNT, GOOGLE_TRACKING_ACTIONS.ACCOUNT_ACTION, isActionSuccess) } } export function* sendEmailForgotPassword(action: PayloadAction) { const { payload } = action const { email } = payload let response = null let isActionSuccess = true try { response = yield call(userApi.sendEmailForgotPassword, { email }) if (response.isSuccess) { yield put(actions.sendEmailForgotPasswordSuccess()) toastSuccess({ title: 'Success', message: 'Send email successfully.', }) } else { throw new CustomError('Send email failed.') } } catch (error) { isActionSuccess = false yield put(actions.sendEmailForgotPasswordFailed()) showToastError(error) } finally { trackEventWithGA( GOOGLE_TRACKING_CATEGORIES.FORGOT_PASSWORD_STEP_1, GOOGLE_TRACKING_ACTIONS.ACCOUNT_ACTION, isActionSuccess, ) } } export function* resetPassword(action: PayloadAction) { const { payload } = action const { token, password, email } = payload let response = null let isActionSuccess = true try { response = yield call(userApi.resetPassword, { token, password, email }) const userData = response yield put(actions.resetPasswordSuccess(userData)) toastSuccess({ title: 'Success', message: 'Reset password successfully', }) } catch (error) { isActionSuccess = false yield put(actions.resetPasswordFailed()) showToastError(error) } finally { trackEventWithGA( GOOGLE_TRACKING_CATEGORIES.FORGOT_PASSWORD_STEP_2, GOOGLE_TRACKING_ACTIONS.ACCOUNT_ACTION, isActionSuccess, ) } } export function* logout(action: PayloadAction) { const { payload } = action const isShowToast = payload ? payload.isShowToast : true yield put(actions.getAdjustedBalancesSuccess([])) yield call(removeUserData) if (isShowToast) { toastSuccess({ title: 'Success', message: 'Logout successfully.', }) } trackEventWithGA(GOOGLE_TRACKING_CATEGORIES.LOGOUT, GOOGLE_TRACKING_ACTIONS.ACCOUNT_ACTION, true) } export function* refreshQRCode(action: PayloadAction) { const { payload } = action const { isShowToast } = payload let response = null let isActionSuccess = true try { response = yield call(userApi.refreshQRCode) const { data: { qrCode }, isSuccess, } = response if (isSuccess) { yield put(actions.refreshQRCodeSuccess(qrCode)) if (isShowToast) { toastSuccess({ title: 'Success', message: 'Refresh QR code successfully', }) } } else { throw new CustomError('Refresh QR code failed.') } } catch (error) { isActionSuccess = false yield put(actions.refreshQRCodeFailed()) showToastError(error) } finally { trackEventWithGA( GOOGLE_TRACKING_CATEGORIES.REFRESH_QR_CODE, GOOGLE_TRACKING_ACTIONS.ACCOUNT_ACTION, isActionSuccess, ) } } export function* checkEmail(action: PayloadAction) { try { const { email } = action.payload const responseCheckEmail = yield call(userApi.checkEmail, { email }) const { isSuccess: isEmailExist } = responseCheckEmail if (!isEmailExist) { throw new CustomError('Email is existed.') } yield put(actions.checkEmailSuccess()) } catch (error) { yield put(actions.checkEmailFailed()) showToastError(error) } } export function* updateProfile(action: PayloadAction) { let response = null let isActionSuccess = true try { const { userId, userData } = action.payload const { email, oldEmail } = userData // If email changed => check email before update profile if (oldEmail !== email) { const responseCheckEmail = yield call(userApi.checkEmail, { email }) const { isSuccess: isEmailExist } = responseCheckEmail if (isEmailExist === false) { yield put(actions.checkEmailFailed()) throw new CustomError('Email is existed.') } yield put(actions.checkEmailSuccess()) } response = yield call(userApi.updateProfile, { userId, userData }) const { data, isSuccess } = response if (isSuccess) { const newUserData = { ...getUserData(), ...data } setUserData(JSON.stringify(newUserData)) yield put(actions.updateProfileSuccess({ userData: newUserData })) } else { throw new CustomError('Update profile failed!') } } catch (error) { isActionSuccess = false yield put(actions.updateProfileFailed()) showToastError(error) } finally { trackEventWithGA( GOOGLE_TRACKING_CATEGORIES.UPDATE_PROFILE_STEP_1, GOOGLE_TRACKING_ACTIONS.ACCOUNT_ACTION, isActionSuccess, ) } } export function* verifyInformationChange(action: PayloadAction) { const { payload } = action const { email, code } = payload let response = null let isActionSuccess = true try { response = yield call(userApi.activeAccount, { email, code }) const { isSuccess, data } = response if (isSuccess) { const newUserData = { ...getUserData(), ...data } setUserData(JSON.stringify(newUserData)) yield put(actions.verifyInformationChangeSuccess({ userData: newUserData })) toastSuccess({ title: 'Success', message: 'Verify information change successfully!!!', }) } else { throw new CustomError('Update profile failed.') } // Router.push('/profile/view') } catch (error) { isActionSuccess = false yield put(actions.verifyInformationChangeFailed()) showToastError(error) } finally { trackEventWithGA( GOOGLE_TRACKING_CATEGORIES.UPDATE_PROFILE_STEP_2, GOOGLE_TRACKING_ACTIONS.ACCOUNT_ACTION, isActionSuccess, ) } } /** * Increase allowance for sender address * @param action */ export function* increaseAllowance(action: PayloadAction) { let isActionSuccess = true const { TokenContract, spenderAddress, increaseAllowanceValue, callback } = action.payload try { const data = yield call(TokenContract.increaseAllowance, spenderAddress, increaseAllowanceValue) const receipt = yield call(data.wait) const { status } = receipt if (status) { toastSuccess({ title: 'Success', message: 'Increase Allowance successfully.', }) callback() yield put(actions.increaseAllowanceSuccess()) // turn off loading yield put(actions.getAdjustedBalances()) } else { throw new CustomError('Failed avatar sale.') } } catch (error) { isActionSuccess = false yield put(actions.increaseAllowanceFailed()) showToastError(error) } finally { const tokenContractName = yield call(TokenContract.symbol) trackEventWithGA( GOOGLE_TRACKING_CATEGORIES[`INCREASE_ALLOWANCE_${tokenContractName}`], GOOGLE_TRACKING_ACTIONS.ACCOUNT_ACTION, isActionSuccess, ) } } export default function* userSaga() { yield takeLatest(actions.loginWithMetaMask.type, loginWithMetaMask) yield takeLatest(actions.loginWithDapp.type, loginWithDapp) yield takeLatest(actions.signUpWithDapp.type, signUpWithDapp) yield takeLatest(actions.activeAccount.type, activeAccount) yield takeLatest(actions.sendEmailForgotPassword.type, sendEmailForgotPassword) yield takeLatest(actions.resetPassword.type, resetPassword) yield takeLatest(actions.checkEmail.type, checkEmail) yield takeLatest(actions.updateProfile.type, updateProfile) yield takeLatest(actions.verifyInformationChange.type, verifyInformationChange) yield takeLatest(actions.refreshQRCode.type, refreshQRCode) yield takeLatest(actions.getAdjustedBalances.type, getAdjustedBalances) yield takeLatest(actions.logout.type, logout) yield takeLatest(actions.increaseAllowance.type, increaseAllowance) yield takeLatest(actions.connectWallet.type, connectWallet) yield takeLatest(actions.resendEmailActive.type, resendEmailActive) }