├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .lessrc ├── .postcssrc ├── .travis.yml ├── LICENSE-BSD ├── LICENSE-MIT ├── README.md ├── __mocks__ └── fileMock.js ├── __tests__ ├── .eslintrc ├── actions │ ├── safe.spec.js │ └── webIds_actions.spec.js ├── components │ ├── Avatar.spec.js │ └── Editor.spec.js ├── containers │ └── App.spec.js ├── reducers │ └── webIds__reducer.spec.js └── setupTests.js ├── appveyor.yml ├── codeowners ├── jest.config.js ├── package.json ├── public └── index.html ├── src ├── actions │ ├── safe_actions.js │ └── webIds_actions.js ├── components │ ├── Avatar │ │ ├── Avatar.jsx │ │ └── index.js │ ├── Editor │ │ ├── Editor.jsx │ │ └── index.js │ ├── Header │ │ ├── Header.jsx │ │ └── index.js │ ├── IdForm │ │ ├── IdForm.jsx │ │ └── index.js │ └── List │ │ ├── List.jsx │ │ └── index.js ├── constants │ ├── appInfo.js │ ├── index.js │ ├── paths.js │ └── safeConstants.js ├── containers │ ├── App.jsx │ └── global.css ├── index.js └── reducers │ ├── index.js │ ├── safe_reducer.js │ └── webIds_reducer.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ], 6 | "plugins": [ 7 | "@babel/plugin-proposal-class-properties", 8 | [ 9 | "import", 10 | { 11 | "libraryName": "antd", 12 | "libraryDirectory": "es", 13 | "style": "css" 14 | } 15 | ] 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | indent_style = space 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | 14 | [*.{json,js,jsx,html,css}] 15 | indent_style = space 16 | indent_size = 4 17 | 18 | [.eslintrc] 19 | indent_style = space 20 | indent_size = 4 21 | 22 | [*.md] 23 | trim_trailing_whitespace = false 24 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | flow-typed/ 2 | dist/ 3 | node_modules 4 | dll 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "airbnb", 4 | "env": { 5 | "browser": true 6 | }, 7 | "rules": { 8 | "arrow-parens": ["off"], 9 | "brace-style": ["error", "allman"], 10 | "compat/compat": "error", 11 | "consistent-return": "off", 12 | "comma-dangle": "off", 13 | "flowtype-errors/show-errors": "error", 14 | "generator-star-spacing": "off", 15 | "key-spacing": [ "error", { 16 | "singleLine": { 17 | "beforeColon": false, 18 | "afterColon": true 19 | }, 20 | "multiLine": { 21 | "beforeColon": true, 22 | "afterColon": true, 23 | "align": "colon" 24 | } 25 | }], 26 | "import/no-unresolved": "off", 27 | "import/no-extraneous-dependencies": "off", 28 | "indent": ["error", 4, 29 | { "SwitchCase": 1 } 30 | ], 31 | "no-console": "off", 32 | "no-use-before-define": "off", 33 | "no-multi-assign": "off", 34 | "promise/param-names": "error", 35 | "promise/always-return": "error", 36 | "promise/catch-or-return": "error", 37 | "promise/no-native": "off", 38 | "react/sort-comp": ["error", { 39 | "order": ["type-annotations", "static-methods", "lifecycle", "everything-else", "render"] 40 | }], 41 | "react/jsx-no-bind": "off", 42 | "react/jsx-curly-spacing": ["error", "always"], 43 | "react/jsx-indent-props": ["error", 4], 44 | "react/jsx-indent": ["error", 4], 45 | "react/jsx-filename-extension": ["error", { "extensions": [".js", ".jsx"] }], 46 | "react/prefer-stateless-function": "off", 47 | "space-in-parens": ["error", "always"] 48 | }, 49 | "plugins": [ 50 | "flowtype", 51 | "flowtype-errors", 52 | "import", 53 | "promise", 54 | "compat", 55 | "react" 56 | ], 57 | "settings": { 58 | "import/resolver": { 59 | # "webpack": { 60 | # "config": "webpack.config.eslint.js" 61 | # } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | Thank you for contributing to the project! 11 | We recommend you check out our ["Contributing to the SAFE Network"](https://github.com/maidsafe/QA/blob/master/CONTRIBUTING.md) guide if you haven't already. 12 | 13 | **Describe the bug** 14 | A clear and concise description of what the bug is. 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | **Expected behavior** 24 | A clear and concise description of what you expected to happen. 25 | 26 | **Screenshots** 27 | If applicable, add screenshots to help explain your problem. 28 | 29 | **Desktop (please complete the following information):** 30 | - OS: [e.g. iOS] 31 | - Browser [e.g. chrome, safari] 32 | - Version [e.g. 22] 33 | 34 | **Smartphone (please complete the following information):** 35 | - Device: [e.g. iPhone6] 36 | - OS: [e.g. iOS8.1] 37 | - Browser [e.g. stock browser, safari] 38 | - Version [e.g. 22] 39 | 40 | **Additional context** 41 | Add any other context about the problem here. 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | Thank you for contributing to the project! 11 | We recommend you check out our ["Contributing to the SAFE Network"](https://github.com/maidsafe/QA/blob/master/CONTRIBUTING.md) guide if you haven't already. 12 | 13 | **Is your feature request related to a problem? Please describe.** 14 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 15 | 16 | **Describe the solution you'd like** 17 | A clear and concise description of what you want to happen. 18 | 19 | **Describe alternatives you've considered** 20 | A clear and concise description of any alternative solutions or features you've considered. 21 | 22 | **Additional context** 23 | Add any other context or screenshots about the feature request here. 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | .tmp 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | .cache 19 | dist 20 | 21 | # node-waf configuration 22 | .lock-wscript 23 | 24 | # Compiled binary addons (http://nodejs.org/api/addons.html) 25 | build/Release 26 | .eslintcache 27 | 28 | # Dependency directory 29 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 30 | node_modules 31 | app/node_modules 32 | 33 | # OSX 34 | .DS_Store 35 | 36 | .idea 37 | npm-debug.log.* -------------------------------------------------------------------------------- /.lessrc: -------------------------------------------------------------------------------- 1 | //.lessrc 2 | { 3 | "modifyVars": { 4 | "@icon-url" : "./iconfont/iconfont" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.postcssrc: -------------------------------------------------------------------------------- 1 | { 2 | "modules": true, 3 | 4 | } 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | env: 2 | global: 3 | - RUST_BACKTRACE=1 4 | - PATH=$PATH:$HOME/.cargo/bin 5 | - RUST_STABLE=1.26.2 6 | - RUST_NIGHTLY=nightly-2018-06-10 7 | - RUST_RUSTFMT=0.8.2 8 | - RUST_CLIPPY=0.0.207 9 | os: 10 | - linux 11 | - osx 12 | language: rust 13 | rust: 14 | - 1.26.2 15 | - nightly-2018-06-10 16 | sudo: false 17 | cache: 18 | cargo: true 19 | before_script: 20 | - curl -sSL https://github.com/maidsafe/QA/raw/master/travis/cargo_install.sh > cargo_install.sh 21 | - bash cargo_install.sh cargo-prune; 22 | if [ "$TRAVIS_RUST_VERSION" = "$RUST_NIGHTLY" ] && [ "$TRAVIS_OS_NAME" = linux ]; then 23 | bash cargo_install.sh rustfmt-nightly "$RUST_RUSTFMT"; 24 | bash cargo_install.sh clippy "$RUST_CLIPPY"; 25 | fi 26 | script: 27 | - if [ "$TRAVIS_RUST_VERSION" = "$RUST_STABLE" ]; then 28 | ( 29 | set -x; 30 | cargo test --no-default-features --release --verbose 31 | ); 32 | elif [ "$TRAVIS_OS_NAME" = linux ]; then 33 | ( 34 | set -x; 35 | cargo fmt -- --check && 36 | cargo clippy --profile=test 37 | ); 38 | fi 39 | - cargo check --examples 40 | before_cache: 41 | - cargo prune 42 | 43 | -------------------------------------------------------------------------------- /LICENSE-BSD: -------------------------------------------------------------------------------- 1 | Copyright 2018 MaidSafe.net limited. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | 13 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2018 Maidsafe.net limited 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # safe-web-Id-Manager-js 2 | 3 | ## Overview 4 | 5 | 6 | |Linux/macOS|Windows|Issues| 7 | |:--------:|:-----:|:----:| 8 | |[![Build Status](https://travis-ci.com/maidsafe/safe-web-id-manager-js.svg?branch=master)](https://travis-ci.com/maidsafe/safe-web-id-manager-js)|[![Build status](https://ci.appveyor.com/api/projects/status/vyq74b658589wsva/branch/master?svg=true)](https://ci.appveyor.com/project/MaidSafe-QA/safe-web-id-manager-js/branch/master)|[![Stories in Ready](https://badge.waffle.io/maidsafe/safe-web-id-manager-js.png?label=ready&title=Ready)](https://waffle.io/maidsafe/safe-web-id-manager-js)| 9 | 10 | | [MaidSafe website](https://maidsafe.net) | [SAFE Dev Forum](https://forum.safedev.org) | [SAFE Network Forum](https://safenetforum.org) | 11 | |:-------:|:-------:|:-------:| 12 | 13 | ## License 14 | 15 | This SAFE Network application is dual-licensed under the Modified BSD ([LICENSE-BSD](LICENSE-BSD) https://opensource.org/licenses/BSD-3-Clause) or the MIT license ([LICENSE-MIT](LICENSE-MIT) https://opensource.org/licenses/MIT) at your option. 16 | 17 | ## Contributing 18 | 19 | Want to contribute? Great :tada: 20 | 21 | There are many ways to give back to the project, whether it be writing new code, fixing bugs, or just reporting errors. All forms of contributions are encouraged! 22 | 23 | For instructions on how to contribute, see our [Guide to contributing](https://github.com/maidsafe/QA/blob/master/CONTRIBUTING.md). 24 | -------------------------------------------------------------------------------- /__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | export default 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /__tests__/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest/globals": true 4 | }, 5 | "plugins": [ 6 | "jest" 7 | ], 8 | "rules": { 9 | "jest/no-disabled-tests": "warn", 10 | "jest/no-focused-tests": "error", 11 | "jest/no-identical-title": "error" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /__tests__/actions/safe.spec.js: -------------------------------------------------------------------------------- 1 | import * as safe from 'actions/safe_actions'; 2 | import toBeType from 'jest-tobetype'; 3 | 4 | expect.extend( toBeType ); 5 | 6 | describe( 'safe actions', () => 7 | { 8 | it( 'should have types', () => 9 | { 10 | expect( safe.TYPES ).toBeDefined(); 11 | } ); 12 | 13 | it( 'should authorise app', () => 14 | { 15 | expect.assertions( 2 ); 16 | 17 | const expectedAction = { 18 | type : safe.TYPES.SAFE_AUTHORISE, 19 | }; 20 | expect( safe.safeAuthorise( ) ).toMatchObject( expectedAction ); 21 | expect( safe.safeAuthorise( ).payload ).toBeType( 'object' ); // although is specifically a promise... 22 | } ); 23 | } ); 24 | -------------------------------------------------------------------------------- /__tests__/actions/webIds_actions.spec.js: -------------------------------------------------------------------------------- 1 | import * as webIdsActions from 'actions/webIds_actions'; 2 | import toBeType from 'jest-tobetype'; 3 | 4 | expect.extend( toBeType ); 5 | 6 | describe( 'webIds actions', () => 7 | { 8 | it( 'should have types', () => 9 | { 10 | expect( webIdsActions.TYPES ).toBeDefined(); 11 | } ); 12 | 13 | it( 'should add ADD_WEB_ID', async () => 14 | { 15 | expect.assertions(3); 16 | 17 | const payload = { 18 | idApp : {}, 19 | webId: { 20 | name : 'testerton' 21 | } 22 | }; 23 | 24 | const expectedAction = { 25 | type : webIdsActions.TYPES.ADD_WEB_ID 26 | }; 27 | 28 | const res = webIdsActions.addWebId( payload ) 29 | const resultPayload = await res.payload; 30 | 31 | expect( res ).toMatchObject( expectedAction ); 32 | expect( resultPayload ).not.toHaveProperty( 'idApp' ); 33 | expect( resultPayload ).toEqual( payload.webId ); 34 | } ); 35 | 36 | it( 'should UDPATE_WEB_ID', async () => 37 | { 38 | const payload = { 39 | idApp : {}, 40 | webId: { 41 | name : 'testerton update ', 42 | '@id' : '6' 43 | } 44 | }; 45 | 46 | const expectedAction = { 47 | type : webIdsActions.TYPES.UPDATE_WEB_ID, 48 | payload: payload.webId 49 | }; 50 | 51 | const res = webIdsActions.updateWebId( payload ) 52 | const resultPayload = await res.payload; 53 | 54 | expect( res.type ).toEqual( expectedAction.type ); 55 | expect( resultPayload ).not.toHaveProperty( 'idApp' ); 56 | expect( resultPayload.name ).toEqual( payload.webId.name.trim() ); 57 | expect( resultPayload['@id'] ).toEqual( payload.webId['@id'] ); 58 | } ); 59 | 60 | 61 | it( 'should GET_AVAILABLE_WEB_IDS', async () => 62 | { 63 | const payload = { 64 | webId: { 65 | name : 'testerton', 66 | id : 6 67 | } 68 | }; 69 | 70 | const expectedAction = { 71 | type : webIdsActions.TYPES.GET_AVAILABLE_WEB_IDS 72 | }; 73 | 74 | const res = webIdsActions.getAvailableWebIds( payload ) 75 | const resultPayload = await res.payload; 76 | 77 | expect( res ).toMatchObject( expectedAction ); 78 | expect( resultPayload ).toBeType( 'array' ); 79 | // expect( resultPayload ).toEqual( payload.webId ); 80 | } ); 81 | } ); 82 | -------------------------------------------------------------------------------- /__tests__/components/Avatar.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import Avatar from 'components/Avatar/Avatar'; 5 | 6 | describe( 'Avatar', () => 7 | { 8 | let wrapper; 9 | let instance; 10 | let props; 11 | 12 | beforeEach( () => 13 | { 14 | props = { 15 | }; 16 | 17 | wrapper = shallow( ); 18 | instance = wrapper.instance(); 19 | } ); 20 | 21 | describe( 'constructor( props )', () => 22 | { 23 | it( 'should have name Avatar', () => 24 | { 25 | expect( instance.constructor.name ).toBe( 'Avatar' ); 26 | } ); 27 | } ); 28 | 29 | 30 | describe( 'render()', () => 31 | { 32 | beforeEach( () => 33 | { 34 | props = { ...props }; 35 | wrapper = shallow( ); 36 | } ); 37 | 38 | it( 'should contain an Upload component', () => 39 | { 40 | expect( wrapper.find( 'Upload' ).length ).toBe( 1 ); 41 | } ); 42 | 43 | } ); 44 | 45 | } ); 46 | -------------------------------------------------------------------------------- /__tests__/components/Editor.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, mount } from 'enzyme'; 3 | import { MemoryRouter } from 'react-router-dom'; 4 | import Editor from 'components/Editor/Editor'; 5 | 6 | describe( 'Editor', () => 7 | { 8 | let wrapper; 9 | let instance; 10 | let props; 11 | 12 | beforeEach( () => 13 | { 14 | props = { 15 | match : { url: '/editor/lala.lalala' }, 16 | updateWebId : jest.fn(), 17 | webIds : [{ 18 | nick: 'josh', 19 | uri: 'lala.lalala' 20 | }] 21 | }; 22 | 23 | wrapper = shallow( ); 24 | instance = wrapper.instance(); 25 | } ); 26 | 27 | describe( 'constructor( props )', () => 28 | { 29 | it( 'should have name Editor', () => 30 | { 31 | expect( instance.constructor.name ).toBe( 'Editor' ); 32 | } ); 33 | } ); 34 | 35 | describe( 'render()', () => 36 | { 37 | beforeEach( () => 38 | { 39 | props = { ...props, match: { url: '/editor/josh'} }; 40 | wrapper = mount( ); 41 | } ); 42 | 43 | it( 'should not have idForm if no name given', () => 44 | { 45 | expect( wrapper.find( 'IdForm' ).length ).toBe( 0 ); 46 | } ); 47 | 48 | it( 'should have idForm if id given', () => 49 | { 50 | const testProps = { ...props }; 51 | testProps.match.params = { id: 'lala.lalala' } 52 | 53 | wrapper = mount( 54 | // 55 | 56 | // 57 | ); 58 | 59 | expect( wrapper.find( 'IdForm' ).length ).toBe( 1 ); 60 | } ); 61 | } ); 62 | 63 | } ); 64 | -------------------------------------------------------------------------------- /__tests__/containers/App.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, mount, render } from 'enzyme'; 3 | import { MemoryRouter } from 'react-router-dom'; 4 | import App from 'containers/App'; 5 | 6 | import { createStore, applyMiddleware } from 'redux'; 7 | import thunk from 'redux-thunk'; 8 | import rootReducer from 'reducers'; 9 | 10 | function configureStore( initialState ) 11 | { 12 | return createStore( 13 | rootReducer, 14 | initialState, 15 | applyMiddleware( thunk ) 16 | ); 17 | } 18 | 19 | describe( 'App', () => 20 | { 21 | let wrapper; 22 | const initialEntries = ['/']; 23 | let props; 24 | 25 | beforeEach( () => 26 | { 27 | window.safe = { initialiseApp: jest.fn() } 28 | 29 | const store = configureStore( {} ); 30 | 31 | props = { 32 | store : configureStore( {} ) 33 | }; 34 | 35 | wrapper = mount( 36 | 37 | ); 38 | } ); 39 | 40 | 41 | describe( 'render()', () => 42 | { 43 | it( 'should contain a Switch component', () => 44 | { 45 | expect( wrapper.find( 'Switch' ).length ).toBe( 1 ); 46 | } ); 47 | 48 | 49 | it( 'should have a Header', () => 50 | { 51 | props = { ...props }; 52 | 53 | wrapper = mount( 54 | 55 | ); 56 | expect( wrapper.find( 'Header' ).length ).toBe( 1 ); 57 | } ); 58 | 59 | } ); 60 | 61 | 62 | describe( '/', () => 63 | { 64 | it( 'should have a List (as it redirects)', () => 65 | { 66 | props = { ...props }; 67 | 68 | wrapper = mount( 69 | 70 | ); 71 | expect( wrapper.find( 'List' ).length ).toBe( 1 ); 72 | } ); 73 | 74 | it( 'should have a no form', () => 75 | { 76 | props = { ...props }; 77 | 78 | wrapper = mount( 79 | 80 | ); 81 | expect( wrapper.find( 'Editor' ).length ).toBe( 0 ); 82 | expect( wrapper.find( 'IdForm' ).length ).toBe( 0 ); 83 | } ); 84 | } ); 85 | 86 | 87 | describe( '/create', () => 88 | { 89 | 90 | it( 'should have a form', () => 91 | { 92 | props = { ...props }; 93 | 94 | wrapper = mount( 95 | 96 | ); 97 | expect( wrapper.find( 'Editor' ).length ).toBe( 0 ); 98 | expect( wrapper.find( 'IdForm' ).length ).toBe( 1 ); 99 | } ); 100 | } ); 101 | 102 | 103 | describe( '/edit/:id', () => 104 | { 105 | 106 | it( 'should have idForm if id given', () => 107 | { 108 | props = { ...props }; 109 | props.store= configureStore( { webIds : [ { uri: 'name' }]} ) 110 | 111 | wrapper = mount( 112 | 113 | ); 114 | expect( wrapper.find( 'Editor' ).length ).toBe( 1 ); 115 | expect( wrapper.find( 'IdForm' ).length ).toBe( 1 ); 116 | } ); 117 | } ); 118 | } ); 119 | -------------------------------------------------------------------------------- /__tests__/reducers/webIds__reducer.spec.js: -------------------------------------------------------------------------------- 1 | import webIdsReducer, { initialState } from 'reducers/webIds_reducer'; 2 | import { TYPES } from 'actions/webIds_actions'; 3 | 4 | 5 | describe( 'webIds reducer', () => 6 | { 7 | it( 'should return the initial state', () => 8 | { 9 | expect( webIdsReducer( undefined, {} ) ).toEqual( initialState ); 10 | } ); 11 | 12 | describe( 'ADD_WEB_ID', () => 13 | { 14 | it( 'should handle adding a webId', () => 15 | { 16 | const payload = { name: 'testttyyy', nick:'llalala' }; 17 | expect( webIdsReducer( {}, { 18 | type : TYPES.ADD_WEB_ID, 19 | payload 20 | } )[0] ).toMatchObject( payload ); 21 | } ); 22 | 23 | it( 'should handle adding multiple webIds', () => 24 | { 25 | const payload = { name: 'testttyyy' , nick:'llalala' }; 26 | const payload2 = { name: 'two', nick:'llassslala' }; 27 | 28 | const state = webIdsReducer( [], { 29 | type : TYPES.ADD_WEB_ID, 30 | payload 31 | } ); 32 | 33 | const state2 = webIdsReducer( state, { 34 | type : TYPES.ADD_WEB_ID, 35 | payload : payload2 36 | } ); 37 | 38 | expect( state2 ).toEqual( [payload, payload2] ); 39 | } ); 40 | } ); 41 | 42 | 43 | describe( 'UPDATE_WEB_ID', () => 44 | { 45 | it( 'should handle updating a webId', () => 46 | { 47 | const updatedPayload = { name: 'nottestttyyy', uri: 1, email: 'here' }; 48 | const updatedState = webIdsReducer( [{ name: 'something', uri: 2 }, { name: 'testttyyy', 'uri': 1 }], { 49 | type : TYPES.UPDATE_WEB_ID, 50 | payload : updatedPayload 51 | } ); 52 | 53 | expect( updatedState[1].name ).toEqual( updatedPayload.name ); 54 | expect( updatedState[1].email ).toEqual( updatedPayload.email ); 55 | } ); 56 | } ); 57 | 58 | describe( 'GET_AVAILABLE_WEB_IDS', () => 59 | { 60 | it( 'should handle replacing current webIds', () => 61 | { 62 | const updatedPayload = [{ name: 'nottestttyyy', uri: 1, email: 'here' }]; 63 | 64 | const updatedState = webIdsReducer( [{ name: 'something', uri: 2 }, { xorName: 'lalala', uri: 1 , typeTag: 26221 }], { 65 | type : TYPES.GET_AVAILABLE_WEB_IDS, 66 | payload : updatedPayload 67 | } ); 68 | 69 | expect( updatedState ).toEqual( updatedPayload ); 70 | expect( updatedState.length ).toEqual( 1 ); 71 | } ); 72 | } ); 73 | } ); 74 | -------------------------------------------------------------------------------- /__tests__/setupTests.js: -------------------------------------------------------------------------------- 1 | import Enzyme from 'enzyme'; 2 | import Adapter from 'enzyme-adapter-react-16'; 3 | 4 | Enzyme.configure({ adapter: new Adapter() }); 5 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | global: 3 | RUST_BACKTRACE: 1 4 | matrix: 5 | - RUST_TOOLCHAIN: 1.26.2 6 | 7 | cache: 8 | - '%USERPROFILE%\.cargo' 9 | - '%APPVEYOR_BUILD_FOLDER%\target' 10 | 11 | clone_depth: 1 12 | 13 | install: 14 | - ps: | 15 | $url = "https://github.com/maidsafe/QA/raw/master/appveyor/install_rustup.ps1" 16 | Invoke-WebRequest $url -OutFile "install_rustup.ps1" 17 | . ".\install_rustup.ps1" 18 | 19 | platform: 20 | - x86 21 | - x64 22 | 23 | configuration: 24 | - Release 25 | 26 | build_script: 27 | - cargo check --verbose --release --lib --tests --examples --no-default-features 28 | 29 | test_script: 30 | - cargo test --verbose --release --no-default-features 31 | 32 | -------------------------------------------------------------------------------- /codeowners: -------------------------------------------------------------------------------- 1 | * @joshuef 2 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | browser : true, 3 | verbose : true, 4 | moduleFileExtensions : ['js', 'jsx', 'json'], 5 | setupFiles : ['raf/polyfill','/__tests__/setupTests.js'], 6 | testPathIgnorePatterns : ['node_modules', '__tests__/setupTests.js'], 7 | moduleDirectories : ['src', '__tests__', 'node_modules'], 8 | transformIgnorePatterns : ['/node_modules\/(?!antd\/dist)/'], 9 | moduleNameMapper : { 10 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$' : 11 | '/mocks/fileMock.js', 12 | '\\.(css|scss)$' : '/__mocks__/fileMock.js', 13 | 14 | '^@actions(.*)$' : '/src/actions$1', 15 | '^@components(.*)$' : '/src/components$1', 16 | '^@containers(.*)$' : '/src/containers$1', 17 | '^@reducers(.*)$' : '/src/reducers$1', 18 | '^@utils(.*)$' : '/app/utils$1' 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webIdManager", 3 | "version": "1.0.0", 4 | "productName": "webIdManager", 5 | "identifier": "net.maidsafe.web-id-manager", 6 | "vendor": "MaidSafe.net Ltd", 7 | "author": { 8 | "name": "Maidsafe.net Ltd", 9 | "email": "qa@maidsafe.net", 10 | "url": "https://github.com/maidsafe" 11 | }, 12 | "description": "Manage RDF data on the SAFE Network", 13 | "main": "src/index.js", 14 | "license": "MIT", 15 | "scripts": { 16 | "test": "cross-env NODE_ENV=test jest --notify", 17 | "build": "rimraf dist && NODE_ENV=production yarn parcel build public/index.html", 18 | "start": "cross-env NODE_ENV=development parcel public/index.html", 19 | "lint": "eslint --cache --format=node_modules/eslint-formatter-pretty .", 20 | "lint-fix": "yarn run lint -- --fix", 21 | "prepush": "yarn test" 22 | }, 23 | "dependencies": { 24 | "@babel/plugin-proposal-class-properties": "^7.0.0", 25 | "add": "2.0.6", 26 | "antd": "3.10.9", 27 | "cross-env": "5.2.0", 28 | "enzyme": "3.7.0", 29 | "enzyme-adapter-react-16": "1.7.0", 30 | "prop-types": "15.6.2", 31 | "react": "^0.14.0 || ^15.0.0-0 || ^16.0.0-0", 32 | "react-dom": "16.6.3", 33 | "react-redux": "5.1.1", 34 | "react-router-dom": "4.3.1", 35 | "redux": "^2.0.0 || ^3.0.0 || ^4.0.0-0", 36 | "redux-actions": "2.6.4", 37 | "redux-promise": "0.6.0", 38 | "redux-thunk": "2.3.0", 39 | "schema.org": "3.1.1" 40 | }, 41 | "devDependencies": { 42 | "@babel/core": "^7.1.6", 43 | "@babel/preset-env": "^7.0.0", 44 | "@babel/preset-react": "^7.0.0", 45 | "babel-eslint": "10.0.1", 46 | "babel-plugin-import": "1.11.0", 47 | "eslint": "5.9.0", 48 | "eslint-config-airbnb": "17.1.0", 49 | "eslint-formatter-pretty": "2.0.0", 50 | "eslint-plugin-compat": "2.6.3", 51 | "eslint-plugin-flowtype": "3.2.0", 52 | "eslint-plugin-flowtype-errors": "3.6.0", 53 | "eslint-plugin-import": "2.14.0", 54 | "eslint-plugin-jest": "22.1.0", 55 | "eslint-plugin-jsx-a11y": "6.1.2", 56 | "eslint-plugin-promise": "4.0.1", 57 | "eslint-plugin-react": "7.11.1", 58 | "flow": "0.2.3", 59 | "flow-bin": "0.87.0", 60 | "jest": "23.6.0", 61 | "jest-tobetype": "1.2.0", 62 | "less": "3.9", 63 | "less-vars-to-js": "1.3.0", 64 | "parcel-bundler": "1.10.3", 65 | "postcss-modules": "1.4.1", 66 | "raf": "3.4.1", 67 | "rimraf": "2.6.2" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SAFE WebID Manager 5 | 6 | 7 |
8 |
9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/actions/safe_actions.js: -------------------------------------------------------------------------------- 1 | import { createActions } from 'redux-actions'; 2 | import { APP_INFO } from '../constants'; 3 | import {message} from 'antd'; 4 | 5 | export const TYPES = { 6 | SAFE_AUTHORISE : 'SAFE_AUTHORISE' 7 | }; 8 | 9 | 10 | export const { 11 | safeAuthorise 12 | // , removeBookmark 13 | 14 | } = createActions( { 15 | [TYPES.SAFE_AUTHORISE] : async () => 16 | { 17 | if ( window.name ) return; // jest short circuit 18 | 19 | if ( !safe ) 20 | { 21 | throw new Error( 'SAFE APIs are missing' ); 22 | } 23 | 24 | try 25 | { 26 | const app = await safe.initialiseApp( APP_INFO.info ); 27 | const authUri = await app.auth.genAuthUri( APP_INFO.permissions, APP_INFO.opts ); 28 | const response = await safe.authorise( authUri ); 29 | const connectedApp = await app.auth.loginFromUri( response ); 30 | 31 | return { idApp: connectedApp }; 32 | } 33 | catch ( e ) 34 | { 35 | message.error('Error authenticating on the network.') 36 | console.log( 'Error in auth attempt', e ); 37 | } 38 | 39 | message.success('Authenticated successfully.') 40 | } 41 | } ); 42 | -------------------------------------------------------------------------------- /src/actions/webIds_actions.js: -------------------------------------------------------------------------------- 1 | import { createActions } from 'redux-actions'; 2 | import { message } from 'antd'; 3 | import { SAFE_CONSTANTS } from '../constants'; 4 | 5 | export const TYPES = { 6 | ADD_WEB_ID : 'ADD_WEB_ID', 7 | UPDATE_WEB_ID : 'UPDATE_WEB_ID', 8 | GET_AVAILABLE_WEB_IDS : 'GET_AVAILABLE_WEB_IDS' 9 | }; 10 | 11 | const TYPE_TAG = 16048; 12 | 13 | const sanitizeWebId = ( webId ) => 14 | { 15 | const newWebId = {}; 16 | 17 | // sanitize for webid rdf for now. 18 | Object.keys( webId ).forEach( key => 19 | { 20 | if ( webId[key] && typeof webId[key] !== 'undefined' ) 21 | { 22 | newWebId[key] = webId[key]; 23 | if ( typeof newWebId[key] === 'string' ) 24 | { 25 | newWebId[key] = webId[key].trim(); 26 | } 27 | 28 | if ( key === 'uri' || key === 'website' ) 29 | { 30 | newWebId[key] = `safe://${newWebId[key]}`; 31 | } 32 | } 33 | } ); 34 | console.log( 'post sanitizing', newWebId ); 35 | 36 | return newWebId; 37 | }; 38 | 39 | 40 | export const { 41 | addWebId, 42 | updateWebId, 43 | getAvailableWebIds 44 | } = createActions( { 45 | 46 | [TYPES.ADD_WEB_ID] : async ( payload ) => 47 | { 48 | const { idApp, history, webId } = payload; 49 | 50 | if ( !idApp ) 51 | { 52 | message.error( 'Not authorised.' ); 53 | console.log( 'Not authorise' ); 54 | throw new Error( 'No idApp provided to action' ); 55 | } 56 | 57 | const newWebId = sanitizeWebId( webId ); 58 | 59 | if ( window.name ) return newWebId; // jest short circuit 60 | 61 | try 62 | { 63 | if (newWebId.image && newWebId.imageMimeType) { 64 | // let's store the image first 65 | const imdWriter = await idApp.immutableData.create(); 66 | await imdWriter.write(newWebId.image); 67 | const cipherOpt = await idApp.cipherOpt.newPlainText(); 68 | const { xorUrl } = await imdWriter.close(cipherOpt, true, newWebId.imageMimeType); 69 | newWebId.image = xorUrl; 70 | } 71 | 72 | const md = await idApp.mutableData.newRandomPublic( TYPE_TAG ); 73 | await md.quickSetup( {} ); 74 | const webIdRDF = await md.emulateAs( 'WebID' ); 75 | await webIdRDF.create( newWebId, newWebId.nick ); 76 | } 77 | catch ( e ) 78 | { 79 | if ( e && e.message === 'No ID has been found in the RDF graph.' ) 80 | { 81 | message.error( 'This publicName already exists (created by another app). You can\'t make a webId here, sorry! ' ); 82 | return {}; 83 | } 84 | 85 | console.error( 'Error in addWebId', e ); 86 | message.error( 'Error creating webId on the network' ); 87 | return {}; 88 | } 89 | message.success( 'WebId created succesfully' ); 90 | 91 | console.log( 'WebId created on the network.' ); 92 | history.push( '/' ); // back to main page 93 | 94 | const webIdForApp = { 95 | ...newWebId, 96 | uri : newWebId.uri.replace( 'safe://', '' ), 97 | website : newWebId.website ? newWebId.website.replace( 'safe://', '' ) : '' 98 | }; 99 | 100 | return webIdForApp; 101 | }, 102 | [TYPES.UPDATE_WEB_ID] : async ( payload ) => 103 | { 104 | const { idApp, webId, history } = payload; 105 | 106 | if ( !idApp ) throw new Error( 'No idApp provided to update action' ); 107 | 108 | const newWebId = sanitizeWebId( webId ); 109 | 110 | if ( window.name ) return newWebId; // jest short circuit 111 | 112 | try 113 | { 114 | if (newWebId.image && newWebId.imageMimeType) { 115 | // let's store the image first 116 | const imdWriter = await idApp.immutableData.create(); 117 | await imdWriter.write(newWebId.image); 118 | const cipherOpt = await idApp.cipherOpt.newPlainText(); 119 | const { xorUrl } = await imdWriter.close(cipherOpt, true, newWebId.imageMimeType); 120 | newWebId.image = xorUrl; 121 | } 122 | 123 | const mdUri = newWebId.uri; 124 | 125 | const { content, resourceType } = await idApp.fetch( mdUri ); 126 | 127 | let pulledWebId; 128 | if ( resourceType === 'RDF' ) 129 | { 130 | pulledWebId = await content.emulateAs( 'WebID' ); 131 | await pulledWebId.fetchContent(); 132 | await pulledWebId.update( newWebId ); 133 | } 134 | } 135 | catch ( e ) 136 | { 137 | console.error( 'Error in updateWebId', e ); 138 | message.error( 'Error updating webID on the network' ); 139 | return e; 140 | } 141 | 142 | console.log( 'WebId updated on the network.', history ); 143 | message.success( 'WebId updated successfully' ); 144 | 145 | // why is this undefined? poush to newnickname.... 146 | history.push( '/' ); // back to main page 147 | return newWebId; 148 | }, 149 | [TYPES.GET_AVAILABLE_WEB_IDS] : async ( payload ) => 150 | { 151 | console.log( 'Getting available ids' ); 152 | const { idApp } = payload; 153 | 154 | if ( window.name ) return []; // jest short circuit 155 | 156 | if ( ! safeExperimentsEnabled ) 157 | { 158 | message.error('The experimental APIs are disabled, please enable them from the SAFE Browser'); 159 | } 160 | 161 | const webIds = await idApp.web.getWebIds(); 162 | 163 | const actualIds = await Promise.all(webIds.map( async webId => 164 | { 165 | const me = webId['#me']; 166 | 167 | // remove what is appended later 168 | me.uri = webId['@id'].replace( 'safe://', '' ); 169 | if ( me.website ) 170 | { 171 | const website = me.website['@id'] ? me.website['@id'] : me.website; 172 | me.website = website.replace('safe://', ''); 173 | } 174 | 175 | if ( me.image && me.image['@id'] ) 176 | { 177 | me.image = me.image['@id']; 178 | } 179 | 180 | if ( me.inbox && me.inbox['@id'] ) 181 | { 182 | me.inbox = me.inbox['@id']; 183 | } 184 | 185 | return me; 186 | } )); 187 | return actualIds; 188 | } 189 | } ); 190 | -------------------------------------------------------------------------------- /src/components/Avatar/Avatar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Upload, message } from 'antd'; 3 | 4 | function getBase64( img, callback ) 5 | { 6 | const reader = new FileReader(); 7 | reader.addEventListener( 'load', () => callback( reader.result ) ); 8 | reader.readAsDataURL( img ); 9 | } 10 | 11 | function getBinary( img, callback ) 12 | { 13 | const reader = new FileReader(); 14 | reader.addEventListener( 'load', () => callback( reader.result ) ); 15 | reader.readAsArrayBuffer( img ); 16 | } 17 | 18 | function beforeUpload( file ) 19 | { 20 | const isJPG = file.type === 'image/jpeg'; 21 | if ( !isJPG ) 22 | { 23 | message.error( 'You can only upload JPG file!' ); 24 | } 25 | const isLt2M = file.size / 1024 / 1024 < 2; 26 | if ( !isLt2M ) 27 | { 28 | message.error( 'Image must smaller than 2MB!' ); 29 | } 30 | 31 | // we dont want to attempt to trigger an upload. 32 | // there is no server! so should return false if not for updating FORM issue (doesnt happen if no upload, 33 | // so looking at a potential dummy server in browser) 34 | return isJPG && isLt2M; 35 | } 36 | 37 | class Avatar extends React.Component 38 | { 39 | state = { 40 | loading : false, 41 | }; 42 | 43 | handleChange = ( info, fileList, event ) => 44 | { 45 | console.log( 'handling file change, ', info ); 46 | // if ( info.file.status === 'uploading' ) 47 | // { 48 | // this.setState( { loading: true } ); 49 | // return; 50 | // } 51 | // if ( info.file.status === 'done' ) 52 | // { 53 | // Get this url from response in real world. 54 | 55 | // var event = new Event('input', { bubbles: true }); 56 | const uploader = this.uploader; 57 | 58 | getBase64( info.file.originFileObj, imageUrl => 59 | { 60 | this.setState( { imageUrl } ); 61 | } ); 62 | 63 | getBinary( info.file.originFileObj, imageBinary => 64 | { 65 | this.setState( { imageBinary, imageMimeType: info.file.type } ); 66 | } ); 67 | } 68 | render() 69 | { 70 | const { value } = this.props; 71 | const uploadButton = ( 72 |
73 | { this.state.loading ? 'loading' : '' } 74 |
Upload
75 |
76 | ); 77 | 78 | const imageUrl = this.state.imageUrl || value; 79 | return ( 80 | 82 | { 83 | this.uploader = c; 84 | } } 85 | name="avatar" 86 | listType="picture-card" 87 | className="avatar-uploader" 88 | showUploadList={ false } 89 | ref={(c)=> {this.uploader = c}} 90 | beforeUpload={ beforeUpload } 91 | onChange={ this.handleChange } 92 | > 93 | {imageUrl ? avatar : uploadButton} 94 | 95 | ); 96 | } 97 | } 98 | 99 | export default Avatar; 100 | -------------------------------------------------------------------------------- /src/components/Avatar/index.js: -------------------------------------------------------------------------------- 1 | export default from './Avatar.jsx'; 2 | -------------------------------------------------------------------------------- /src/components/Editor/Editor.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Switch, Route } from 'react-router-dom'; 4 | 5 | import IdForm from '../IdForm/IdForm'; 6 | 7 | 8 | export default class Editor extends React.Component 9 | { 10 | static propTypes = 11 | { 12 | webIds : PropTypes.arrayOf( PropTypes.object ), 13 | idApp : PropTypes.shape(), 14 | match : PropTypes.shape( { url: PropTypes.string } ).isRequired, 15 | updateWebId : PropTypes.func.isRequired 16 | } 17 | 18 | static defaultProps = 19 | { 20 | webIds : [] 21 | } 22 | 23 | render() 24 | { 25 | const { match, idApp, updateWebId, webIds, history } = this.props; 26 | const params = match.params || {}; //or for testing. 27 | const idToMatch = params.id || ''; 28 | 29 | const webId = webIds.find( id => id.uri === idToMatch ); 30 | if( !webId ) return
No matching WebId found
; 31 | 32 | return ( 33 |
34 |

{ `Editing`}

35 | 42 |
43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/components/Editor/index.js: -------------------------------------------------------------------------------- 1 | export default from './Editor.jsx'; 2 | -------------------------------------------------------------------------------- /src/components/Header/Header.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { NavLink } from 'react-router-dom'; 3 | import { Menu } from 'antd'; 4 | import {PATHS} from '../../constants'; 5 | 6 | const Header = ( props ) => 7 | { 8 | const selectedKeys = []; 9 | const location = props.location.pathname; 10 | 11 | // set menu as active on load if on a specific path 12 | Object.keys( PATHS ).forEach( path => 13 | { 14 | location.startsWith( PATHS[path] ) ? selectedKeys.push( PATHS[path] ) : ''; 15 | } ); 16 | 17 | return ( 18 |
19 |

WebID Profile Manager

20 | 21 | 22 | List 23 | 24 | 25 | Create 26 | 27 | 28 |
29 | 30 | ); 31 | }; 32 | 33 | export default Header; 34 | -------------------------------------------------------------------------------- /src/components/Header/index.js: -------------------------------------------------------------------------------- 1 | export default from './Header.jsx'; 2 | -------------------------------------------------------------------------------- /src/components/IdForm/IdForm.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Redirect } from 'react-router-dom'; 4 | import { Form, Input, Button, Icon } from 'antd'; 5 | import Avatar from '../Avatar/Avatar'; 6 | 7 | const FormItem = Form.Item; 8 | 9 | const uriRegex = '^([-a-z0-9]{3,63}\\.)*?[a-z0-9][-a-z0-9]{0,61}[a-z0-9]\\.[a-z0-9][-a-z0-9]{0,61}[a-z0-9]{2,6}(/[-\\w@\\+\\.~#\\?&/=%]*)?$'; 10 | 11 | 12 | const defaultId = { 13 | name : '', 14 | nick : '', 15 | uri : '', 16 | website : '', 17 | image : '' 18 | }; 19 | 20 | /** 21 | * Helper function for ant design forms to populate 22 | * the form with passed values. 23 | * @param {Object} id webId object 24 | */ 25 | const mapPropsToFields = ( { webId } ) => 26 | { 27 | // if( !id ) return; 28 | let idToUse = webId || defaultId; 29 | 30 | idToUse = idToUse['#me'] || idToUse; 31 | 32 | return { 33 | nick : Form.createFormField( { 34 | ...idToUse, 35 | value : idToUse.nick || '', 36 | } ), 37 | name : Form.createFormField( { 38 | ...idToUse, 39 | value : idToUse.name || '', 40 | } ), 41 | uri : Form.createFormField( { 42 | ...idToUse, 43 | value : idToUse.uri || '', 44 | } ), 45 | website : Form.createFormField( { 46 | ...idToUse, 47 | value : idToUse.website || '', 48 | } ), 49 | image : Form.createFormField( { 50 | ...idToUse, 51 | value : idToUse.image || '', 52 | } ), 53 | // pk : Form.createFormField( { 54 | // ...id, 55 | // value : id.pk || '', 56 | // } ), 57 | }; 58 | }; 59 | 60 | /** 61 | * Form for WebId creation/editing. Uses and design Form components (and form.create() method) 62 | * to create a form that can be easily validated/populated. 63 | * @extends React 64 | */ 65 | class IdForm extends React.Component 66 | { 67 | static propTypes = { 68 | webId : PropTypes.shape( { 69 | nick : PropTypes.string, 70 | name : PropTypes.string, 71 | website : PropTypes.string, 72 | uri : PropTypes.string, 73 | pk : PropTypes.string 74 | } ), 75 | webIds : PropTypes.arrayOf( PropTypes.shape ).isRequired, 76 | submit : PropTypes.func.isRequired, 77 | getAvailableWebIds : PropTypes.func.isRequired, 78 | // idApp : PropTypes.shape.isRequired 79 | } 80 | static defaultProps = { 81 | webId : defaultId 82 | } 83 | 84 | isEditing = () => { 85 | return !!this.props.webId.uri; 86 | } 87 | 88 | componentWillReceiveProps = ( newProps ) => 89 | { 90 | const { idApp } = this.props; 91 | 92 | // didnt have app, but now we doooo.... 93 | if ( !idApp && newProps.idApp && newProps.webIds.length === 0 ) 94 | { 95 | this.getIds( newProps.idApp ); 96 | } 97 | } 98 | 99 | // TODO: Dry this out across components 100 | getIds = ( passedIdApp ) => 101 | { 102 | const { getAvailableWebIds, idApp } = this.props; 103 | 104 | const appToUse = passedIdApp || idApp; 105 | if ( appToUse ) 106 | { 107 | getAvailableWebIds( { idApp: appToUse } ); 108 | } 109 | } 110 | 111 | handleSubmit = ( e ) => 112 | { 113 | e.preventDefault(); 114 | 115 | const { 116 | match, submit, idApp, history, webId 117 | } = this.props; 118 | 119 | const imageBinary = this.theAvatar.state.imageBinary; 120 | const imageMimeType = this.theAvatar.state.imageMimeType; 121 | 122 | this.props.form.validateFields( ( err, values ) => 123 | { 124 | if ( !err ) 125 | { 126 | let image = imageBinary; 127 | if (!image && webId) { 128 | image = webId.image; 129 | } 130 | 131 | const webIdWithImageAndUpdates = { ...webId, ...values, image, imageMimeType }; 132 | console.log( 'ON CLICK:: Updating with', webIdWithImageAndUpdates, history ); 133 | 134 | // history to move us on a page when successful 135 | submit( { idApp, webId: webIdWithImageAndUpdates, history } ); 136 | } 137 | } ); 138 | } 139 | 140 | validateUniqueId = async ( rule, value, callback ) => 141 | { 142 | const { webIds, webId } = this.props; 143 | 144 | // if we're editing do nothing... 145 | if ( webId.uri ) return callback(); 146 | 147 | const foundId = webIds.find( aWebId => 148 | aWebId['@id'] === `safe://${value}#me` ); 149 | 150 | if ( foundId ) return callback( 'Error.' ); 151 | 152 | return callback(); 153 | } 154 | 155 | render = ( ) => 156 | { 157 | const { getFieldDecorator } = this.props.form; 158 | 159 | return ( 160 |
161 | 162 | {getFieldDecorator( 'nick', { 163 | rules : [{ required: true, whitespace: true, message: 'Please input a nickname, this will be used to entify this WebId!' }], 164 | } )( } 166 | placeholder="nickname" 167 | /> )} 168 | 169 | 170 | {getFieldDecorator( 'name', { 171 | rules : [{ required: true, whitespace: true, message: 'Please input a webId name.' }], 172 | } )( } 174 | placeholder="full name" 175 | /> )} 176 | 177 | 178 | {getFieldDecorator( 'uri', { 179 | rules : [ 180 | { required: true, message: 'URI location for this webId should be provided.' }, 181 | { validator: this.validateUniqueId, message: 'This must not be an existing WebId.' }, 182 | { pattern: uriRegex, message: 'This must be a valid safe:// url, in for format: .' }], 183 | } )( } 187 | placeholder="public name" 188 | /> )} 189 | { 190 | !this.isEditing() && 191 |
Note: It's not currently possible to create a webId using a domain created in the WebHosting Manager.
192 | } 193 |
194 | 195 | {getFieldDecorator( 'website', { 196 | rules : [{ pattern: uriRegex, message: 'This must be a valid safe:// url' }], 197 | } )( } 200 | placeholder="website location" 201 | /> )} 202 | 203 | 204 | {getFieldDecorator( 'image', { 205 | rules : [], 206 | } )( 207 | { 208 | this.theAvatar = c; 209 | } } 210 | /> )} 211 | 212 | {/* 213 | {getFieldDecorator( 'publickey', { 214 | rules : [], 215 | } )( } 218 | placeholder="Public Key" 219 | /> )} 220 | */} 221 | {/* 222 | 223 | */} 224 | 229 |
230 | ); 231 | } 232 | } 233 | 234 | const WrappedIdForm = Form.create( { mapPropsToFields } )( IdForm ); 235 | 236 | export default WrappedIdForm; 237 | -------------------------------------------------------------------------------- /src/components/IdForm/index.js: -------------------------------------------------------------------------------- 1 | export default from './IdForm.jsx'; 2 | -------------------------------------------------------------------------------- /src/components/List/List.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { Menu, List, Button, Avatar } from 'antd'; 4 | import { PATHS } from '../../constants'; 5 | 6 | class IdList extends React.Component 7 | { 8 | static defaultProps = { 9 | webIds : [] 10 | } 11 | 12 | componentDidMount = () => 13 | { 14 | this.getIds(); 15 | } 16 | 17 | componentWillReceiveProps = ( newProps ) => 18 | { 19 | const { idApp } = this.props; 20 | 21 | // didnt have app, but now we doooo.... 22 | if ( !idApp && newProps.idApp ) 23 | { 24 | this.getIds( newProps.idApp ); 25 | } 26 | } 27 | 28 | 29 | getIds = ( passedIdApp ) => 30 | { 31 | const { getAvailableWebIds, idApp } = this.props; 32 | 33 | const appToUse = passedIdApp || idApp; 34 | 35 | if ( appToUse ) 36 | { 37 | getAvailableWebIds( { idApp: appToUse } ); 38 | } 39 | } 40 | 41 | 42 | handleGetIds = ( newProps ) => 43 | { 44 | this.getIds(); 45 | } 46 | 47 | render = () => 48 | { 49 | const { idApp, webIds, history } = this.props; 50 | 51 | // TODO: Make real loading state. 52 | const isLoading = !idApp; 53 | 54 | const createNewWebId = () => 55 | { 56 | history.push( PATHS.CREATE ) 57 | } 58 | 59 | const IdList = webIds.map( ( webId, i ) => 60 | { 61 | const nickname = webId.nick.length ? webId.nick : ''; 62 | const { uri, image } = webId; 63 | const safeUri = `safe://${uri}`; 64 | return ( 65 | edit] } 68 | > 69 | 72 | : 73 | 74 | {nickname ? nickname.substring(0, 1).toUpperCase() : ''} 75 | 76 | } 77 | title={ nickname } 78 | description={ 79 | {safeUri} 80 | } 81 | /> 82 | 83 | ); 84 | } ); 85 | 86 | return ( 87 |
88 |

Your Current WebIds:

89 | 96 | 99 | {IdList} 100 | 101 |
102 | ); 103 | } 104 | } 105 | 106 | export default IdList; 107 | -------------------------------------------------------------------------------- /src/components/List/index.js: -------------------------------------------------------------------------------- 1 | export default from './List.jsx'; 2 | -------------------------------------------------------------------------------- /src/constants/appInfo.js: -------------------------------------------------------------------------------- 1 | import pkg from '../../package.json'; 2 | 3 | export const APP_INFO = { 4 | info : { 5 | id : pkg.identifier, 6 | scope : null, 7 | name : pkg.productName, 8 | vendor : pkg.author.name 9 | }, 10 | opts : { 11 | own_container : true, 12 | }, 13 | permissions : { 14 | _public : ['Read', 'Insert', 'Update', 'Delete'], 15 | _publicNames : ['Read', 'Insert', 'Update', 'Delete'] 16 | }, 17 | }; 18 | 19 | // export default APP_INFO; 20 | -------------------------------------------------------------------------------- /src/constants/index.js: -------------------------------------------------------------------------------- 1 | export { PATHS } from './paths'; 2 | export { APP_INFO } from './appInfo'; 3 | export { SAFE_CONSTANTS } from './safeConstants'; 4 | -------------------------------------------------------------------------------- /src/constants/paths.js: -------------------------------------------------------------------------------- 1 | export const PATHS = { 2 | CREATE : '/create/new', 3 | EDIT : '/edit', 4 | LIST : '/list', 5 | }; 6 | -------------------------------------------------------------------------------- /src/constants/safeConstants.js: -------------------------------------------------------------------------------- 1 | 2 | export const SAFE_CONSTANTS = { 3 | ACCESS_CONTAINERS : { 4 | // PUBLIC: '_public', 5 | PUBLIC_NAMES : '_publicNames', 6 | }, 7 | }; 8 | 9 | // export default APP_INFO; 10 | -------------------------------------------------------------------------------- /src/containers/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { withRouter, Route, Switch, Redirect } from 'react-router-dom'; 3 | import { bindActionCreators } from 'redux'; 4 | import { connect } from 'react-redux'; 5 | import * as webIdsActions from '../actions/webIds_actions'; 6 | import * as safeActions from '../actions/safe_actions'; 7 | 8 | import styles from './global.css'; 9 | import Header from '../components/Header/Header'; 10 | import Editor from '../components/Editor/Editor'; 11 | import List from '../components/List/List'; 12 | 13 | import IdForm from '../components/IdForm/IdForm'; 14 | 15 | import { Layout, Row, Col } from 'antd'; 16 | 17 | const { Content } = Layout; 18 | 19 | class App extends React.Component 20 | { 21 | componentDidMount = () => 22 | { 23 | const { safeAuthorise, safe } = this.props; 24 | const { idApp } = safe; 25 | 26 | if ( !idApp ) 27 | { 28 | safeAuthorise(); 29 | } 30 | } 31 | 32 | 33 | render = () => 34 | { 35 | const { 36 | webIds 37 | , history 38 | , addWebId 39 | , getAvailableWebIds 40 | , updateWebId 41 | , safe 42 | } = this.props; 43 | 44 | return ( 45 |
51 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 67 | } 68 | /> 69 | ( ) 78 | } 79 | /> 80 | ( ) } 89 | /> 90 | } /> 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 |
99 | ); 100 | } 101 | } 102 | 103 | 104 | function mapDispatchToProps( dispatch ) 105 | { 106 | const actions = 107 | { 108 | ...safeActions, 109 | ...webIdsActions 110 | }; 111 | return bindActionCreators( actions, dispatch ); 112 | } 113 | 114 | function mapStateToProps( state ) 115 | { 116 | return { 117 | webIds : state.webIds, 118 | safe : state.safe 119 | }; 120 | } 121 | export default withRouter( connect( mapStateToProps, mapDispatchToProps )( App ) ); 122 | -------------------------------------------------------------------------------- /src/containers/global.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | .appContainer { 4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 5 | "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", 6 | SimSun, sans-serif; 7 | } 8 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { HashRouter } from 'react-router-dom'; 4 | import { createStore, applyMiddleware, compose } from 'redux'; 5 | import thunk from 'redux-thunk'; 6 | import promiseMiddleware from 'redux-promise'; 7 | import "@babel/polyfill"; 8 | 9 | import App from './containers/App'; 10 | import rootReducer from './reducers'; 11 | 12 | if ( window.webIdEventEmitter ) 13 | { 14 | console.log( 'webId emitter exists!' ); 15 | 16 | window.webIdEventEmitter.once( 'update', ( webId ) => 17 | { 18 | console.log( 'WebId has been updated (though not sure what to do with it...)', webId ); 19 | } ); 20 | } 21 | 22 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; 23 | const middleware = [thunk, promiseMiddleware]; 24 | const enhancer = composeEnhancers( applyMiddleware( ...middleware ) ); 25 | 26 | 27 | function configureStore( initialState ) 28 | { 29 | return createStore( 30 | rootReducer, 31 | initialState, 32 | enhancer 33 | ); 34 | } 35 | 36 | const store = configureStore( {} ); 37 | 38 | const reactRoot = document.getElementById( 'react-root' ); 39 | 40 | render( 41 | 42 | 43 | 44 | , 45 | reactRoot 46 | ); 47 | -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import webIds from './webIds_reducer'; 3 | import safe from './safe_reducer'; 4 | 5 | // Add your new reducer here 6 | const reducers = { 7 | safe, 8 | webIds 9 | }; 10 | 11 | const rootReducer = combineReducers( reducers ); 12 | 13 | export default rootReducer; 14 | -------------------------------------------------------------------------------- /src/reducers/safe_reducer.js: -------------------------------------------------------------------------------- 1 | import { TYPES } from '../actions/safe_actions'; 2 | 3 | export const initialState = { 4 | } 5 | 6 | export default ( state = initialState, action ) => 7 | { 8 | const { payload } = action; 9 | 10 | switch ( action.type ) 11 | { 12 | case TYPES.SAFE_AUTHORISE: { 13 | return { ...state, ...payload }; 14 | } 15 | 16 | // from browser not needed 17 | // case TYPES.SET_SELECTED_WEB_ID: { 18 | // const selectedId = payload; 19 | // } 20 | 21 | default: { 22 | return state; 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /src/reducers/webIds_reducer.js: -------------------------------------------------------------------------------- 1 | import { TYPES } from '../actions/webIds_actions'; 2 | 3 | export const initialState = [ 4 | ]; 5 | 6 | export default ( state = initialState, action ) => 7 | { 8 | const { payload } = action; 9 | switch ( action.type ) 10 | { 11 | case TYPES.ADD_WEB_ID: 12 | { 13 | if( ! payload.nick ) 14 | return state; 15 | 16 | return [...state, payload]; 17 | } 18 | case TYPES.UPDATE_WEB_ID: 19 | { 20 | const oldIdIndex = state.findIndex( webId => webId.uri === payload.uri ); 21 | const oldId = state[oldIdIndex]; 22 | const updatedId = { ...oldId, ...payload }; 23 | 24 | const newState = [ ...state ]; 25 | 26 | newState[oldIdIndex] = updatedId; 27 | return newState; 28 | } 29 | case TYPES.GET_AVAILABLE_WEB_IDS: { 30 | return [ ...payload ]; 31 | } 32 | // from browser not needed 33 | // case TYPES.SET_SELECTED_WEB_ID: { 34 | // const selectedId = payload; 35 | // } 36 | 37 | default: 38 | return state; 39 | } 40 | }; 41 | --------------------------------------------------------------------------------