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

Commit 2ac31f6

Browse files
authored
Data vault view insert (#14)
* Create authenticated component/container to hold the header and navigtation. - will handle navigation next. * Create navigation with useState() - Using useState() instead of routes right now becuase it is only two pages. Keeping it simple - Empty DataVault component/container that will be populated next. - Add test for navigation file * Create Add Declarative Details UI, update scss and add icons. * Add DeclarativeDetails display and update styles on Add. * Move navigation and header into the authenticated folder. - These are not reusable components but specific to the id manager. * Fix broken test by wrapping it in act(). * Fix broken test by wrapping it in act(). * Add DataVault pinner service - Save pinner client into context - Create reducer to hold DV content, and connect it to components - Create config endpoint for DV * Connect "Add New" component to datavault - Create create operations to connect DV - Ad loading component and update button and input styles when loading. * Fix test and create defaultstate for context. * Move DV config to JSON files and handle null - Save if it has DataVault in redux. Will be used in multiple places soon - Do not show navigation tab for datavault if none for network - Remove test that will always fail * Remove hasDataVault from redux - get the vault from context in the authenticated component and pass down to navigation. * Implement ipfs-cpinner-client...beta2 version - Save item in redux using its key - Content is saved with its id to be used as the <tr> key and will be used for delete/update - Update frontend and tests to reflect the changes
1 parent 67cd951 commit 2ac31f6

40 files changed

+977
-88
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"typescript": "^4.0.3"
2828
},
2929
"devDependencies": {
30+
"@rsksmart/ipfs-cpinner-client": "^0.1.1-beta.2",
3031
"@rsksmart/rlogin": "0.0.1-beta.3",
3132
"@testing-library/jest-dom": "^5.11.4",
3233
"@testing-library/react": "^11.1.0",

src/app/App.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useContext } from 'react'
22
import '../assets/scss/_index.scss'
33
import { version } from '../../package.json'
44
import LoginScreenContainer from './Login/LoginScreenContainer'
5-
import DashboardContainer from './Dashboard/DashboardContainer'
5+
import AuthenticatedContainer from './Authenticated/AuthenticatedContainer'
66
import RifFooter from '../components/RifFooter/RifFooter'
77
import { Web3ProviderContext } from '../providerContext'
88

