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

Commit be96e94

Browse files
authored
DataVault Summary Panel with max storage (#22)
* Build DataVault Summary Panel on Dashboard - Show MaxStorage, button to DataVault screen - New component: progress bar that takes total and value * Implement getStorageInformation operation and redux value - Static storage until beta3 is published * Use beta3 version of the ipfs pinner - Implement size operation - Show sizes on hover as bytes - Progress bar will show at least 1% if the value is more than 0 but less than 1%. This is visual for the user to at least see a value * final cleanup.
1 parent 9268633 commit be96e94

17 files changed

+255
-25
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"typescript": "~4.0.5"
2929
},
3030
"devDependencies": {
31-
"@rsksmart/ipfs-cpinner-client": "^0.1.1-beta.2",
31+
"@rsksmart/ipfs-cpinner-client": "^0.1.1-beta.3",
3232
"@rsksmart/rlogin": "0.0.1-beta.3",
3333
"@testing-library/jest-dom": "^5.11.4",
3434
"@testing-library/react": "^11.1.0",

src/app/Authenticated/AuthenticatedComponent.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,17 @@ const AuthenticatedComponent: React.FC<AuthenticatedComponentInterface> = ({ cha
1414
const [screen, setScreen] = useState<screens>(screens.DASHBOARD)
1515
const context = useContext(Web3ProviderContext)
1616

17+
const changeScreen = (screen: screens) => setScreen(screen)
18+
1719
return (
1820
<>
1921
<HeaderComponent chainId={chainId} did={address} />
2022
<Navigation
2123
selected={screen}
22-
handleClick={(screen: screens) => setScreen(screen)}
24+
handleClick={changeScreen}
2325
showDataVault={!!context.dvClient}
2426
/>
25-
{screen === screens.DASHBOARD && <DashboardContainer />}
27+
{screen === screens.DASHBOARD && <DashboardContainer changeScreen={changeScreen} />}
2628
{screen === screens.DATAVAULT && <DataVaultContainer />}
2729
</>
2830
)

src/app/Dashboard/DashboardContainer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ const mapStateToProps = (state: stateInterface) => ({
1717
chainId: state.identity.chainId,
1818
tokens: state.tokens.tokens,
1919
owner: getOwnerFromDidDoc(state.ethrdid.didDocument),
20-
delegates: state.ethrdid.didDocument.authentication?.filter((pk: Authentication) => !pk.publicKey.endsWith('controller'))
20+
delegates: state.ethrdid.didDocument.authentication?.filter((pk: Authentication) => !pk.publicKey.endsWith('controller')),
21+
storage: state.datavault.storage
2122
})
2223

2324
const mapDispatchToProps = (dispatch: ThunkDispatch<stateInterface, {}, AnyAction>) => ({

src/app/Dashboard/DashboardScreen.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,25 @@ import IdentityInformationComponent from './panels/IdentityInformation'
33
import { Authentication } from 'did-resolver'
44
import Balance from './panels/Balance'
55
import { Token } from '../state/reducers/tokens'
6+
import DataVaultSummary from './panels/DataVaultSummary'
7+
import { screens } from '../Authenticated/components/Navigation'
8+
import { DataVaultStorageState } from '../state/reducers/datavault'
69

710
interface DashboardScreenInterface {
811
chainId?: number | null
912
address: string | null
1013
owner?: string | null
1114
delegates?: Authentication[]
1215
tokens?: Token[]
16+
storage?: DataVaultStorageState
1317
changeOwner: (provider: any, newOwner: string) => any
1418
addDelegate: (provider: any, delegateAddr: string) => any
1519
addCustomToken: (provider: any, tokenAddr: string) => any
20+
changeScreen: (screen: string) => void
1621
}
1722

1823
const DashboardScreen: React.FC<DashboardScreenInterface> = ({
19-
chainId, address, owner, delegates, tokens, changeOwner, addDelegate, addCustomToken
24+
chainId, address, owner, delegates, tokens, changeOwner, addDelegate, addCustomToken, changeScreen, storage
2025
}) => {
2126
return (
2227
<div className="content dashboard">
@@ -32,7 +37,9 @@ const DashboardScreen: React.FC<DashboardScreenInterface> = ({
3237
<div className="column">
3338
<Balance tokens={tokens} addCustomToken={addCustomToken} />
3439
</div>
35-
<div className="column">&nbsp;</div>
40+
<div className="column">
41+
<DataVaultSummary storage={storage} handleButton={() => changeScreen(screens.DATAVAULT)} />
42+
</div>
3643
</div>
3744
</div>
3845
)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React from 'react'
2+
import { mount } from 'enzyme'
3+
import DataVaultSummary from './DataVaultSummary'
4+
5+
describe('Component: DataVaultSummary.test', () => {
6+
const defaultProps = { storage: { used: 0, available: 0 }, handleButton: jest.fn() }
7+
8+
it('renders the component', () => {
9+
const wrapper = mount(<DataVaultSummary {...defaultProps} />)
10+
expect(wrapper).toBeDefined()
11+
})
12+
13+
it('handles DataVault click', () => {
14+
const handleClick = jest.fn()
15+
const wrapper = mount(<DataVaultSummary {...defaultProps} handleButton={handleClick} />)
16+
17+
wrapper.find('button').simulate('click')
18+
expect(handleClick).toBeCalledTimes(1)
19+
})
20+
21+
it('displays numbers in tooltip', () => {
22+
const props = { storage: { used: 1000, available: 9000 }, handleButton: jest.fn() }
23+
const wrapper = mount(<DataVaultSummary {...props} />)
24+
expect(wrapper.find('.hover-content').first().text()).toBe('1000 of 10,000 bytes')
25+
})
26+
})
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React from 'react'
2+
import Panel from '../../../components/Panel/Panel'
3+
import datavaultIcon from '../../../assets/images/icons/data-vault.svg'
4+
import { BaseButton } from '../../../components/Buttons'
5+
import ProgressBar from '../../../components/ProgressBar/ProgressBar'
6+
import ToolTip from '../../../components/Tooltip/Tooltip'
7+
import { DataVaultStorageState } from '../../state/reducers/datavault'
8+
9+
interface DataVaultSummaryInterface {
10+
storage?: DataVaultStorageState
11+
handleButton: () => void
12+
}
13+
14+
const DataVaultSummary: React.FC<DataVaultSummaryInterface> = ({ storage, handleButton }) =>
15+
storage ? (
16+
<Panel title={<><img src={datavaultIcon} alt="DataVault" /> DataVault Summary</>} className="dataVault">
17+
<h2>Storage Usage</h2>
18+
<div className="container">
19+
<div className="columnDouble">
20+
<ToolTip className="tooltip-progress" hoverContent={<p>{storage.used} of {(storage.available + storage.used).toLocaleString()} bytes</p>}>
21+
<ProgressBar total={(storage.available + storage.used)} value={storage.used} />
22+
</ToolTip>
23+
</div>
24+
<div className="column">
25+
<BaseButton onClick={handleButton} className="turquoise panel-button">DataVault</BaseButton>
26+
</div>
27+
</div>
28+
</Panel>
29+
)
30+
: <></>
31+
32+
export default DataVaultSummary

src/app/state/operations/datavault.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Dispatch } from 'react'
22
import DataVaultWebClient from '@rsksmart/ipfs-cpinner-client'
33
import { createDidFormat } from '../../../formatters'
4-
import { addContentToKey, DataVaultContent, receiveKeyData, removeContentfromKey, swapContentById } from '../reducers/datavault'
4+
import { addContentToKey, DataVaultContent, receiveKeyData, removeContentfromKey, swapContentById, receiveStorageInformation, DataVaultStorageState } from '../reducers/datavault'
55
import { getDataVault } from '../../../config/getConfig'
66
import { CreateContentResponse } from '@rsksmart/ipfs-cpinner-client/lib/types'
77

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

63+
/**
64+
* Swap content in the datavault by key, and Id
65+
* @param client DataVault client
66+
* @param key Key of object
67+
* @param content New content
68+
* @param id id of the content
69+
*/
6370
export const swapDataVaultContent = (client: DataVaultWebClient, key: string, content: string, id: string) => (dispatch: Dispatch<any>) =>
6471
client.swap({ key, content, id })
6572
.then(() => dispatch(swapContentById({ key, id, content })))
73+
74+
/**
75+
* Returns storage information from DataVault
76+
* @param client DataVault client
77+
*/
78+
export const getStorageInformation = (client: DataVaultWebClient) => (dispatch: Dispatch<any>) =>
79+
client.getStorageInformation()
80+
.then((storage: DataVaultStorageState) => dispatch(receiveStorageInformation({ storage })))

src/app/state/operations/identity.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { rLogin } from '../../../features/rLogin'
55
import { changeAccount, changeChainId } from '../reducers/identity'
66
import { resolveDidDocument } from './ethrdid'
77
import { getTokenList } from './tokens'
8-
import { createClient, getDataVaultContent } from './datavault'
8+
import { createClient, getDataVaultContent, getStorageInformation } from './datavault'
99
import { createDidFormat } from '../../../formatters'
1010

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

3030
dataVaultClient && dispatch(getDataVaultContent(dataVaultClient, createDidFormat(address, chainId, true)))
31+
dataVaultClient && dispatch(getStorageInformation(dataVaultClient))
3132
})
3233
})
3334
.catch((err: string) => console.log('rLogin Error', err))

src/app/state/reducers/datavault.test.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { configureStore, Store, AnyAction } from '@reduxjs/toolkit'
2-
import dataVaultSlice, { DataVaultState, receiveKeyData, initialState, addContentToKey, removeContentfromKey, swapContentById, DataVaultContent } from './datavault'
2+
import dataVaultSlice, { DataVaultState, receiveKeyData, initialState, addContentToKey, removeContentfromKey, swapContentById, DataVaultContent, receiveStorageInformation } from './datavault'
33

44
describe('dataVault slice', () => {
55
describe('action creators', () => {
@@ -22,6 +22,11 @@ describe('dataVault slice', () => {
2222
const content = { key: 'KEY', id: '2', content: 'new' }
2323
expect(swapContentById(content)).toEqual({ type: swapContentById.type, payload: content })
2424
})
25+
26+
test('receiveStorageInformation', () => {
27+
const storage = { used: 150, available: 200 }
28+
expect(receiveStorageInformation({ storage })).toEqual({ type: receiveStorageInformation.type, payload: { storage } })
29+
})
2530
})
2631

2732
describe('reducer', () => {
@@ -110,5 +115,12 @@ describe('dataVault slice', () => {
110115
expect(store.getState().data.MY_KEY).toMatchObject(initContent)
111116
})
112117
})
118+
119+
describe('receiveStorageInformation', () => {
120+
test('it receives storage information', () => {
121+
store.dispatch(receiveStorageInformation({ storage: { used: 10, available: 15 } }))
122+
expect(store.getState().storage).toMatchObject({ used: 10, available: 15 })
123+
})
124+
})
113125
})
114126
})

src/app/state/reducers/datavault.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,19 @@ interface SwapPayLoad {
2020
content: string
2121
}
2222

23+
export interface DataVaultStorageState {
24+
used: number,
25+
available: number,
26+
}
27+
2328
export interface DataVaultState {
2429
data: DataVaultKey
30+
storage?: DataVaultStorageState
2531
}
2632

2733
export const initialState: DataVaultState = {
28-
data: {}
34+
data: {},
35+
storage: undefined
2936
}
3037

3138
const dataVaultSlice = createSlice({
@@ -43,10 +50,13 @@ const dataVaultSlice = createSlice({
4350
},
4451
swapContentById (state: DataVaultState, { payload: { key, id, content } }: PayloadAction<SwapPayLoad>) {
4552
state.data[key] = state.data[key].map((item: DataVaultContent) => item.id === id ? { ...item, content } : item)
53+
},
54+
receiveStorageInformation (state: DataVaultState, { payload: { storage } }: PayloadAction<{ storage: DataVaultStorageState }>) {
55+
state.storage = storage
4656
}
4757
}
4858
})
4959

50-
export const { receiveKeyData, addContentToKey, removeContentfromKey, swapContentById } = dataVaultSlice.actions
60+
export const { receiveKeyData, addContentToKey, removeContentfromKey, swapContentById, receiveStorageInformation } = dataVaultSlice.actions
5161

5262
export default dataVaultSlice.reducer
Lines changed: 8 additions & 0 deletions
Loading

src/assets/scss/screens/_dashboard.scss

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
.dashboard {
2+
h2 {
3+
font-size: 14px;
4+
font-weight: 500 !important;
5+
color: $gray;
6+
text-transform: uppercase;
7+
}
8+
29
.identity-information {
310
.advancedToggle {
411
border: none;
@@ -8,13 +15,6 @@
815
font-size: 14px;
916
}
1017

11-
h2 {
12-
font-size: 14px;
13-
font-weight: 500 !important;
14-
color: $gray;
15-
text-transform: uppercase;
16-
}
17-
1818
p.value {
1919
color: $black;
2020
font-size: 18px;
@@ -64,4 +64,36 @@
6464
}
6565
}
6666
}
67+
68+
.dataVault {
69+
.columnDouble {
70+
padding: 0;
71+
72+
.tooltip-progress {
73+
display: block;
74+
width: 100%;
75+
}
76+
}
77+
78+
.column {
79+
padding: 0;
80+
text-align: right;
81+
82+
button {
83+
padding: 10px 25px;
84+
}
85+
}
86+
}
87+
}
88+
89+
@media screen and (max-width: $breakpoint-phone) {
90+
.dashboard {
91+
button.panel-cta {
92+
display: block;
93+
position: relative;
94+
bottom: auto;
95+
right: auto;
96+
margin-top: 15px;
97+
}
98+
}
6799
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React from 'react'
2+
import { mount } from 'enzyme'
3+
import ProgressBar from './ProgressBar'
4+
5+
describe('Component: ProgressBar.test', () => {
6+
it('renders the component', () => {
7+
const wrapper = mount(<ProgressBar total={100} value={5} />)
8+
expect(wrapper).toBeDefined()
9+
})
10+
11+
describe('returns correct progress bar width', () => {
12+
it('5 of 100', () => {
13+
const wrapper = mount(<ProgressBar total={100} value={5} />)
14+
expect(wrapper.find('.progress')).toBeDefined()
15+
expect(wrapper.find('.progress').first().props().style).toMatchObject({ width: '5%' })
16+
})
17+
18+
it('26 of 150', () => {
19+
const wrapper = mount(<ProgressBar value={26} total={150} />)
20+
expect(wrapper.find('.progress').first().props().style).toMatchObject({ width: '18%' })
21+
})
22+
})
23+
24+
it('returns 100% if thge total is higher than the value', () => {
25+
const wrapper = mount(<ProgressBar value={82} total={40} />)
26+
expect(wrapper.find('.progress').first().props().style).toMatchObject({ width: '100%' })
27+
})
28+
29+
it('returns 0% if the value is 0', () => {
30+
const wrapper = mount(<ProgressBar value={0} total={40} />)
31+
expect(wrapper.find('.progress').first().props().style).toMatchObject({ width: '0%' })
32+
})
33+
34+
it('shows a visual bar of 1% if the value is less than 1% but not 0', () => {
35+
const wrapper = mount(<ProgressBar value={1} total={1000} />)
36+
expect(wrapper.find('.progress').first().props().style).toMatchObject({ width: '1%' })
37+
})
38+
})

0 commit comments

Comments
 (0)