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

DataVault Summary Panel with max storage #22

Merged
merged 4 commits into from
Dec 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"typescript": "~4.0.5"
},
"devDependencies": {
"@rsksmart/ipfs-cpinner-client": "^0.1.1-beta.2",
"@rsksmart/ipfs-cpinner-client": "^0.1.1-beta.3",
"@rsksmart/rlogin": "0.0.1-beta.3",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
Expand Down
6 changes: 4 additions & 2 deletions src/app/Authenticated/AuthenticatedComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@ const AuthenticatedComponent: React.FC<AuthenticatedComponentInterface> = ({ cha
const [screen, setScreen] = useState<screens>(screens.DASHBOARD)
const context = useContext(Web3ProviderContext)

const changeScreen = (screen: screens) => setScreen(screen)

return (
<>
<HeaderComponent chainId={chainId} did={address} />
<Navigation
selected={screen}
handleClick={(screen: screens) => setScreen(screen)}
handleClick={changeScreen}
showDataVault={!!context.dvClient}
/>
{screen === screens.DASHBOARD && <DashboardContainer />}
{screen === screens.DASHBOARD && <DashboardContainer changeScreen={changeScreen} />}
{screen === screens.DATAVAULT && <DataVaultContainer />}
</>
)
Expand Down
3 changes: 2 additions & 1 deletion src/app/Dashboard/DashboardContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ const mapStateToProps = (state: stateInterface) => ({
chainId: state.identity.chainId,
tokens: state.tokens.tokens,
owner: getOwnerFromDidDoc(state.ethrdid.didDocument),
delegates: state.ethrdid.didDocument.authentication?.filter((pk: Authentication) => !pk.publicKey.endsWith('controller'))
delegates: state.ethrdid.didDocument.authentication?.filter((pk: Authentication) => !pk.publicKey.endsWith('controller')),
storage: state.datavault.storage
})

const mapDispatchToProps = (dispatch: ThunkDispatch<stateInterface, {}, AnyAction>) => ({
Expand Down
11 changes: 9 additions & 2 deletions src/app/Dashboard/DashboardScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,25 @@ import IdentityInformationComponent from './panels/IdentityInformation'
import { Authentication } from 'did-resolver'
import Balance from './panels/Balance'
import { Token } from '../state/reducers/tokens'
import DataVaultSummary from './panels/DataVaultSummary'
import { screens } from '../Authenticated/components/Navigation'
import { DataVaultStorageState } from '../state/reducers/datavault'

interface DashboardScreenInterface {
chainId?: number | null
address: string | null
owner?: string | null
delegates?: Authentication[]
tokens?: Token[]
storage?: DataVaultStorageState
changeOwner: (provider: any, newOwner: string) => any
addDelegate: (provider: any, delegateAddr: string) => any
addCustomToken: (provider: any, tokenAddr: string) => any
changeScreen: (screen: string) => void
}

const DashboardScreen: React.FC<DashboardScreenInterface> = ({
chainId, address, owner, delegates, tokens, changeOwner, addDelegate, addCustomToken
chainId, address, owner, delegates, tokens, changeOwner, addDelegate, addCustomToken, changeScreen, storage
}) => {
return (
<div className="content dashboard">
Expand All @@ -32,7 +37,9 @@ const DashboardScreen: React.FC<DashboardScreenInterface> = ({
<div className="column">
<Balance tokens={tokens} addCustomToken={addCustomToken} />
</div>
<div className="column">&nbsp;</div>
<div className="column">
<DataVaultSummary storage={storage} handleButton={() => changeScreen(screens.DATAVAULT)} />
</div>
</div>
</div>
)
Expand Down
26 changes: 26 additions & 0 deletions src/app/Dashboard/panels/DataVaultSummary.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react'
import { mount } from 'enzyme'
import DataVaultSummary from './DataVaultSummary'

describe('Component: DataVaultSummary.test', () => {
const defaultProps = { storage: { used: 0, available: 0 }, handleButton: jest.fn() }

it('renders the component', () => {
const wrapper = mount(<DataVaultSummary {...defaultProps} />)
expect(wrapper).toBeDefined()
})

it('handles DataVault click', () => {
const handleClick = jest.fn()
const wrapper = mount(<DataVaultSummary {...defaultProps} handleButton={handleClick} />)

wrapper.find('button').simulate('click')
expect(handleClick).toBeCalledTimes(1)
})

it('displays numbers in tooltip', () => {
const props = { storage: { used: 1000, available: 9000 }, handleButton: jest.fn() }
const wrapper = mount(<DataVaultSummary {...props} />)
expect(wrapper.find('.hover-content').first().text()).toBe('1000 of 10,000 bytes')
})
})
32 changes: 32 additions & 0 deletions src/app/Dashboard/panels/DataVaultSummary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react'
import Panel from '../../../components/Panel/Panel'
import datavaultIcon from '../../../assets/images/icons/data-vault.svg'
import { BaseButton } from '../../../components/Buttons'
import ProgressBar from '../../../components/ProgressBar/ProgressBar'
import ToolTip from '../../../components/Tooltip/Tooltip'
import { DataVaultStorageState } from '../../state/reducers/datavault'

interface DataVaultSummaryInterface {
storage?: DataVaultStorageState
handleButton: () => void
}

const DataVaultSummary: React.FC<DataVaultSummaryInterface> = ({ storage, handleButton }) =>
storage ? (
<Panel title={<><img src={datavaultIcon} alt="DataVault" /> DataVault Summary</>} className="dataVault">
<h2>Storage Usage</h2>
<div className="container">
<div className="columnDouble">
<ToolTip className="tooltip-progress" hoverContent={<p>{storage.used} of {(storage.available + storage.used).toLocaleString()} bytes</p>}>
<ProgressBar total={(storage.available + storage.used)} value={storage.used} />
</ToolTip>
</div>
<div className="column">
<BaseButton onClick={handleButton} className="turquoise panel-button">DataVault</BaseButton>
</div>
</div>
</Panel>
)
: <></>

export default DataVaultSummary
17 changes: 16 additions & 1 deletion src/app/state/operations/datavault.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Dispatch } from 'react'
import DataVaultWebClient from '@rsksmart/ipfs-cpinner-client'
import { createDidFormat } from '../../../formatters'
import { addContentToKey, DataVaultContent, receiveKeyData, removeContentfromKey, swapContentById } from '../reducers/datavault'
import { addContentToKey, DataVaultContent, receiveKeyData, removeContentfromKey, swapContentById, receiveStorageInformation, DataVaultStorageState } from '../reducers/datavault'
import { getDataVault } from '../../../config/getConfig'
import { CreateContentResponse } from '@rsksmart/ipfs-cpinner-client/lib/types'

Expand Down Expand Up @@ -60,6 +60,21 @@ export const deleteDataVaultContent = (client: DataVaultWebClient, key: string,
client.delete({ key, id })
.then(() => dispatch(removeContentfromKey({ key, id })))

/**
* Swap content in the datavault by key, and Id
* @param client DataVault client
* @param key Key of object
* @param content New content
* @param id id of the content
*/
export const swapDataVaultContent = (client: DataVaultWebClient, key: string, content: string, id: string) => (dispatch: Dispatch<any>) =>
client.swap({ key, content, id })
.then(() => dispatch(swapContentById({ key, id, content })))

/**
* Returns storage information from DataVault
* @param client DataVault client
*/
export const getStorageInformation = (client: DataVaultWebClient) => (dispatch: Dispatch<any>) =>
client.getStorageInformation()
.then((storage: DataVaultStorageState) => dispatch(receiveStorageInformation({ storage })))
3 changes: 2 additions & 1 deletion src/app/state/operations/identity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { rLogin } from '../../../features/rLogin'
import { changeAccount, changeChainId } from '../reducers/identity'
import { resolveDidDocument } from './ethrdid'
import { getTokenList } from './tokens'
import { createClient, getDataVaultContent } from './datavault'
import { createClient, getDataVaultContent, getStorageInformation } from './datavault'
import { createDidFormat } from '../../../formatters'

/**
Expand All @@ -28,6 +28,7 @@ export const login = (context: any) => (dispatch: Dispatch<any>) =>
context.setDvClient(dataVaultClient)

dataVaultClient && dispatch(getDataVaultContent(dataVaultClient, createDidFormat(address, chainId, true)))
dataVaultClient && dispatch(getStorageInformation(dataVaultClient))
})
})
.catch((err: string) => console.log('rLogin Error', err))
14 changes: 13 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 } from './datavault'
import dataVaultSlice, { DataVaultState, receiveKeyData, initialState, addContentToKey, removeContentfromKey, swapContentById, DataVaultContent, receiveStorageInformation } from './datavault'

describe('dataVault slice', () => {
describe('action creators', () => {
Expand All @@ -22,6 +22,11 @@ describe('dataVault slice', () => {
const content = { key: 'KEY', id: '2', content: 'new' }
expect(swapContentById(content)).toEqual({ type: swapContentById.type, payload: content })
})

test('receiveStorageInformation', () => {
const storage = { used: 150, available: 200 }
expect(receiveStorageInformation({ storage })).toEqual({ type: receiveStorageInformation.type, payload: { storage } })
})
})

describe('reducer', () => {
Expand Down Expand Up @@ -110,5 +115,12 @@ describe('dataVault slice', () => {
expect(store.getState().data.MY_KEY).toMatchObject(initContent)
})
})

describe('receiveStorageInformation', () => {
test('it receives storage information', () => {
store.dispatch(receiveStorageInformation({ storage: { used: 10, available: 15 } }))
expect(store.getState().storage).toMatchObject({ used: 10, available: 15 })
})
})
})
})
14 changes: 12 additions & 2 deletions src/app/state/reducers/datavault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,19 @@ interface SwapPayLoad {
content: string
}

export interface DataVaultStorageState {
used: number,
available: number,
}

export interface DataVaultState {
data: DataVaultKey
storage?: DataVaultStorageState
}

export const initialState: DataVaultState = {
data: {}
data: {},
storage: undefined
}

const dataVaultSlice = createSlice({
Expand All @@ -43,10 +50,13 @@ const dataVaultSlice = createSlice({
},
swapContentById (state: DataVaultState, { payload: { key, id, content } }: PayloadAction<SwapPayLoad>) {
state.data[key] = state.data[key].map((item: DataVaultContent) => item.id === id ? { ...item, content } : item)
},
receiveStorageInformation (state: DataVaultState, { payload: { storage } }: PayloadAction<{ storage: DataVaultStorageState }>) {
state.storage = storage
}
}
})

export const { receiveKeyData, addContentToKey, removeContentfromKey, swapContentById } = dataVaultSlice.actions
export const { receiveKeyData, addContentToKey, removeContentfromKey, swapContentById, receiveStorageInformation } = dataVaultSlice.actions

export default dataVaultSlice.reducer
8 changes: 8 additions & 0 deletions src/assets/images/icons/data-vault.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 39 additions & 7 deletions src/assets/scss/screens/_dashboard.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
.dashboard {
h2 {
font-size: 14px;
font-weight: 500 !important;
color: $gray;
text-transform: uppercase;
}

.identity-information {
.advancedToggle {
border: none;
Expand All @@ -8,13 +15,6 @@
font-size: 14px;
}

h2 {
font-size: 14px;
font-weight: 500 !important;
color: $gray;
text-transform: uppercase;
}

p.value {
color: $black;
font-size: 18px;
Expand Down Expand Up @@ -64,4 +64,36 @@
}
}
}

.dataVault {
.columnDouble {
padding: 0;

.tooltip-progress {
display: block;
width: 100%;
}
}

.column {
padding: 0;
text-align: right;

button {
padding: 10px 25px;
}
}
}
}

@media screen and (max-width: $breakpoint-phone) {
.dashboard {
button.panel-cta {
display: block;
position: relative;
bottom: auto;
right: auto;
margin-top: 15px;
}
}
}
38 changes: 38 additions & 0 deletions src/components/ProgressBar/ProgressBar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react'
import { mount } from 'enzyme'
import ProgressBar from './ProgressBar'

describe('Component: ProgressBar.test', () => {
it('renders the component', () => {
const wrapper = mount(<ProgressBar total={100} value={5} />)
expect(wrapper).toBeDefined()
})

describe('returns correct progress bar width', () => {
it('5 of 100', () => {
const wrapper = mount(<ProgressBar total={100} value={5} />)
expect(wrapper.find('.progress')).toBeDefined()
expect(wrapper.find('.progress').first().props().style).toMatchObject({ width: '5%' })
})

it('26 of 150', () => {
const wrapper = mount(<ProgressBar value={26} total={150} />)
expect(wrapper.find('.progress').first().props().style).toMatchObject({ width: '18%' })
})
})

it('returns 100% if thge total is higher than the value', () => {
const wrapper = mount(<ProgressBar value={82} total={40} />)
expect(wrapper.find('.progress').first().props().style).toMatchObject({ width: '100%' })
})

it('returns 0% if the value is 0', () => {
const wrapper = mount(<ProgressBar value={0} total={40} />)
expect(wrapper.find('.progress').first().props().style).toMatchObject({ width: '0%' })
})

it('shows a visual bar of 1% if the value is less than 1% but not 0', () => {
const wrapper = mount(<ProgressBar value={1} total={1000} />)
expect(wrapper.find('.progress').first().props().style).toMatchObject({ width: '1%' })
})
})
Loading