@@ -13,7 +13,7 @@ const App = () => {
1313
return (
1414
<div className={isLoggedIn ? 'app loggedin' : 'app login'}>
1515
{isLoggedIn
16-
? <DashboardContainer />
16+
? <AuthenticatedContainer />
1717
: <LoginScreenContainer context={context} />
1818
}
1919
<RifFooter isLoggedIn={isLoggedIn} version={version} />
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React, { useContext, useState } from 'react'
2+
import HeaderComponent from './components/HeaderComponent'
3+
import Navigation, { screens } from './components/Navigation'
4+
import DashboardContainer from '../Dashboard/DashboardContainer'
5+
import DataVaultContainer from '../DataVault/DataVaultContainer'
6+
import { Web3ProviderContext } from '../../providerContext'
7+
8+
interface AuthenticatedComponentInterface {
9+
chainId: number | null
10+
address: string | null
11+
}
12+
13+
const AuthenticatedComponent: React.FC<AuthenticatedComponentInterface> = ({ chainId, address }) => {
14+
const [screen, setScreen] = useState<screens>(screens.DASHBOARD)
15+
const context = useContext(Web3ProviderContext)
16+
17+
return (
18+
<>
19+
<HeaderComponent chainId={chainId} did={address} />
20+
<Navigation
21+
selected={screen}
22+
handleClick={(screen: screens) => setScreen(screen)}
23+
showDataVault={!!context.dvClient}
24+
/>
25+
{screen === screens.DASHBOARD && <DashboardContainer />}
26+
{screen === screens.DATAVAULT && <DataVaultContainer />}
27+
</>
28+
)
29+
}
30+
31+
export default AuthenticatedComponent
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { connect } from 'react-redux'
2+
import { stateInterface } from '../state/configureStore'
3+
import AuthenticatedComponent from './AuthenticatedComponent'
4+
5+
const mapStateToProps = (state: stateInterface) => ({
6+
address: state.identity.address,
7+
chainId: state.identity.chainId
8+
})
9+
10+
export default connect(mapStateToProps)(AuthenticatedComponent)

src/components/Header/HeaderComponent.tsx renamed to src/app/Authenticated/components/HeaderComponent.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import React from 'react'
2-
import rifIdManager from '../../assets/images/rif-id-manager-gray.svg'
3-
import NetworkStatus from '../NetworkStatus/NetworkStatus'
4-
import ToolTip from '../Tooltip/Tooltip'
5-
import { truncateAddressDid } from '../../formatters'
2+
import rifIdManager from '../../../assets/images/rif-id-manager-gray.svg'
3+
import NetworkStatus from '../../../components/NetworkStatus/NetworkStatus'
4+
import ToolTip from '../../../components/Tooltip/Tooltip'
5+
import { truncateAddressDid } from '../../../formatters'
66

77
interface HeaderComponentInterface {
88
did: string | null
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from 'react'
2+
import { shallow, mount } from 'enzyme'
3+
import Navigation, { screens } from './Navigation'
4+
5+
describe('Screen: Dashboard', () => {
6+
it('renders the component', () => {
7+
const wrapper = shallow(<Navigation selected='DASHBOARD' handleClick={jest.fn()} />)
8+
expect(wrapper).toBeDefined()
9+
})
10+
11+
it('sets the active item', () => {
12+
const wrapper = mount(<Navigation selected={screens.DASHBOARD} handleClick={jest.fn()} />)
13+
expect(wrapper.find('li.active').text()).toBe('Dashboard')
14+
})
15+
16+
it('function clicks the correct item', () => {
17+
const onClick = jest.fn()
18+
const wrapper = shallow(<Navigation selected={screens.DASHBOARD} handleClick={onClick} showDataVault />)
19+
wrapper.find('button').at(1).simulate('click')
20+
expect(onClick).toBeCalledWith(screens.DATAVAULT)
21+
})
22+
})
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/* eslint-disable no-unused-vars */
2+
import React from 'react'
3+
4+
export enum screens {
5+
DASHBOARD = 'dashboard',
6+
DATAVAULT = 'datavault'
7+
}
8+
9+
interface NavigationInterface {
10+
selected: string
11+
handleClick: (screen: screens) => void
12+
showDataVault?: boolean
13+
}
14+
15+
const Navigation: React.FC<NavigationInterface> = ({ selected, showDataVault, handleClick }) => (
16+
<div className="container">
17+
<div className="column">
18+
<ul className="navigation">
19+
<li className={selected === screens.DASHBOARD ? 'active' : ''}>
20+
<button onClick={() => handleClick(screens.DASHBOARD)}>Dashboard</button>
21+
</li>
22+
{showDataVault && (
23+
<li className={selected === screens.DATAVAULT ? 'active' : ''}>
24+
<button onClick={() => handleClick(screens.DATAVAULT)}>Data Vault</button>
25+
</li>
26+
)}
27+
<li>Request Credentials</li>
28+
<li>My Dapps</li>
29+
</ul>
30+
</div>
31+
</div>
32+
)
33+
34+
export default Navigation

src/app/Dashboard/DashboardScreen.tsx

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import React from 'react'
2-
import HeaderComponent from '../../components/Header/HeaderComponent'
3-
import Navigation from '../../components/Navigation/Navigation'
42
import IdentityInformationComponent from './panels/IdentityInformation'
53
import { Authentication } from 'did-resolver'
64
import Balance from './panels/Balance'
@@ -21,26 +19,22 @@ const DashboardScreen: React.FC<DashboardScreenInterface> = ({
2119
chainId, address, owner, delegates, tokens, changeOwner, addDelegate, addCustomToken
2220
}) => {
2321
return (
24-
<>
25-
<HeaderComponent chainId={chainId} did={address} />
26-
<div className="content dashboard">
27-
<Navigation />
28-
<IdentityInformationComponent
29-
address={address}
30-
chainId={chainId}
31-
owner={owner}
32-
delegates={delegates}
33-
changeOwner={changeOwner}
34-
addDelegate={addDelegate}
35-
/>
36-
<div className="container">
37-
<div className="column">
38-
<Balance tokens={tokens} addCustomToken={addCustomToken} />
39-
</div>
40-
<div className="column">&nbsp;</div>
22+
<div className="content dashboard">
23+
<IdentityInformationComponent
24+
address={address}
25+
chainId={chainId}
26+
owner={owner}
27+
delegates={delegates}
28+
changeOwner={changeOwner}
29+
addDelegate={addDelegate}
30+
/>
31+
<div className="container">
32+
<div className="column">
33+
<Balance tokens={tokens} addCustomToken={addCustomToken} />
4134
</div>
35+
<div className="column">&nbsp;</div>
4236
</div>
43-
</>
37+
</div>
4438
)
4539
}
4640

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React from 'react'
2+
import DataVaultWebClient from '@rsksmart/ipfs-cpinner-client'
3+
import DeclarativeDetailsDisplay from './panels/DeclarativeDetailsDisplay'
4+
import AddDeclarativeDetails from './panels/AddDeclarativeDetails'
5+
import { DataVaultKey } from '../state/reducers/datavault'
6+
7+
interface DataVaultComponentProps {
8+
declarativeDetails: DataVaultKey
9+
addDeclarativeDetail: (client: DataVaultWebClient, key: string, content: string) => any
10+
}
11+
12+
const DataVaultComponent: React.FC<DataVaultComponentProps> = ({ addDeclarativeDetail, declarativeDetails }) => {
13+
return (
14+
<div className="content data-vault">
15+
<div className="container">
16+
<div className="column">
17+
<AddDeclarativeDetails addDeclarativeDetail={addDeclarativeDetail} />
18+
</div>
19+
</div>
20+
<div className="container">
21+
<div className="column">
22+
<DeclarativeDetailsDisplay details={declarativeDetails} />
23+
</div>
24+
</div>
25+
</div>
26+
)
27+
}
28+
29+
export default DataVaultComponent
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { ThunkDispatch } from 'redux-thunk'
2+
import { connect } from 'react-redux'
3+
import DataVaultWebClient from '@rsksmart/ipfs-cpinner-client'
4+
import { stateInterface } from '../state/configureStore'
5+
import DataVaultComponent from './DataVaultComponent'
6+
import { AnyAction } from 'redux'
7+
import { createDataVaultContent } from '../state/operations/datavault'
8+
9+
const mapStateToProps = (state: stateInterface) => ({
10+
declarativeDetails: state.datavault.data
11+
})
12+
13+
const mapDispatchToProps = (dispatch: ThunkDispatch<stateInterface, {}, AnyAction>) => ({
14+
addDeclarativeDetail: (client: DataVaultWebClient, type: string, content: string) =>
15+
dispatch(createDataVaultContent(client, type, content))
16+
})
17+
18+
export default connect(mapStateToProps, mapDispatchToProps)(DataVaultComponent)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React from 'react'
2+
import { shallow, mount } from 'enzyme'
3+
import AddDeclarativeDetails from './AddDeclarativeDetails'
4+
5+
describe('Component: AddDeclarativeDetails', () => {
6+
it('renders the component', () => {
7+
const wrapper = shallow(<AddDeclarativeDetails addDeclarativeDetail={jest.fn()} />)
8+
expect(wrapper).toBeDefined()
9+
})
10+
11+
it('adds handles errors if type and content is empty', () => {
12+
const submitData = jest.fn()
13+
const wrapper = mount(<AddDeclarativeDetails addDeclarativeDetail={submitData} />)
14+
const button = wrapper.find('button.submit')
15+
button.simulate('click')
16+
expect(submitData).toBeCalledTimes(0)
17+
expect(wrapper.find('.alert.error').text()).toBe('Type and Content cannot be empty.')
18+
19+
wrapper.find('input.type').simulate('change', { target: { value: 'email' } })
20+
expect(wrapper.find('input.type').props().value).toBe('email')
21+
button.simulate('click')
22+
expect(submitData).toBeCalledTimes(0)
23+
})
24+
})
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import React, { useState, useContext } from 'react'
2+
import DataVaultWebClient from '@rsksmart/ipfs-cpinner-client'
3+
import { BaseButton } from '../../../components/Buttons'
4+
import Panel from '../../../components/Panel/Panel'
5+
import uploadIcon from '../../../assets/images/icons/upload.svg'
6+
import { Web3ProviderContext } from '../../../providerContext'
7+
import LoadingComponent from '../../../components/Loading/LoadingComponent'
8+
9+
interface AddDeclarativeDetailsInterface {
10+
addDeclarativeDetail: (client: DataVaultWebClient, key: string, content: string) => Promise<any>
11+
}
12+
13+
const AddDeclarativeDetails: React.FC<AddDeclarativeDetailsInterface> = ({ addDeclarativeDetail }) => {
14+
const context = useContext(Web3ProviderContext)
15+
16+
const [type, setType] = useState('')
17+
const [content, setContent] = useState('')
18+
const [isLoading, setIsLoading] = useState<boolean>(false)
19+
const [isError, setIsError] = useState<string | null>(null)
20+
21+
const handleClick = () => {
22+
setIsLoading(true)
23+
setIsError(null)
24+
25+
if (type === '' || content === '') {
26+
setIsLoading(false)
27+
return setIsError('Type and Content cannot be empty.')
28+
}
29+
30+
context.dvClient && addDeclarativeDetail(context.dvClient, type, content)
31+
.then(() => {
32+
setIsLoading(false)
33+
setContent('')
34+
setType('')
35+
})
36+
.catch((err: Error) => {
37+
setIsLoading(false)
38+
setIsError(err.message)
39+
})
40+
}
41+
42+
const title = <><img src={uploadIcon} /> Add New Declarative Details</>
43+
44+
return (
45+
<Panel title={title} className="add-declarative">
46+
<div className="container">
47+
<div className="column">
48+
<p className="title">Type</p>
49+
<input type="text"
50+
className="line type"
51+
value={type}
52+
onChange={(evt) => setType(evt.target.value)}
53+
disabled={isLoading}
54+
placeholder="Content type" />
55+
</div>
56+
<div className="columnDouble">
57+
<p className="title">Content</p>
58+
<textarea
59+
className="line content"
60+
value={content}
61+
onChange={(evt) => setContent(evt.target.value)}
62+
disabled={isLoading}
63+
placeholder="Content"
64+
/>
65+
</div>
66+
<div className="column submitColumn">
67+
<BaseButton className="submit turquoise" onClick={handleClick} disabled={isLoading}>Add Data</BaseButton>
68+
</div>
69+
</div>
70+
{isError && (
71+
<div className="error container">
72+
<div className="alert error">{isError}</div>
73+
</div>
74+
)}
75+
76+
{isLoading && <LoadingComponent />}
77+
</Panel>
78+
)
79+
}
80+
81+
export default AddDeclarativeDetails
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React from 'react'
2+
import { shallow } from 'enzyme'
3+
import DeclarativeDetailsDisplay from './DeclarativeDetailsDisplay'
4+
import { DataVaultKey } from '../../state/reducers/datavault'
5+
6+
describe('Component: DeclarativeDetailsDisplay', () => {
7+
const mockDeclarativeDetials: DataVaultKey = {
8+
EMAIL: [{ id: '1', content: '[email protected]' }],
9+
NAME: [{ id: '5', content: 'Jesse Clark' }]
10+
}
11+
12+
it('renders the component', () => {
13+
const wrapper = shallow(<DeclarativeDetailsDisplay details={mockDeclarativeDetials} />)
14+
expect(wrapper).toBeDefined()
15+
})
16+
17+
it('shows the content in a row', () => {
18+
const wrapper = shallow(<DeclarativeDetailsDisplay details={mockDeclarativeDetials} />)
19+
expect(wrapper.find('tbody').children()).toHaveLength(2)
20+
21+
expect(wrapper.find('tr').at(1).find('td').at(0).text()).toBe('EMAIL')
22+
expect(wrapper.find('tr').at(1).find('td').at(1).text()).toBe('[email protected]')
23+
})
24+
})

0 commit comments

Comments
 (0)