diff --git a/src/app/Authenticated/AuthenticatedComponent.tsx b/src/app/Authenticated/AuthenticatedComponent.tsx index edda398..88d08d9 100644 --- a/src/app/Authenticated/AuthenticatedComponent.tsx +++ b/src/app/Authenticated/AuthenticatedComponent.tsx @@ -15,14 +15,19 @@ interface AuthenticatedComponentInterface { address: string | null persona: DataVaultKey modifyMultipleItems: (client: DataVaultWebClient, items: DataVaultKey) => any + logout: () => void } -const AuthenticatedComponent: React.FC = ({ chainId, address, persona, modifyMultipleItems }) => { +const AuthenticatedComponent: React.FC = ({ chainId, address, persona, modifyMultipleItems, logout }) => { const [screen, setScreen] = useState(screens.DASHBOARD) const context = useContext(Web3ProviderContext) const changeScreen = (screen: screens) => setScreen(screen) const updatePersona = (items: DataVaultKey) => context.dvClient && modifyMultipleItems(context.dvClient, items) + const handleLogout = () => { + context.reset() + logout() + } return ( <> @@ -37,6 +42,7 @@ const AuthenticatedComponent: React.FC = ({ cha selected={screen} handleClick={changeScreen} showDataVault={!!context.dvClient} + logout={handleLogout} /> {screen === screens.DASHBOARD && } {screen === screens.DATAVAULT && } diff --git a/src/app/Authenticated/AuthenticatedContainer.ts b/src/app/Authenticated/AuthenticatedContainer.ts index 77df460..fc863d1 100644 --- a/src/app/Authenticated/AuthenticatedContainer.ts +++ b/src/app/Authenticated/AuthenticatedContainer.ts @@ -6,6 +6,7 @@ import { stateInterface } from '../state/configureStore' import AuthenticatedComponent from './AuthenticatedComponent' import { modifyMultipleItems } from '../state/operations/datavault' import { DataVaultKey } from '../state/reducers/datavault' +import { logout } from '../state/operations/identity' /** * Get items that are specifically to the Persona from the DataVault collection @@ -31,7 +32,8 @@ const mapStateToProps = (state: stateInterface) => ({ const mapDispatchToProps = (dispatch: ThunkDispatch) => ({ modifyMultipleItems: (client: DataVaultWebClient, items: DataVaultKey) => - dispatch(modifyMultipleItems(client, items)) + dispatch(modifyMultipleItems(client, items)), + logout: () => dispatch(logout()) }) export default connect(mapStateToProps, mapDispatchToProps)(AuthenticatedComponent) diff --git a/src/app/Authenticated/components/Navigation.test.tsx b/src/app/Authenticated/components/Navigation.test.tsx index cd129af..4f7b502 100644 --- a/src/app/Authenticated/components/Navigation.test.tsx +++ b/src/app/Authenticated/components/Navigation.test.tsx @@ -3,21 +3,34 @@ import { shallow, mount } from 'enzyme' import Navigation, { screens } from './Navigation' describe('Screen: Dashboard', () => { + const sharedProps = { + selected: 'DASHBOARD', + handleClick: jest.fn(), + logout: jest.fn() + } it('renders the component', () => { - const wrapper = shallow() + const wrapper = shallow() expect(wrapper).toBeDefined() }) it('sets the active item', () => { - const wrapper = mount() + const wrapper = mount() expect(wrapper.find('li.active').text()).toBe('Dashboard') }) it('function clicks the correct item', () => { const onClick = jest.fn() - const wrapper = mount() + const wrapper = mount() wrapper.find('.datavault').find('button').simulate('click') expect(onClick).toBeCalledWith(screens.DATAVAULT) }) + + it('calls logout when clicked', () => { + const logout = jest.fn() + const wrapper = shallow() + wrapper.find('.logout').find('button').simulate('click') + + expect(logout).toBeCalledTimes(1) + }) }) diff --git a/src/app/Authenticated/components/Navigation.tsx b/src/app/Authenticated/components/Navigation.tsx index 4147617..3aac32f 100644 --- a/src/app/Authenticated/components/Navigation.tsx +++ b/src/app/Authenticated/components/Navigation.tsx @@ -12,9 +12,10 @@ interface NavigationInterface { selected: string handleClick: (screen: screens) => void showDataVault?: boolean + logout: () => void } -const Navigation: React.FC = ({ selected, showDataVault, handleClick }) => ( +const Navigation: React.FC = ({ selected, showDataVault, handleClick, logout }) => (
    @@ -34,6 +35,7 @@ const Navigation: React.FC = ({ selected, showDataVault, ha
  • Request Credentials
  • My Dapps
  • +
diff --git a/src/app/state/operations/identity.ts b/src/app/state/operations/identity.ts index 8f0a2ed..aba9658 100644 --- a/src/app/state/operations/identity.ts +++ b/src/app/state/operations/identity.ts @@ -1,13 +1,17 @@ import { Dispatch } from 'react' import { getAccountAndNetwork } from '../../../ethrpc' -import { rLogin } from '../../../features/rLogin' +import { rLogin, clearRloginStorage } from '../../../features/rLogin' -import { changeAccount, changeChainId } from '../reducers/identity' +import { changeAccount, changeChainId, reset as resetIdentity } from '../reducers/identity' import { resolveDidDocument } from './ethrdid' import { getBalance, getTokenList } from './defi' import { dataVaultStart } from './datavault' +import { reset as resetDV } from '../reducers/datavault' +import { reset as resetDefi } from '../reducers/defi' +import { reset as resetEthrDid } from '../reducers/ethrdid' + /** * Login into web3 provider via rLogin * Saves the web3 provider into context and saves address and chainId to redux @@ -30,3 +34,14 @@ export const login = (context: any) => (dispatch: Dispatch) => }) }) .catch((err: string) => console.log('rLogin Error', err)) + +export const logout = () => (dispatch: Dispatch) => { + // local storage + clearRloginStorage() + + // reducers + dispatch(resetDV()) + dispatch(resetDefi()) + dispatch(resetEthrDid()) + dispatch(resetIdentity()) +} diff --git a/src/app/state/reducers/datavault.test.ts b/src/app/state/reducers/datavault.test.ts index 5f54bb0..e1352f0 100644 --- a/src/app/state/reducers/datavault.test.ts +++ b/src/app/state/reducers/datavault.test.ts @@ -1,5 +1,5 @@ import { configureStore, Store, AnyAction } from '@reduxjs/toolkit' -import dataVaultSlice, { DataVaultState, receiveKeyData, initialState, addContentToKey, removeContentfromKey, swapContentById, DataVaultContent, receiveStorageInformation, receiveKeys } from './datavault' +import dataVaultSlice, { DataVaultState, receiveKeyData, initialState, addContentToKey, removeContentfromKey, swapContentById, DataVaultContent, receiveStorageInformation, receiveKeys, reset } from './datavault' describe('dataVault slice', () => { describe('action creators', () => { @@ -45,6 +45,13 @@ describe('dataVault slice', () => { expect(store.getState()).toEqual(initialState) }) + test('it resets', () => { + store.dispatch(receiveKeys({ keys: ['oneDD', 'twoCredential'] })) + store.dispatch(addContentToKey({ key: 'twoCredential', content: { id: '2', content: 'bye' } })) + store.dispatch(reset()) + expect(store.getState()).toEqual(initialState) + }) + describe('receiveKeyData', () => { test('receiveKeyData', () => { const content = [{ id: '1', content: 'hello' }, { id: '2', content: 'bye' }] diff --git a/src/app/state/reducers/datavault.ts b/src/app/state/reducers/datavault.ts index 2465c73..90c2b3c 100644 --- a/src/app/state/reducers/datavault.ts +++ b/src/app/state/reducers/datavault.ts @@ -67,10 +67,11 @@ const dataVaultSlice = createSlice({ keys.forEach((key: string) => { key.endsWith('Credential') ? state.credentials[key] = [] : state.declarativeDetails[key] = [] }) - } + }, + reset: _state => initialState } }) -export const { receiveKeyData, addContentToKey, removeContentfromKey, swapContentById, receiveStorageInformation, receiveKeys } = dataVaultSlice.actions +export const { receiveKeyData, addContentToKey, removeContentfromKey, swapContentById, receiveStorageInformation, receiveKeys, reset } = dataVaultSlice.actions export default dataVaultSlice.reducer diff --git a/src/app/state/reducers/defi.test.ts b/src/app/state/reducers/defi.test.ts index 8599717..6622a36 100644 --- a/src/app/state/reducers/defi.test.ts +++ b/src/app/state/reducers/defi.test.ts @@ -1,5 +1,5 @@ import { configureStore, Store, AnyAction } from '@reduxjs/toolkit' -import tokenSlice, { addTokenData, DefiState, initialState, tokenInitialState, receiveBalance, receiveConversionBalance } from './defi' +import tokenSlice, { addTokenData, DefiState, initialState, tokenInitialState, receiveBalance, receiveConversionBalance, reset } from './defi' describe('token slide', () => { describe('action creators', () => { @@ -87,5 +87,13 @@ describe('token slide', () => { store.dispatch(receiveConversionBalance({ usd: 18 })) expect(store.getState().conversion).toEqual(18) }) + + test('reset', () => { + store.dispatch(receiveBalance({ balance: 1.846 })) + store.dispatch(receiveConversionBalance({ usd: 18 })) + store.dispatch(addTokenData({ data: { address: '0x123', name: 'test' } })) + store.dispatch(reset()) + expect(store.getState()).toEqual(initialState) + }) }) }) diff --git a/src/app/state/reducers/defi.ts b/src/app/state/reducers/defi.ts index fb02871..80502f6 100644 --- a/src/app/state/reducers/defi.ts +++ b/src/app/state/reducers/defi.ts @@ -50,10 +50,11 @@ const defiSlice = createSlice({ }, receiveConversionBalance (state: DefiState, { payload: { usd } }: PayloadAction<{ usd: number }>) { state.conversion = usd - } + }, + reset: _state => initialState } }) -export const { addTokenData, receiveBalance, receiveConversionBalance } = defiSlice.actions +export const { addTokenData, receiveBalance, receiveConversionBalance, reset } = defiSlice.actions export default defiSlice.reducer diff --git a/src/app/state/reducers/ethrdid.test.ts b/src/app/state/reducers/ethrdid.test.ts index 7de38e9..b471a4e 100644 --- a/src/app/state/reducers/ethrdid.test.ts +++ b/src/app/state/reducers/ethrdid.test.ts @@ -1,6 +1,6 @@ import { configureStore, Store, AnyAction } from '@reduxjs/toolkit' import { DIDDocument } from 'did-resolver' -import ethrdidSlice, { EtherdidState, initialState, resolveDid } from './ethrdid' +import ethrdidSlice, { EtherdidState, initialState, reset, resolveDid } from './ethrdid' describe('ethrdid slice', () => { const data: DIDDocument = { @@ -34,5 +34,11 @@ describe('ethrdid slice', () => { didDocument: data }) }) + + test('reset', () => { + store.dispatch(resolveDid({ data })) + store.dispatch(reset()) + expect(store.getState()).toEqual(initialState) + }) }) }) diff --git a/src/app/state/reducers/ethrdid.ts b/src/app/state/reducers/ethrdid.ts index 85abf64..e6e6dce 100644 --- a/src/app/state/reducers/ethrdid.ts +++ b/src/app/state/reducers/ethrdid.ts @@ -24,10 +24,11 @@ const ethrDidSlice = createSlice({ reducers: { resolveDid (state: EtherdidState, { payload: { data } }: PayloadAction) { state.didDocument = data - } + }, + reset: _state => initialState } }) -export const { resolveDid } = ethrDidSlice.actions +export const { resolveDid, reset } = ethrDidSlice.actions export default ethrDidSlice.reducer diff --git a/src/app/state/reducers/identity.test.ts b/src/app/state/reducers/identity.test.ts index 41149d6..c266970 100644 --- a/src/app/state/reducers/identity.test.ts +++ b/src/app/state/reducers/identity.test.ts @@ -1,5 +1,5 @@ import { configureStore, Store, AnyAction } from '@reduxjs/toolkit' -import identitySlice, { changeAccount, changeChainId, IdentityState, initialState } from './identity' +import identitySlice, { changeAccount, changeChainId, IdentityState, initialState, reset } from './identity' describe('identity slide', () => { const address = '0xf3beac30c498d9e26865f34fcaa57dbb935b0d74' @@ -39,5 +39,12 @@ describe('identity slide', () => { chainId: 30 }) }) + + test('reset', () => { + store.dispatch(changeAccount({ address })) + store.dispatch(changeChainId({ chainId: 30 })) + store.dispatch(reset()) + expect(store.getState()).toEqual(initialState) + }) }) }) diff --git a/src/app/state/reducers/identity.ts b/src/app/state/reducers/identity.ts index dea336a..4434962 100644 --- a/src/app/state/reducers/identity.ts +++ b/src/app/state/reducers/identity.ts @@ -27,10 +27,11 @@ const identitySlice = createSlice({ }, changeChainId (state: IdentityState, { payload: { chainId } }: PayloadAction) { state.chainId = chainId - } + }, + reset: _state => initialState } }) -export const { changeAccount, changeChainId } = identitySlice.actions +export const { changeAccount, changeChainId, reset } = identitySlice.actions export default identitySlice.reducer diff --git a/src/assets/scss/components/_navigation.scss b/src/assets/scss/components/_navigation.scss index 3ff4098..3eaebe4 100644 --- a/src/assets/scss/components/_navigation.scss +++ b/src/assets/scss/components/_navigation.scss @@ -32,6 +32,10 @@ ul.navigation { li.disabled { color: $lightGray; } + + li.logout { + float: right + } } @media screen and (max-width: $breakpoint-phone) { @@ -42,5 +46,9 @@ ul.navigation { display: block; padding: 5px 0; } + + li.logout { + float: none + } } } diff --git a/src/features/rLogin.ts b/src/features/rLogin.ts index 9f5d483..02f9c79 100644 --- a/src/features/rLogin.ts +++ b/src/features/rLogin.ts @@ -19,3 +19,15 @@ export const rLogin = new RLogin({ }, supportedChains: [1, 30, 31, 5777] }) + +export const clearRloginStorage = () => { + localStorage.removeItem('RLOGIN_ACCESS_TOKEN') + localStorage.removeItem('RLOGIN_REFRESH_TOKEN') + localStorage.removeItem('walletconnect') + + Object.keys(localStorage).map((key: string) => { + if (key.startsWith('DV_ACCESS_TOKEN') || key.startsWith('DV_REFRESH_TOKEN')) { + localStorage.removeItem(key) + } + }) +} diff --git a/src/providerContext.tsx b/src/providerContext.tsx index ac3a54a..02575c9 100644 --- a/src/providerContext.tsx +++ b/src/providerContext.tsx @@ -6,11 +6,13 @@ export interface Web3ProviderContextInterface { setProvider?: (value: any) => void dvClient: DataVaultWebClient | null, setDvClient?: (client: DataVaultWebClient) => void + reset: () => void } export const Web3ProviderContext = React.createContext({ provider: null, - dvClient: null + dvClient: null, + reset: () => {} }) interface Web3ProviderElementInterface { @@ -25,7 +27,11 @@ export const Web3ProviderElement: React.FC = ({ ch provider: provider, setProvider: (provider: any) => setProvider(provider), dvClient: dvClient, - setDvClient: (client: DataVaultWebClient) => setDvClient(client) + setDvClient: (client: DataVaultWebClient | null) => setDvClient(client), + reset: () => { + setProvider(null) + setDvClient(null) + } } return (