Skip to content
This repository has been archived by the owner on Nov 7, 2023. It is now read-only.

Commit

Permalink
Add logout button (#54)
Browse files Browse the repository at this point in the history
* Add reset option to each reducer and test.

* Add code to reset app on Logout

- Centralized logout function
- Connect to each reset() function in reducers
- Clear localStorage. This will be moved to rLogin in the future

* Add visual logout button and connect it to context.reset() and the operations logout file.
  • Loading branch information
jessgusclark authored Feb 1, 2021
1 parent e93efa6 commit 8d4a45d
Show file tree
Hide file tree
Showing 16 changed files with 118 additions and 22 deletions.
8 changes: 7 additions & 1 deletion src/app/Authenticated/AuthenticatedComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,19 @@ interface AuthenticatedComponentInterface {
address: string | null
persona: DataVaultKey
modifyMultipleItems: (client: DataVaultWebClient, items: DataVaultKey) => any
logout: () => void
}

const AuthenticatedComponent: React.FC<AuthenticatedComponentInterface> = ({ chainId, address, persona, modifyMultipleItems }) => {
const AuthenticatedComponent: React.FC<AuthenticatedComponentInterface> = ({ chainId, address, persona, modifyMultipleItems, logout }) => {
const [screen, setScreen] = useState<screens>(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 (
<>
Expand All @@ -37,6 +42,7 @@ const AuthenticatedComponent: React.FC<AuthenticatedComponentInterface> = ({ cha
selected={screen}
handleClick={changeScreen}
showDataVault={!!context.dvClient}
logout={handleLogout}
/>
{screen === screens.DASHBOARD && <DashboardContainer changeScreen={changeScreen} />}
{screen === screens.DATAVAULT && <DataVaultContainer />}
Expand Down
4 changes: 3 additions & 1 deletion src/app/Authenticated/AuthenticatedContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -31,7 +32,8 @@ const mapStateToProps = (state: stateInterface) => ({

const mapDispatchToProps = (dispatch: ThunkDispatch<stateInterface, {}, AnyAction>) => ({
modifyMultipleItems: (client: DataVaultWebClient, items: DataVaultKey) =>
dispatch(modifyMultipleItems(client, items))
dispatch(modifyMultipleItems(client, items)),
logout: () => dispatch(logout())
})

export default connect(mapStateToProps, mapDispatchToProps)(AuthenticatedComponent)
19 changes: 16 additions & 3 deletions src/app/Authenticated/components/Navigation.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(<Navigation selected='DASHBOARD' handleClick={jest.fn()} />)
const wrapper = shallow(<Navigation {...sharedProps} selected='DASHBOARD' />)
expect(wrapper).toBeDefined()
})

it('sets the active item', () => {
const wrapper = mount(<Navigation selected={screens.DASHBOARD} handleClick={jest.fn()} />)
const wrapper = mount(<Navigation {...sharedProps} selected={screens.DASHBOARD} />)
expect(wrapper.find('li.active').text()).toBe('Dashboard')
})

it('function clicks the correct item', () => {
const onClick = jest.fn()
const wrapper = mount(<Navigation selected={screens.DASHBOARD} handleClick={onClick} showDataVault={true} />)
const wrapper = mount(<Navigation {...sharedProps} selected={screens.DASHBOARD} handleClick={onClick} showDataVault={true} />)

wrapper.find('.datavault').find('button').simulate('click')
expect(onClick).toBeCalledWith(screens.DATAVAULT)
})

it('calls logout when clicked', () => {
const logout = jest.fn()
const wrapper = shallow(<Navigation {...sharedProps} logout={logout} />)
wrapper.find('.logout').find('button').simulate('click')

expect(logout).toBeCalledTimes(1)
})
})
4 changes: 3 additions & 1 deletion src/app/Authenticated/components/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ interface NavigationInterface {
selected: string
handleClick: (screen: screens) => void
showDataVault?: boolean
logout: () => void
}

const Navigation: React.FC<NavigationInterface> = ({ selected, showDataVault, handleClick }) => (
const Navigation: React.FC<NavigationInterface> = ({ selected, showDataVault, handleClick, logout }) => (
<div className="container">
<div className="column">
<ul className="navigation">
Expand All @@ -34,6 +35,7 @@ const Navigation: React.FC<NavigationInterface> = ({ selected, showDataVault, ha
</li>
<li className="disabled">Request Credentials</li>
<li className="disabled">My Dapps</li>
<li className="logout"><button onClick={logout}>Logout</button></li>
</ul>
</div>
</div>
Expand Down
19 changes: 17 additions & 2 deletions src/app/state/operations/identity.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -30,3 +34,14 @@ export const login = (context: any) => (dispatch: Dispatch<any>) =>
})
})
.catch((err: string) => console.log('rLogin Error', err))

export const logout = () => (dispatch: Dispatch<any>) => {
// local storage
clearRloginStorage()

// reducers
dispatch(resetDV())
dispatch(resetDefi())
dispatch(resetEthrDid())
dispatch(resetIdentity())
}
9 changes: 8 additions & 1 deletion src/app/state/reducers/datavault.test.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down Expand Up @@ -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' }]
Expand Down
5 changes: 3 additions & 2 deletions src/app/state/reducers/datavault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
10 changes: 9 additions & 1 deletion src/app/state/reducers/defi.test.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down Expand Up @@ -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)
})
})
})
5 changes: 3 additions & 2 deletions src/app/state/reducers/defi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 7 additions & 1 deletion src/app/state/reducers/ethrdid.test.ts
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down Expand Up @@ -34,5 +34,11 @@ describe('ethrdid slice', () => {
didDocument: data
})
})

