From 7569ac8bd58174095f3f897548e26d0ba905236c Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 21 Sep 2016 15:39:03 -0700 Subject: [feat] the setup wizard for the new ui --- ui/README.md | 29 +++- ui/app/app.js | 19 ++- ui/app/components/center.js | 6 +- ui/app/components/greeter_panel.js | 6 +- ui/app/components/layout/index.js | 41 ++++++ ui/app/components/layout/layout.less | 25 ++++ ui/app/components/list_edit.js | 122 ---------------- ui/app/components/list_editor/index.js | 105 ++++++++++++++ ui/app/components/list_editor/list_editor.less | 29 ++++ ui/app/components/login.js | 130 +++++++++++++++-- ui/app/components/main_panel/email_section.js | 2 - ui/app/components/main_panel/index.js | 2 - ui/app/components/main_panel/user_section.js | 10 +- ui/app/components/panel_switcher.js | 11 +- ui/app/components/wizard/add_provider_modal.js | 61 +++++--- ui/app/components/wizard/index.js | 19 +-- ui/app/components/wizard/provider_select_stage.js | 168 +++++++++++++++++----- ui/app/components/wizard/register_stage.js | 102 +++++++++++++ ui/app/css/common.css | 20 ++- ui/app/main.js | 2 +- ui/app/models/account.js | 21 ++- ui/app/models/provider.js | 59 ++++++++ 22 files changed, 760 insertions(+), 229 deletions(-) create mode 100644 ui/app/components/layout/index.js create mode 100644 ui/app/components/layout/layout.less delete mode 100644 ui/app/components/list_edit.js create mode 100644 ui/app/components/list_editor/index.js create mode 100644 ui/app/components/list_editor/list_editor.less create mode 100644 ui/app/components/wizard/register_stage.js create mode 100644 ui/app/models/provider.js diff --git a/ui/README.md b/ui/README.md index 3f276c0..fb409bf 100644 --- a/ui/README.md +++ b/ui/README.md @@ -84,7 +84,10 @@ way. We have enabled these plugins: * babel-presets-stage-0: Allows the use of some ES7 proposals, even though these are not standardized yet. Makes classes nicer. -* babel-polyfill: This is not part of the babel transpiling, but is distributed by babel. This polyfill will give you a full ES2015 environment even if the browser is missing some javascript features. We include this in the 'entry' option of the webpack config. https://babeljs.io/docs/usage/polyfill/ +* babel-polyfill: This is not part of the babel transpiling, but is distributed + by babel. This polyfill will give you a full ES2015 environment even if the + browser is missing some javascript features. We include this in the 'entry' + option of the webpack config. https://babeljs.io/docs/usage/polyfill/ **react** @@ -110,3 +113,27 @@ To integrate Bootstrap with React: A password strength checker that doesn't suck, but which is big. This JS is only loaded when we think we are about to need it. +Known Issues +----------------------------------------------------------------- + +Wizard + +* In the wizard, the username field gets deselected. +* User sign up does not work, getting an error from the backend: + No such subcommand: create +* This wizard is kind of ugly + The list of providers should have icons, be sortable, filterable. + The list of providers does not show seeded providers (backend is not returning them) + The provider details should show human readable output, not codes. + +Main window + +* UI doesn't subscribe to events yet, won't get updated if + user has logged out via the command line interface. +* The backend doesn't have a concept of multiple accounts that are + all authenticated yet. +* If you cancel the wizard, it does not select the appropriate account + in the main window. +* Removing accounts doesn't do anything +* Collapsing account list looks weird, and is state is not remembered + diff --git a/ui/app/app.js b/ui/app/app.js index 57120a4..45f87dd 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -8,10 +8,18 @@ class Application { // // main entry point for the application // + initialize() { + if (this.debugging()) { + this.show(this.debug_panel) + } else { + this.start() + } + } + start() { Account.active().then(account => { if (account == null) { - this.show('greeter', {onLogin: this.onLogin.bind(this)}) + this.show('greeter') } else { this.show('main', {initialAccount: account}) } @@ -20,13 +28,14 @@ class Application { }) } - onLogin(account) { - this.show('main', {initialAccount: account}) - } - show(panel, properties) { this.switcher.show(panel, properties) } + + debugging() { + this.debug_panel = window.location.hash.replace('#', '') + return this.debug_panel && this.debug_panel != 'main' + } } var App = new Application diff --git a/ui/app/components/center.js b/ui/app/components/center.js index 6fa6212..5e47d0c 100644 --- a/ui/app/components/center.js +++ b/ui/app/components/center.js @@ -7,7 +7,8 @@ import React from 'react' class Center extends React.Component { static get defaultProps() {return{ - width: null + width: null, + direction: 'both', }} constructor(props) { @@ -19,8 +20,9 @@ class Center extends React.Component { if (this.props.width) { style = {width: this.props.width + 'px'} } + let className = "center-container center-" + this.props.direction return ( -
+
{this.props.children}
diff --git a/ui/app/components/greeter_panel.js b/ui/app/components/greeter_panel.js index 4552db1..c95b183 100644 --- a/ui/app/components/greeter_panel.js +++ b/ui/app/components/greeter_panel.js @@ -16,12 +16,16 @@ export default class GreeterPanel extends React.Component { App.show('wizard') } + onLogin(account) { + App.show('main', {initialAccount: account}) + } + render () { return
- + diff --git a/ui/app/components/layout/index.js b/ui/app/components/layout/index.js new file mode 100644 index 0000000..8e7c4b5 --- /dev/null +++ b/ui/app/components/layout/index.js @@ -0,0 +1,41 @@ +import React from 'react' + +import './layout.less' + +class HorizontalLayout extends React.Component { + static get defaultProps() {return{ + equalWidths: false + }} + + constructor(props) { + super(props) + } + + render() { + let className = "horizontal-layout" + if (this.props.equalWidths) { + className = className + " equal" + this.props.children.length + } + return ( +
+ {this.props.children} +
+ ) + } +} + +class Column extends React.Component { + constructor(props) { + super(props) + } + + render() { + return ( +
+ {this.props.children} +
+ ) + } +} + +export {HorizontalLayout, Column} \ No newline at end of file diff --git a/ui/app/components/layout/layout.less b/ui/app/components/layout/layout.less new file mode 100644 index 0000000..dae99aa --- /dev/null +++ b/ui/app/components/layout/layout.less @@ -0,0 +1,25 @@ +@gutter: 20px; + +.horizontal-layout { + display: -webkit-flex; + display: flex; + -webkit-flex-direction: row; + flex-direction: row; + -webkit-flex: 1 1 auto; + flex: 1 1 auto; + > .layout-column { + display: -webkit-flex; + display: flex; + -webkit-flex: 1 1 auto; + flex: 1 1 auto; + margin-right: @gutter; + } + > .layout-column:last-child { + margin-right: 0px; + } +} + +.horizontal-layout.equal1 > .layout-column {width: 100%;} +.horizontal-layout.equal2 > .layout-column {width: 50%;} +.horizontal-layout.equal3 > .layout-column {width: 33.33%;} +.horizontal-layout.equal4 > .layout-column {width: 25%;} \ No newline at end of file diff --git a/ui/app/components/list_edit.js b/ui/app/components/list_edit.js deleted file mode 100644 index 0d557d2..0000000 --- a/ui/app/components/list_edit.js +++ /dev/null @@ -1,122 +0,0 @@ -// -// A simple list of items, with minus and plus buttons to add and remove -// items. -// - -import React from 'react' -import {Button, ButtonGroup, ButtonToolbar, Glyphicon, FormControl} from 'react-bootstrap' - -const CONTAINER_CSS = { - display: "flex", - flexDirection: "column" -} -const SELECT_CSS = { - padding: "0px", - flex: "1 1 1000px", - overflowY: "scroll" -} -const OPTION_CSS = { - padding: "10px" -} -const TOOLBAR_CSS = { - paddingTop: "10px", - flex: "0 0 auto" -} - -class ListEdit extends React.Component { - - static get defaultProps() {return{ - width: null, - items: [ - 'aaaaaaa', - 'bbbbbbb', - 'ccccccc' - ], - selected: null, - onRemove: null, - onAdd: null, - }} - - constructor(props) { - super(props) - let index = 0 - if (props.selected) { - index = props.items.indexOf(props.selected) - } - this.state = { - selected: index - } - this.click = this.click.bind(this) - this.add = this.add.bind(this) - this.remove = this.remove.bind(this) - } - - setSelected(index) { - this.setState({ - selected: index - }) - } - - click(e) { - let row = parseInt(e.target.value) - if (row >= 0) { - this.setState({selected: row}) - } - } - - add() { - if (this.props.onAdd) { - this.props.onAdd() - } - } - - remove() { - if (this.state.selected >= 0 && this.props.onRemove) { - if (this.props.items.length == this.state.selected + 1) { - // if we remove the last item, set the selected item - // to the one right before it. - this.setState({selected: (this.state.selected - 1)}) - } - this.props.onRemove(this.props.items[this.state.selected]) - } - } - - render() { - let options = null - if (this.props.items) { - options = this.props.items.map((item, i) => { - return - }, this) - } - return( -
- - {options} - - - - - - - -
- ) - } - -} - -ListEdit.propTypes = { - children: React.PropTypes.oneOfType([ - React.PropTypes.element, - React.PropTypes.arrayOf(React.PropTypes.element) - ]) -} - -export default ListEdit diff --git a/ui/app/components/list_editor/index.js b/ui/app/components/list_editor/index.js new file mode 100644 index 0000000..58b6ec4 --- /dev/null +++ b/ui/app/components/list_editor/index.js @@ -0,0 +1,105 @@ +// +// A simple list of items, with minus and plus buttons to add and remove +// items. +// + +import React from 'react' +import {Button, ButtonGroup, ButtonToolbar, Glyphicon, FormControl} from 'react-bootstrap' + +import './list_editor.less' + +class ListEdit extends React.Component { + + static get defaultProps() {return{ + width: null, + items: [ + 'aaaaaaa', + 'bbbbbbb', + 'ccccccc' + ], + selected: null, // string of the selected item + onRemove: null, + onAdd: null, + onSelect: null + }} + + constructor(props) { + super(props) + this.click = this.click.bind(this) + this.add = this.add.bind(this) + this.remove = this.remove.bind(this) + } + + row(str) { + return this.props.items.indexOf(str) + } + + click(e) { + let row = parseInt(e.target.value) + if (row >= 0) { + if (this.props.onSelect) { + this.props.onSelect(this.props.items[row]) + } + } + } + + add() { + if (this.props.onAdd) { + this.props.onAdd() + } + } + + remove() { + if (this.props.onRemove) { + let currentRow = this.row(this.props.selected) + let newSelected = null + if (this.props.items.length == currentRow + 1) { + // if we remove the last item, set the new selected to be + // the new last item. + newSelected = this.props.items[currentRow - 1] + } else { + newSelected = this.props.items[currentRow + 1] + } + this.props.onRemove(this.props.selected, newSelected) + } + } + + render() { + let options = null + if (this.props.items) { + options = this.props.items.map((item, i) => { + return + }, this) + } + return( +
+ + {options} + + + + + + + +
+ ) + } + +} + +ListEdit.propTypes = { + children: React.PropTypes.oneOfType([ + React.PropTypes.element, + React.PropTypes.arrayOf(React.PropTypes.element) + ]) +} + +export default ListEdit diff --git a/ui/app/components/list_editor/list_editor.less b/ui/app/components/list_editor/list_editor.less new file mode 100644 index 0000000..e2d2e55 --- /dev/null +++ b/ui/app/components/list_editor/list_editor.less @@ -0,0 +1,29 @@ +.list-editor { + + display: -webkit-flex; + display: flex; + + -webkit-flex-direction: column; + flex-direction: column; + + -webkit-flex: 1 1 auto; + flex: 1 1 auto; + + .list-select { + padding: 0px; + flex: 1 1 1000px; + -webkit-flex: 1 1 1000px; + overflow-y: scroll; + } + + .list-option { + padding: 10px; + } + + .list-toolbar { + padding-top: 10px; + + -webkit-flex: 0 0 auto; + flex: 0 0 auto; + } +} diff --git a/ui/app/components/login.js b/ui/app/components/login.js index fe4ef5b..562ab5a 100644 --- a/ui/app/components/login.js +++ b/ui/app/components/login.js @@ -14,7 +14,9 @@ class Login extends React.Component { static get defaultProps() {return{ rememberAllowed: false, // if set, show remember password checkbox domain: null, // if set, only allow this domain - onLogin: null + address: null, // if set, only allow this username@domain + onLogin: null, // callback + mode: "login" // one of "login" or "signup" }} constructor(props) { @@ -27,14 +29,18 @@ class Login extends React.Component { authError: false, // authentication error message - username: "etest1@riseup.net", + username: this.props.address, usernameState: null, // username validation state usernameError: false, // username help message - password: "whatever", + password: null, passwordState: null, // password validation state passwordError: false, // password help message + password2: null, // password confirmation + password2State: null, // password confirm validation state + password2Error: false, // password confirm help message + disabled: false, remember: false // remember is checked? } @@ -42,9 +48,10 @@ class Login extends React.Component { // prebind: this.onUsernameChange = this.onUsernameChange.bind(this) this.onUsernameBlur = this.onUsernameBlur.bind(this) - this.onPassword = this.onPassword.bind(this) - this.onSubmit = this.onSubmit.bind(this) - this.onRemember = this.onRemember.bind(this) + this.onPassword = this.onPassword.bind(this) + this.onPassword2 = this.onPassword2.bind(this) + this.onSubmit = this.onSubmit.bind(this) + this.onRemember = this.onRemember.bind(this) } componentDidMount() { @@ -56,8 +63,14 @@ class Login extends React.Component { let submitButton = "" let usernameHelp = null let passwordHelp = null + let password2Help = null + let password2Elem = null let message = null + let buttonText = "Log In" + /* + * disabled for now + * if (this.props.rememberAllowed) { let props = { style: {marginTop: "0px"}, @@ -74,6 +87,7 @@ class Login extends React.Component { } } + */ if (this.state.authError) { // style may be: success, warning, danger, info @@ -108,6 +122,25 @@ class Login extends React.Component { //passwordHelp =   } + if (this.props.mode == 'signup') { + buttonText = 'Sign Up' + if (this.state.password2Error) { + + } + password2Elem = ( + + Repeat Password + + {this.state.password2State == 'success' ? null : } + {password2Help} + + ) + } + let buttonProps = { type: "button", onClick: this.onSubmit, @@ -116,11 +149,16 @@ class Login extends React.Component { if (this.state.loading) { submitButton = } else { - submitButton = + submitButton = } let usernameref = null - if (this.props.domain) { + let usernameDisabled = false + let usernameValue = this.state.username || "" + if (this.props.address) { + usernameDisabled = true + usernameValue = this.props.address + } else if (this.props.domain) { usernameref = function(c) { if (c != null) { let textarea = ReactDOM.findDOMNode(c) @@ -142,7 +180,8 @@ class Login extends React.Component { rows="1" ref={usernameref} autoFocus - value={this.state.username} + value={usernameValue} + disabled={usernameDisabled} onChange={this.onUsernameChange} onBlur={this.onUsernameBlur} /> {this.state.usernameState == 'success' ? null : } @@ -154,12 +193,13 @@ class Login extends React.Component { {this.state.passwordState == 'success' ? null : } {passwordHelp} + {password2Elem} {submitButton} {rememberCheck} @@ -226,6 +266,12 @@ class Login extends React.Component { } } + onPassword2(e) { + let password2 = e.target.value + this.setState({password2: password2}) + this.validatePassword2(password2, this.state.password) + } + onRemember(e) { let currentValue = e.target.value == 'on' ? true : false let value = !currentValue @@ -258,15 +304,43 @@ class Login extends React.Component { passwordState: state, passwordError: message }) + this.validatePassword2(this.state.password2, password) + } + + validatePassword2(password2, password) { + if (password2) { + if (password != password2) { + this.setState({ + password2State: 'error', + password2Error: "Does not match" + }) + } else { + this.setState({ + password2State: 'success', + password2Error: null + }) + } + } else { + this.setState({ + password2State: null, + password2Error: null + }) + } } maySubmit() { - return( + let ok = ( !this.stateLoading && !this.state.usernameError && - this.state.username != "" && - this.state.password != "" + this.state.username && + this.state.password ) + + if (this.props.mode == 'login') { + return ok + } else if (this.props.mode == 'signup') { + return ok && this.state.password2 == this.state.password + } } onSubmit(e) { @@ -274,6 +348,14 @@ class Login extends React.Component { if (!this.maySubmit()) { return } this.setState({loading: true}) + if (this.props.mode == 'login') { + this.doLogin() + } else if (this.props.mode == 'signup') { + this.doSignup() + } + } + + doLogin() { let account = Account.find(this.state.username) account.login(this.state.password).then( account => { @@ -283,9 +365,27 @@ class Login extends React.Component { } }, error => { - console.log(error) if (error == "") { - error = 'Something failed, but we did not get a message' + error = "Something failed, but we did not get a message" + } + this.setState({ + loading: false, + usernameState: 'error', + passwordState: 'error', + authError: error + }) + } + ) + } + + doSignup() { + Account.create(this.state.username, this.state.password).then( + account => { + this.doLogin() + }, + error => { + if (error == "") { + error = "Something failed, but we did not get a message" } this.setState({ loading: false, diff --git a/ui/app/components/main_panel/email_section.js b/ui/app/components/main_panel/email_section.js index a6525d9..a3ff11c 100644 --- a/ui/app/components/main_panel/email_section.js +++ b/ui/app/components/main_panel/email_section.js @@ -19,8 +19,6 @@ export default class EmailSection extends React.Component { this.openKeys = this.openKeys.bind(this) this.openApp = this.openApp.bind(this) this.openPrefs = this.openPrefs.bind(this) - - console.log('email constructor') } openKeys() {} diff --git a/ui/app/components/main_panel/index.js b/ui/app/components/main_panel/index.js index 3cc6c11..a05f307 100644 --- a/ui/app/components/main_panel/index.js +++ b/ui/app/components/main_panel/index.js @@ -32,9 +32,7 @@ export default class MainPanel extends React.Component { componentWillMount() { if (this.props.initialAccount) { - console.log(Account.list) Account.add(this.props.initialAccount) - Account.add(new DummyAccount(this.props.initialAccount)) this.setState({ account: this.props.initialAccount, accounts: Account.list diff --git a/ui/app/components/main_panel/user_section.js b/ui/app/components/main_panel/user_section.js index 0b4ba13..acaea23 100644 --- a/ui/app/components/main_panel/user_section.js +++ b/ui/app/components/main_panel/user_section.js @@ -61,9 +61,17 @@ export default class UserSection extends React.Component { ) } else { + let address = null + if (this.props.account.userpart) { + address = this.props.account.address + } return ( - + ) } diff --git a/ui/app/components/panel_switcher.js b/ui/app/components/panel_switcher.js index aaf2dc5..2746f00 100644 --- a/ui/app/components/panel_switcher.js +++ b/ui/app/components/panel_switcher.js @@ -17,7 +17,7 @@ export default class PanelSwitcher extends React.Component { this.state = { panel: null, panel_properties: null, - debug: false + debug: true } App.switcher = this } @@ -33,10 +33,11 @@ export default class PanelSwitcher extends React.Component { this.panelRender(this.state.panel, this.state.panel_properties) ) } - if (this.state.debug) { - elems.push( - elem(DebugPanel, {key: 'debug'}) - ) + if (this.state.debug && this.state.panel) { + window.location.hash = this.state.panel + //elems.push( + // elem(DebugPanel, {key: 'debug'}) + //) } return
{elems}
} diff --git a/ui/app/components/wizard/add_provider_modal.js b/ui/app/components/wizard/add_provider_modal.js index bc5e023..d54ec08 100644 --- a/ui/app/components/wizard/add_provider_modal.js +++ b/ui/app/components/wizard/add_provider_modal.js @@ -3,12 +3,13 @@ // import React from 'react' -import { FormGroup, ControlLabel, FormControl, HelpBlock, Button, Modal } from 'react-bootstrap' -import Spinner from '../spinner' -import Validate from '../../lib/validate' -import App from '../../app' +import { FormGroup, ControlLabel, FormControl, HelpBlock, Button, ButtonToolbar, Modal } from 'react-bootstrap' -class AddProviderModal extends React.Component { +import Spinner from 'components/spinner' +import Validate from 'lib/validate' +import Provider from 'models/provider' + +export default class AddProviderModal extends React.Component { static get defaultProps() {return{ title: 'Add a provider', @@ -18,20 +19,36 @@ class AddProviderModal extends React.Component { constructor(props) { super(props) this.state = { - validationState: null, + validationState: null, // one of 'success', 'error', 'warning' errorMsg: null, - domain: "" + domain: "", + working: false, // true if waiting for something } this.accept = this.accept.bind(this) this.cancel = this.cancel.bind(this) this.changed = this.changed.bind(this) } - accept() { + accept(e=null) { + if (e) { + e.preventDefault() // don't reload the page please! + } if (this.state.domain) { - App.providers.add(this.state.domain) + this.setState({working: true}) + Provider.setup(this.state.domain).then( + provider => { + this.props.onClose(provider) + // this.setState({working: false}) + }, + error => { + this.setState({ + validationState: 'warning', + errorMsg: error, + working: false + }) + } + ) } - this.props.onClose() } cancel() { @@ -44,9 +61,9 @@ class AddProviderModal extends React.Component { let newMsg = null if (domain.length > 0) { - let error = Validate.domain(domain) - newState = error ? 'error' : 'success' - newMsg = error + let msg = Validate.domain(domain) + newState = msg ? 'error' : 'success' + newMsg = msg } this.setState({ domain: domain, @@ -57,11 +74,21 @@ class AddProviderModal extends React.Component { render() { let help = null + let addButton = null if (this.state.errorMsg) { help = {this.state.errorMsg} } else { help =   } + if (this.state.working) { + addButton = + } else if (this.state.validationState == 'warning') { + addButton = + } else if (this.state.validationState == 'error') { + addButton = + } else { + addButton = + } let form =
Domain @@ -72,10 +99,12 @@ class AddProviderModal extends React.Component { value={this.state.domain} onChange={this.changed} onBlur={this.changed} /> - {help} - + + {addButton} + +
return( @@ -90,5 +119,3 @@ class AddProviderModal extends React.Component { ) } } - -export default AddProviderModal \ No newline at end of file diff --git a/ui/app/components/wizard/index.js b/ui/app/components/wizard/index.js index 613b88f..75e3a1d 100644 --- a/ui/app/components/wizard/index.js +++ b/ui/app/components/wizard/index.js @@ -6,26 +6,27 @@ import React from 'react' import App from 'app' import ProviderSelectStage from './provider_select_stage' +import RegisterStage from './register_stage' import './wizard.less' export default class Wizard extends React.Component { + static get defaultProps() {return{ + stage: "provider" + }} + constructor(props) { super(props) - this.state = { - stage: 'provider' - } - } - - setStage(stage) { - this.setState({stage: stage}) } render() { let stage = null - switch(this.state.stage) { + switch(this.props.stage) { case 'provider': - stage = + stage = + break + case 'register': + stage = break } return( diff --git a/ui/app/components/wizard/provider_select_stage.js b/ui/app/components/wizard/provider_select_stage.js index 20674be..19799f8 100644 --- a/ui/app/components/wizard/provider_select_stage.js +++ b/ui/app/components/wizard/provider_select_stage.js @@ -2,7 +2,11 @@ import React from 'react' import {Button, ButtonGroup, ButtonToolbar, Glyphicon} from 'react-bootstrap' import App from 'app' -import ListEdit from 'components/list_edit' +import Provider from 'models/provider' + +import ListEditor from 'components/list_editor' +import {HorizontalLayout, Column} from 'components/layout' + import StageLayout from './stage_layout' import AddProviderModal from './add_provider_modal' @@ -10,75 +14,163 @@ export default class ProviderSelectStage extends React.Component { static get defaultProps() {return{ title: "Choose a provider", - subtitle: "This doesn't work yet" + initialProvider: null }} constructor(props) { super(props) - let domains = this.currentDomains() this.state = { - domains: domains, - showModal: false + domains: [], // array of domains, as strings + showModal: false, + selected: null, // domain of selected item + provider: null, // Provider object, if selected + error: null // error message } - this.add = this.add.bind(this) - this.remove = this.remove.bind(this) - this.close = this.close.bind(this) - this.previous = this.previous.bind(this) + this.add = this.add.bind(this) + this.remove = this.remove.bind(this) + this.select = this.select.bind(this) + this.close = this.close.bind(this) + this.cancel = this.cancel.bind(this) + this.next = this.next.bind(this) + } + + componentWillMount() { + this.refreshList({ + provider: this.props.initialProvider, + selected: (this.props.initialProvider ? this.props.initialProvider.domain : null) + }) } - currentDomains() { - // return(App.providers.domains().slice() || []) - return ['domain1', 'domain2', 'domain3'] + // + // newState is the state to apply after + // domains are refreshed + // + refreshList(newState=null) { + Provider.list(true).then(domains => { + this.setState(Object.assign({domains: domains}, newState)) + if (domains.length > 0) { + let domain = this.state.selected + if (domains.includes(domain)) { + this.select(domain) + } else { + this.select(domains[0]) + } + } else { + this.select(null) + } + }) } add() { this.setState({showModal: true}) } - remove(provider) { - // App.providers.remove(provider) - this.setState({domains: this.currentDomains()}) + remove(domain, newactive) { + Provider.delete(domain).then( + response => { + this.refreshList({selected: newactive}) + }, + error => { + console.log(error) + } + ) } - close() { - let domains = this.currentDomains() - if (domains.length != this.state.domains.length) { - // this is ugly, but i could not get selection working - // by passing it as a property - this.refs.list.setSelected(0) - } + select(domain) { this.setState({ - domains: domains, - showModal: false + selected: domain }) + if (domain) { + Provider.get(domain).then( + provider => { + this.setState({ + provider: provider + }) + }, + error => { + this.setState({ + provider: null, + error: error + }) + } + ) + } else { + this.setState({ + provider: null, + error: null + }) + } + } + + close(provider=null) { + if (provider) { + this.refreshList({ + showModal: false, + provider: provider, + selected: provider.domain + }) + } else { + this.setState({ + showModal: false + }) + } } - previous() { + cancel() { App.start() } + next() { + App.show('wizard', { + stage: 'register', + provider: this.state.provider + }) + } + render() { let modal = null + let info = null + if (this.state.provider) { + info = ( +
+

{this.state.provider.name}

+

{this.state.provider.domain}

+

{this.state.provider.description}

+

Enrollment Policy: {this.state.provider.enrollment_policy}

+

Services: {this.state.provider.services}

+

Languages: {this.state.provider.languages.join(', ')}

+
+ ) + } else if (this.state.error) { + info =
{this.state.error}
+ } if (this.state.showModal) { modal = } let buttons = ( - - - - +
+ + + + + + +
) - let select = + let editlist = return( - {select} + + {editlist} + {info} + {modal} ) diff --git a/ui/app/components/wizard/register_stage.js b/ui/app/components/wizard/register_stage.js new file mode 100644 index 0000000..9afa958 --- /dev/null +++ b/ui/app/components/wizard/register_stage.js @@ -0,0 +1,102 @@ +import React from 'react' +import {Button, ButtonGroup, ButtonToolbar, + Glyphicon, Tabs, Tab} from 'react-bootstrap' + +import App from 'app' +import Provider from 'models/provider' +import Login from 'components/login' +import Center from 'components/center' + +import StageLayout from './stage_layout' + +export default class RegisterStage extends React.Component { + + static get defaultProps() {return{ + provider: null + }} + + constructor(props) { + super(props) + this.state = { + activeTab: 'signup', // either 'login' or 'signup' + error: null // error message + } + // this.add = this.add.bind(this) + // this.remove = this.remove.bind(this) + // this.select = this.select.bind(this) + this.selectTab = this.selectTab.bind(this) + this.previous = this.previous.bind(this) + this.cancel = this.cancel.bind(this) + this.login = this.login.bind(this) + } + + previous() { + App.show('wizard', { + stage: 'provider', + initialProvider: this.props.provider + }) + } + + cancel() { + App.start() + } + + login(account) { + App.show('main', {initialAccount: account}) + } + + selectTab(key) { + this.setState({ + activeTab: key + }) + } + + render() { + let info = null + if (this.props.provider) { + info = ( +
+

{this.props.provider.name}

+

{this.props.provider.domain}

+

{this.props.provider.description}

+

Enrollment Policy: {this.props.provider.enrollment_policy}

+

Services: {this.props.provider.services}

+

Languages: {this.props.provider.languages.join(', ')}

+
+ ) + } + let buttons = ( +
+ + + + + + +
+ ) + return( + + + +
+
+ +
+ + +
+
+ +
+ + + + ) + } +} diff --git a/ui/app/css/common.css b/ui/app/css/common.css index acf164e..fcae7fa 100644 --- a/ui/app/css/common.css +++ b/ui/app/css/common.css @@ -63,12 +63,21 @@ body { */ .center-container { - position: absolute; + display: -webkit-flex; + display: flex; -webkit-flex-flow: row nowrap; + flex-flow: row nowrap; -webkit-justify-content: center; + justify-content: center; -webkit-align-content: center; + align-content: center; -webkit-align-items: center; + align-items: center; +} + +.center-container.center-both { + position: absolute; top: 0px; left: 0px; height: 100%; @@ -77,4 +86,13 @@ body { .center-container .center-item { -webkit-flex: 0 1 auto; + flex: 0 1 auto; } + +/* + * typography + */ + +h1.first { margin-top: 0px } + +.vspacer {margin-top: 20px } \ No newline at end of file diff --git a/ui/app/main.js b/ui/app/main.js index b162895..8f42765 100644 --- a/ui/app/main.js +++ b/ui/app/main.js @@ -16,7 +16,7 @@ class Main extends React.Component { } componentDidMount() { - App.start() + App.initialize() } } diff --git a/ui/app/models/account.js b/ui/app/models/account.js index 52fea93..726a8b8 100644 --- a/ui/app/models/account.js +++ b/ui/app/models/account.js @@ -57,6 +57,11 @@ export default class Account { return bitmask.bonafide.user.auth(this.address, password).then( response => { if (response.uuid) { + // currently, only one account can be authenticated at once + Account.list.forEach(account => { + account._authenticated = false + // if(account.id != this.id) {account.logout()} + }) this._uuid = response.uuid this._authenticated = true } @@ -131,13 +136,15 @@ export default class Account { return i.id != account.id }) } - // return Account.list - // return new Promise(function(resolve, reject) { - // window.setTimeout(function() { - // resolve(['@blah', '@lala']) - // }, 1000) - // }) - // } + + static create(address, password) { + return bitmask.bonafide.user.create(address, password).then( + response => { + console.log(response) + return new Account(address) + } + ) + } } Account.list = [] diff --git a/ui/app/models/provider.js b/ui/app/models/provider.js new file mode 100644 index 0000000..2ed2dc8 --- /dev/null +++ b/ui/app/models/provider.js @@ -0,0 +1,59 @@ +import bitmask from 'lib/bitmask' + +var LOCALE = 'en' + +export default class Provider { + + constructor(props) { + this._name = props.name + this._description = props.description + let k = null + for (k in props) { + if (k != 'description' && k != 'name') { + this[k] = props[k] + } + } + } + + get name() { + return this._name[LOCALE] + } + + get description() { + return this._description[LOCALE] + } + + static setup(domain) { + return bitmask.bonafide.provider.create(domain).then( + response => { + console.log("Provider configured: " + response.domain) + return new Provider(response) + } + ) + } + + static get(domain) { + return bitmask.bonafide.provider.read(domain).then( + response => { + return new Provider(response) + } + ) + } + + static list(seeded=false) { + return bitmask.bonafide.provider.list(seeded).then( + response => { + return response.map( + i => { return i['domain'] } + ) + } + ) + } + + static delete(domain) { + return bitmask.bonafide.provider.delete(domain) + } +} + + + -- cgit v1.2.3