From e0f837958bae368f0fe77632c3e2f3a2e6e210b6 Mon Sep 17 00:00:00 2001 From: OlhaD Date: Mon, 21 Aug 2023 13:37:51 -0400 Subject: [PATCH 1/2] fix: fix login issue Token is taken from the context instead of the LocalStorage --- src/api/transfers.js | 6 +++-- src/api/wallets.js | 6 +++-- src/pages/MyTransfers/MyTransfers.js | 10 ++++++-- src/pages/SendTokens/SendTokens.js | 8 +++++-- .../SendTokens/SendTokensForm/SelectWallet.js | 13 ++++++++--- src/pages/Wallet/Wallet.js | 9 +++++--- src/pages/Wallet/WalletHeader/WalletHeader.js | 2 +- .../Wallet/WalletHeader/WalletHeaderStyled.js | 4 ++-- src/store/AuthProvider.js | 5 ++-- src/store/auth-context.js | 2 ++ src/utils/apiClient.js | 23 ++++++++++--------- 11 files changed, 58 insertions(+), 30 deletions(-) diff --git a/src/api/transfers.js b/src/api/transfers.js index e8453d2..3e1316d 100644 --- a/src/api/transfers.js +++ b/src/api/transfers.js @@ -1,7 +1,7 @@ import apiClient from '../utils/apiClient'; import { makeQueryString } from '../utils/formatting'; -export const getTransfers = async ({ pagination, filter }) => { +export const getTransfers = async (token, { pagination, filter }) => { try { const where = filter.getWhereObj(); // pagination: limit, offset @@ -13,7 +13,9 @@ export const getTransfers = async ({ pagination, filter }) => { const queryString = makeQueryString(transferFilter); - const response = await apiClient.get(`/transfers?${queryString}`); + const response = await apiClient + .setAuthHeader(token) + .get(`/transfers?${queryString}`); return response.data; } catch (error) { console.error(error); diff --git a/src/api/wallets.js b/src/api/wallets.js index a383ff5..bc35db3 100644 --- a/src/api/wallets.js +++ b/src/api/wallets.js @@ -10,7 +10,7 @@ const mapWallet = (walletData, nameProp) => { }; }; -export const getWallets = async (name = '', pageNumber = 1) => { +export const getWallets = async (token, name = '', pageNumber = 1) => { const params = { offset: pageNumber - 1, }; @@ -20,6 +20,7 @@ export const getWallets = async (name = '', pageNumber = 1) => { } const { total, wallets } = await apiClient + .setAuthHeader(token) .get('/wallets', { params: { name: name || undefined, // Pass 'name' if it exists, or pass 'undefined' to exclude it @@ -46,8 +47,9 @@ export const getWallets = async (name = '', pageNumber = 1) => { }; }; -export const getWalletById = async (id) => { +export const getWalletById = async (token, id) => { const walletData = await apiClient + .setAuthHeader(token) .get('/wallets/' + id) .then((response) => { return mapWallet(response.data, 'wallet'); diff --git a/src/pages/MyTransfers/MyTransfers.js b/src/pages/MyTransfers/MyTransfers.js index 30b8aaf..fec0727 100644 --- a/src/pages/MyTransfers/MyTransfers.js +++ b/src/pages/MyTransfers/MyTransfers.js @@ -1,9 +1,10 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { Grid } from '@mui/material'; import TransfersTable from './TransfersTable'; import Message from '../../components/UI/components/Message/Message'; import { getTransfers } from '../../api/transfers'; import { useTransfersContext } from '../../store/TransfersContext'; +import AuthContext from '../../store/auth-context'; /**@function * @name MyTransfers @@ -22,12 +23,17 @@ const MyTransfers = () => { // total rows count for pagination const [totalRowCount, setTotalRowCount] = useState(null); + const authContext = useContext(AuthContext); + // load data useEffect(() => { const loadData = async () => { try { setIsLoading(true); - const data = await getTransfers({ pagination, filter }); + const data = await getTransfers(authContext.token, { + pagination, + filter, + }); const preparedRows = prepareRows(await data.transfers); setTableRows(preparedRows); diff --git a/src/pages/SendTokens/SendTokens.js b/src/pages/SendTokens/SendTokens.js index c6a9b3c..41febfc 100644 --- a/src/pages/SendTokens/SendTokens.js +++ b/src/pages/SendTokens/SendTokens.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useContext, useState } from 'react'; import { Paper } from '@mui/material'; import { ContentContainer, @@ -14,7 +14,7 @@ import apiClient from '../../utils/apiClient'; import { Loader } from '../../components/UI/components/Loader/Loader'; import TokenInfoBlock from './TokenInfoBlock/TokenInfoBlock'; import { formatWithCommas } from '../../utils/formatting'; -// import AuthContext from '../../store/auth-context'; +import AuthContext from '../../store/auth-context'; const SendTokens = () => { const [createdWalletName, setCreatedWalletName] = useState(); @@ -25,6 +25,8 @@ const SendTokens = () => { const [senderWalletName, setSenderWalletName] = useState(); const [senderWalletTokens, setSenderWalletTokens] = useState(0); + const authContext = useContext(AuthContext); + // TODO: uncomment when API is ready: is should have a totalTokens value // const [totalTokensAmount, setTotalTokensAmount] = useState(); @@ -62,6 +64,7 @@ const SendTokens = () => { setIsLoading(true); apiClient + .setAuthHeader(authContext.token) .post('/transfers', { bundle: { bundle_size: data.tokensAmount }, sender_wallet: data.senderWallet, @@ -104,6 +107,7 @@ const SendTokens = () => { setIsLoading(true); apiClient + .setAuthHeader(authContext.token) .post('/wallets', { wallet: name, }) diff --git a/src/pages/SendTokens/SendTokensForm/SelectWallet.js b/src/pages/SendTokens/SendTokensForm/SelectWallet.js index 4c2440c..d37ebb4 100644 --- a/src/pages/SendTokens/SendTokensForm/SelectWallet.js +++ b/src/pages/SendTokens/SendTokensForm/SelectWallet.js @@ -1,6 +1,7 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { Autocomplete, Button, TextField } from '@mui/material'; import { getWallets } from '../../../api/wallets'; +import AuthContext from '../../../store/auth-context'; function SelectWallet({ wallet, onChangeWallet, label, createdWalletName }) { const filterLoadMore = 'LOAD_MORE'; @@ -11,12 +12,14 @@ function SelectWallet({ wallet, onChangeWallet, label, createdWalletName }) { const [walletsFullLoadedData, setWalletsFullLoadedData] = useState([]); const [walletSearchString, setWalletSearchString] = useState(''); + const authContext = useContext(AuthContext); + // Is called when page loads and when user starts to type in a 'Wallet' filter useEffect(() => { const getWalletsData = async () => { setWalletPage(0); try { - let response = await getWallets(walletSearchString); + let response = await getWallets(authContext.token, walletSearchString); if (!response) { console.log('No response from getWallets'); @@ -69,7 +72,11 @@ function SelectWallet({ wallet, onChangeWallet, label, createdWalletName }) { } try { - const response = await getWallets(walletSearchString, walletPage); + const response = await getWallets( + authContext.token, + walletSearchString, + walletPage + ); const total = response.total; setWalletsFullLoadedData(response.wallets); diff --git a/src/pages/Wallet/Wallet.js b/src/pages/Wallet/Wallet.js index 31bf10f..25564b5 100644 --- a/src/pages/Wallet/Wallet.js +++ b/src/pages/Wallet/Wallet.js @@ -41,12 +41,15 @@ const Wallet = () => { const fetchData = async () => { try { // get wallet data - const returnedWalletData = await getWalletById(wallet.id); + const returnedWalletData = await getWalletById( + authContext.token, + wallet.id + ); setWallet(returnedWalletData); // get pending transfers data const pendingTransferFilter = new TransferFilter({ state: 'pending' }); - const returnedTransferData = await getTransfers({ + const returnedTransferData = await getTransfers(authContext.token, { filter: pendingTransferFilter, }); setPendingTransfers(returnedTransferData.transfers); @@ -59,7 +62,7 @@ const Wallet = () => { }; fetchData(); - }, []); + }, [authContext.token]); if (isLoading) { return ; diff --git a/src/pages/Wallet/WalletHeader/WalletHeader.js b/src/pages/Wallet/WalletHeader/WalletHeader.js index 525c034..c8e001c 100644 --- a/src/pages/Wallet/WalletHeader/WalletHeader.js +++ b/src/pages/Wallet/WalletHeader/WalletHeader.js @@ -27,7 +27,7 @@ const WalletHeader = ({ pendingTransfers, walletName, walletLogoURL }) => { sx={{ display: 'flex', flexDirection: 'column' }} > {walletName} - + Pending transfers - {pendingTransfers} diff --git a/src/pages/Wallet/WalletHeader/WalletHeaderStyled.js b/src/pages/Wallet/WalletHeader/WalletHeaderStyled.js index 208cffe..0869d79 100644 --- a/src/pages/Wallet/WalletHeader/WalletHeaderStyled.js +++ b/src/pages/Wallet/WalletHeader/WalletHeaderStyled.js @@ -24,8 +24,8 @@ export const WalletTitle = styled(Typography)({ lineHeight: '40px', }); -export const WalletPendingTransfers = styled(Typography)(({ pendingTransfers }) => ({ - color: pendingTransfers === 0 ? '#000000' : '#FF2B2B', +export const WalletPendingTransfers = styled(Typography)(({ pendingtransfers }) => ({ + color: pendingtransfers === 0 ? '#000000' : '#FF2B2B', fontSize: '16px', fontStyle: 'normal', fontWeight: '400', diff --git a/src/store/AuthProvider.js b/src/store/AuthProvider.js index 4ec0b9c..c74a8c4 100644 --- a/src/store/AuthProvider.js +++ b/src/store/AuthProvider.js @@ -1,3 +1,4 @@ +/* eslint-disable no-debugger */ import React, { useState } from 'react'; import jwt_decode from 'jwt-decode'; import AuthContext from './auth-context'; @@ -23,12 +24,12 @@ const AuthProvider = (props) => { const wallet = newWallet ? newWallet : parseToken(newToken); setWallet(wallet); + localStorage.setItem(walletKey, JSON.stringify(wallet)); + if (rememberDetails) { localStorage.setItem(tokenKey, newToken); - localStorage.setItem(walletKey, JSON.stringify(wallet)); } else { localStorage.removeItem(tokenKey); - localStorage.removeItem(walletKey); } setIsLoggedIn(true); diff --git a/src/store/auth-context.js b/src/store/auth-context.js index 71ac6bd..3b7205c 100644 --- a/src/store/auth-context.js +++ b/src/store/auth-context.js @@ -2,6 +2,8 @@ import { createContext } from "react"; const AuthContext = createContext({ isLoggedIn: false, + token: "", + wallet: {}, login: () => {}, logout: () => {}, }); diff --git a/src/utils/apiClient.js b/src/utils/apiClient.js index 892f0cc..63859d5 100644 --- a/src/utils/apiClient.js +++ b/src/utils/apiClient.js @@ -8,18 +8,19 @@ const apiClient = axios.create({ }, }); -apiClient.interceptors.request.use( - (config) => { - const token = localStorage.getItem('token'); - - if (token) { - config.headers.Authorization = `Bearer ${token}`; - } +apiClient.setAuthToken = (token) => { + if (token) { + apiClient.defaults.headers.common['Authorization'] = `Bearer ${token}`; + } else { + delete apiClient.defaults.headers.common['Authorization']; + } +}; - return config; - }, - (error) => Promise.reject(error) -); +// Custom method to set auth header and make API call +apiClient.setAuthHeader = (token) => { + apiClient.setAuthToken(token); + return apiClient; +}; // all unauthorized responses will be redirected to login apiClient.interceptors.response.use( From 6c9b11af6c6d9b385f9792d4b857c0dbe64b78f0 Mon Sep 17 00:00:00 2001 From: OlhaD Date: Mon, 21 Aug 2023 14:33:38 -0400 Subject: [PATCH 2/2] fix tests --- src/pages/MyTransfers/MyTransfers.test.js | 11 ++++------ src/pages/Wallet/Wallet.test.js | 26 +++++++++++------------ 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/pages/MyTransfers/MyTransfers.test.js b/src/pages/MyTransfers/MyTransfers.test.js index 73782fc..eaba2c7 100644 --- a/src/pages/MyTransfers/MyTransfers.test.js +++ b/src/pages/MyTransfers/MyTransfers.test.js @@ -5,10 +5,10 @@ import AuthProvider from '../../store/AuthProvider'; import { BrowserRouter as Router } from 'react-router-dom'; import { TransfersProvider } from '../../store/TransfersContext'; import MyTransfers from './MyTransfers'; -import apiClient from '../../utils/apiClient'; +import { getTransfers } from '../../api/transfers'; -jest.mock('../../utils/apiClient', () => ({ - get: jest.fn(), +jest.mock('../../api/transfers', () => ({ + getTransfers: jest.fn(), })); const mockTransfersData = { @@ -98,21 +98,18 @@ describe('My Transfers page', function () { }); it('renders table pagination correctly', async () => { - apiClient.get.mockResolvedValueOnce({ data: mockTransfersData }); - + getTransfers.mockResolvedValueOnce(mockTransfersData); render( ); - expect(await screen.findByTestId('table-pagination')).toBeInTheDocument(); expect(await screen.findByTestId('transfers-table')).toBeInTheDocument(); expect(await screen.findByTestId('date-range-filter')).toBeInTheDocument(); expect( await screen.findByTestId('transfer-status-filter') ).toBeInTheDocument(); - await waitFor(() => { expect(screen.getAllByRole('row')).toHaveLength(3 + 1); }); diff --git a/src/pages/Wallet/Wallet.test.js b/src/pages/Wallet/Wallet.test.js index 4dc11a6..50fb195 100644 --- a/src/pages/Wallet/Wallet.test.js +++ b/src/pages/Wallet/Wallet.test.js @@ -1,11 +1,11 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; -import apiClient from '../../utils/apiClient'; import { getTransfers } from '../../api/transfers'; import Wallet from './Wallet'; +import { getWalletById } from '../../api/wallets'; -jest.mock('../../utils/apiClient', () => ({ - get: jest.fn(), +jest.mock('../../api/wallets', () => ({ + getWalletById: jest.fn(), })); jest.mock('../../api/transfers', () => ({ @@ -76,6 +76,13 @@ const mockPendingTransfers = { total: 3, }; +const mockWallet = { + id: '9d6c674f-ae62-4fab-8d14-ae5de9f14ab8', + logoUrl: 'https://example.com/logo.png', + tokensInWallet: 100, + name: 'test wallet', +}; + describe('', () => { beforeEach(() => { localStorage.setItem( @@ -92,14 +99,7 @@ describe('', () => { }); it('fetches and displays wallet data', async () => { - apiClient.get.mockResolvedValueOnce({ - data: { - id: '9d6c674f-ae62-4fab-8d14-ae5de9f14ab8', - logo_url: 'https://example.com/logo.png', - tokens_in_wallet: 100, - wallet: 'test wallet', - }, - }); + getWalletById.mockResolvedValueOnce(mockWallet); getTransfers.mockResolvedValueOnce(mockPendingTransfers); @@ -115,11 +115,9 @@ describe('', () => { }); it('displays error message on fetch failure', async () => { - apiClient.get.mockImplementationOnce(() => Promise.reject('API error')); + getWalletById.mockImplementationOnce(() => Promise.reject('API error')); getTransfers.mockImplementation(() => Promise.reject('API error')); - render(); - expect(screen.getByRole('progressbar')).toBeInTheDocument(); // TODO: fix and uncomment