test('reset', () => {
store.dispatch(resolveDid({ data }))
store.dispatch(reset())
expect(store.getState()).toEqual(initialState)
})
})
})
5 changes: 3 additions & 2 deletions src/app/state/reducers/ethrdid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ const ethrDidSlice = createSlice({
reducers: {
resolveDid (state: EtherdidState, { payload: { data } }: PayloadAction<ResolveDidPayload>) {
state.didDocument = data
}
},
reset: _state => initialState
}
})

export const { resolveDid } = ethrDidSlice.actions
export const { resolveDid, reset } = ethrDidSlice.actions

export default ethrDidSlice.reducer
9 changes: 8 additions & 1 deletion src/app/state/reducers/identity.test.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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)
})
})
})
5 changes: 3 additions & 2 deletions src/app/state/reducers/identity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ const identitySlice = createSlice({
},
changeChainId (state: IdentityState, { payload: { chainId } }: PayloadAction<ChangeChainIdPayload>) {
state.chainId = chainId
}
},
reset: _state => initialState
}
})

export const { changeAccount, changeChainId } = identitySlice.actions
export const { changeAccount, changeChainId, reset } = identitySlice.actions

export default identitySlice.reducer
8 changes: 8 additions & 0 deletions src/assets/scss/components/_navigation.scss
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ ul.navigation {
li.disabled {
color: $lightGray;
}

li.logout {
float: right
}
}

@media screen and (max-width: $breakpoint-phone) {
Expand All @@ -42,5 +46,9 @@ ul.navigation {
display: block;
padding: 5px 0;
}

li.logout {
float: none
}
}
}
12 changes: 12 additions & 0 deletions src/features/rLogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
})
}
10 changes: 8 additions & 2 deletions src/providerContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<Web3ProviderContextInterface>({
provider: null,
dvClient: null
dvClient: null,
reset: () => {}
})

interface Web3ProviderElementInterface {
Expand All @@ -25,7 +27,11 @@ export const Web3ProviderElement: React.FC<Web3ProviderElementInterface> = ({ 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 (
Expand Down

0 comments on commit 8d4a45d

Please sign in to comment.