diff --git a/img/ascribe_animated_large.gif b/img/ascribe_animated_large.gif new file mode 100644 index 00000000..bb3c620d Binary files /dev/null and b/img/ascribe_animated_large.gif differ diff --git a/img/ascribe_animated_small.gif b/img/ascribe_animated_small.gif new file mode 100644 index 00000000..7d249c1e Binary files /dev/null and b/img/ascribe_animated_small.gif differ diff --git a/js/app.js b/js/app.js index 447ecc96..c50dd902 100644 --- a/js/app.js +++ b/js/app.js @@ -15,15 +15,11 @@ let headers = { 'Content-Type': 'application/json' }; -if (window.DEBUG) { - headers.Authorization = 'Basic ' + window.CREDENTIALS; -} - fetch.defaults({ urlMap: ApiUrls, http: { headers: headers, - credentials: 'same-origin' + credentials: 'include' }, fatalErrorHandler: (err) => { console.log(err); diff --git a/js/components/ascribe_buttons/button_submit.js b/js/components/ascribe_buttons/button_submit.js new file mode 100644 index 00000000..ef5999cd --- /dev/null +++ b/js/components/ascribe_buttons/button_submit.js @@ -0,0 +1,27 @@ +'use strict'; + +import React from 'react'; + +let ButtonSubmitOrClose = React.createClass({ + propTypes: { + submitted: React.PropTypes.bool.isRequired, + text: React.PropTypes.string.isRequired + }, + + render() { + if (this.props.submitted){ + return ( +
+ +
+ ); + } + return ( +
+ +
+ ); + } +}); + +export default ButtonSubmitOrClose; diff --git a/js/components/ascribe_buttons/delete_button.js b/js/components/ascribe_buttons/delete_button.js index 1b2a7748..7077138d 100644 --- a/js/components/ascribe_buttons/delete_button.js +++ b/js/components/ascribe_buttons/delete_button.js @@ -49,7 +49,7 @@ let DeleteButton = React.createClass({ btnDelete = ; } else{ - return
; + return null; } return ( + + + + +
+ Forgot your password? + Reset password}/> +
+
+ Not a member yet? + Sign up}/> +
+ + + ); + } +}); + +export default LoginForm; \ No newline at end of file diff --git a/js/components/ascribe_forms/form_password_reset.js b/js/components/ascribe_forms/form_password_reset.js new file mode 100644 index 00000000..60e9b077 --- /dev/null +++ b/js/components/ascribe_forms/form_password_reset.js @@ -0,0 +1,50 @@ +'use strict'; + +import React from 'react'; + +import apiUrls from '../../constants/api_urls'; +import FormMixin from '../../mixins/form_mixin'; +import InputText from './input_text'; +import ButtonSubmit from '../ascribe_buttons/button_submit'; + +let PasswordResetForm = React.createClass({ + mixins: [FormMixin], + + url() { + return apiUrls.users_password_reset; + }, + + getFormData() { + return { + email: this.props.email, + token: this.props.token, + password: this.refs.password.state.value, + password_confirm: this.refs.password_confirm.state.value + }; + }, + + renderForm() { + return ( +
+
Reset the password for {this.props.email}:
+ + + + + ); + } +}); + +export default PasswordResetForm; \ No newline at end of file diff --git a/js/components/ascribe_forms/form_password_reset_request.js b/js/components/ascribe_forms/form_password_reset_request.js new file mode 100644 index 00000000..f61e8b05 --- /dev/null +++ b/js/components/ascribe_forms/form_password_reset_request.js @@ -0,0 +1,41 @@ +'use strict'; + +import React from 'react'; + +import apiUrls from '../../constants/api_urls'; +import FormMixin from '../../mixins/form_mixin'; +import InputText from './input_text'; +import ButtonSubmitOrClose from '../ascribe_buttons/button_submit_close'; + +let PasswordResetRequestForm = React.createClass({ + mixins: [FormMixin], + + url() { + return apiUrls.users_password_reset_request; + }, + + getFormData() { + return { + email: this.refs.email.state.value + }; + }, + + renderForm() { + return ( +
+ + + + ); + } +}); + +export default PasswordResetRequestForm; \ No newline at end of file diff --git a/js/components/ascribe_forms/form_signup.js b/js/components/ascribe_forms/form_signup.js new file mode 100644 index 00000000..f46a0b64 --- /dev/null +++ b/js/components/ascribe_forms/form_signup.js @@ -0,0 +1,79 @@ +'use strict'; + +import React from 'react'; + +import apiUrls from '../../constants/api_urls'; +import FormMixin from '../../mixins/form_mixin'; +import InputText from './input_text'; +import InputCheckbox from './input_checkbox'; +import ButtonSubmitOrClose from '../ascribe_buttons/button_submit_close'; + +let SignupForm = React.createClass({ + mixins: [FormMixin], + + url() { + return apiUrls.users_signup; + }, + + getFormData() { + return { + email: this.refs.email.state.value, + password: this.refs.password.state.value, + password_confirm: this.refs.password_confirm.state.value, + terms: this.refs.terms.state.value, + promo_code: this.refs.promo_code.state.value + }; + }, + + renderForm() { + return ( +
+ + + + + +
+ Your password must be at least 10 characters. + This password is securing your digital property like a bank account. + Store it in a safe place! +
+ + I agree to the  + Terms of Service + }/> + + + + ); + } +}); + +export default SignupForm; \ No newline at end of file diff --git a/js/components/ascribe_modal/modal_login.js b/js/components/ascribe_modal/modal_login.js new file mode 100644 index 00000000..fae1e2d7 --- /dev/null +++ b/js/components/ascribe_modal/modal_login.js @@ -0,0 +1,32 @@ +'use strict'; + +import React from 'react'; + +import ModalWrapper from './modal_wrapper'; +import LoginForm from '../ascribe_forms/form_login'; + +import GlobalNotificationModel from '../../models/global_notification_model'; +import GlobalNotificationActions from '../../actions/global_notification_actions'; + +let LoginModal = React.createClass({ + handleLoginSuccess(){ + this.props.handleSuccess(); + let notificationText = 'Login successful'; + let notification = new GlobalNotificationModel(notificationText, 'success'); + GlobalNotificationActions.appendGlobalNotification(notification); + }, + + render() { + return ( + + + + ); + } +}); + +export default LoginModal; diff --git a/js/components/ascribe_modal/modal_password_request_reset.js b/js/components/ascribe_modal/modal_password_request_reset.js new file mode 100644 index 00000000..17768f2c --- /dev/null +++ b/js/components/ascribe_modal/modal_password_request_reset.js @@ -0,0 +1,30 @@ +'use strict'; + +import React from 'react'; + +import ModalWrapper from './modal_wrapper'; +import PasswordResetRequestForm from '../ascribe_forms/form_password_reset_request'; + +import GlobalNotificationModel from '../../models/global_notification_model'; +import GlobalNotificationActions from '../../actions/global_notification_actions'; + +let PasswordResetRequestModal = React.createClass({ + handleResetSuccess(){ + let notificationText = 'Request succesfully sent, check your email'; + let notification = new GlobalNotificationModel(notificationText, 'success', 50000); + GlobalNotificationActions.appendGlobalNotification(notification); + }, + render() { + return ( + + + + ); + } +}); + +export default PasswordResetRequestModal; diff --git a/js/components/ascribe_modal/modal_signup.js b/js/components/ascribe_modal/modal_signup.js new file mode 100644 index 00000000..0cd32a0a --- /dev/null +++ b/js/components/ascribe_modal/modal_signup.js @@ -0,0 +1,31 @@ +'use strict'; + +import React from 'react'; + +import ModalWrapper from './modal_wrapper'; +import SignupForm from '../ascribe_forms/form_signup'; + +import GlobalNotificationModel from '../../models/global_notification_model'; +import GlobalNotificationActions from '../../actions/global_notification_actions'; + +let SignupModal = React.createClass({ + handleSignupSuccess(response){ + let notificationText = 'We sent an email to your address ' + response.user.email + ', please confirm.'; + let notification = new GlobalNotificationModel(notificationText, 'success', 50000); + GlobalNotificationActions.appendGlobalNotification(notification); + }, + + render() { + return ( + + + + ); + } +}); + +export default SignupModal; diff --git a/js/components/header.js b/js/components/header.js index 2ad26657..aed6b846 100644 --- a/js/components/header.js +++ b/js/components/header.js @@ -6,11 +6,19 @@ import Router from 'react-router'; import UserActions from '../actions/user_actions'; import UserStore from '../stores/user_store'; +import apiUrls from '../constants/api_urls.js'; +import PieceListActions from '../actions/piece_list_actions'; + import Nav from 'react-bootstrap/lib/Nav'; import Navbar from 'react-bootstrap/lib/Navbar'; +import NavItem from 'react-bootstrap/lib/NavItem'; import DropdownButton from 'react-bootstrap/lib/DropdownButton'; import MenuItem from 'react-bootstrap/lib/MenuItem'; +import LoginModal from '../components/ascribe_modal/modal_login'; +import SignupModal from '../components/ascribe_modal/modal_signup'; + + import { getLangText } from '../utils/lang_utils'; let Link = Router.Link; @@ -34,7 +42,34 @@ let Header = React.createClass({ this.setState(state); }, + refreshData(){ + UserActions.fetchCurrentUser(); + PieceListActions.fetchPieceList(1, 10); + }, render() { + let account = null; + let signup = null; + if (this.state.currentUser.username){ + account = ( + + {getLangText('Account Settings')} +
  • + {getLangText('FAQ')} + {getLangText('Terms of Service')} + + {getLangText('Log out')} +
    + ); + } + else { + account = ( + LOGIN} + handleSuccess={this.refreshData}/>); + signup = ( + SIGNUP} />); + } return ( ); diff --git a/js/components/login_modal_handler.js b/js/components/login_modal_handler.js new file mode 100644 index 00000000..e224cc60 --- /dev/null +++ b/js/components/login_modal_handler.js @@ -0,0 +1,42 @@ +'use strict'; + +import React from 'react'; + +import Button from 'react-bootstrap/lib/Button'; +import Modal from 'react-bootstrap/lib/Modal'; +import OverlayMixin from 'react-bootstrap/lib/OverlayMixin'; + +let LoginModalHandler = React.createClass({ + mixins: [OverlayMixin], + + getInitialState() { + return { + isModalOpen: true + }; + }, + + handleToggle() { + this.setState({ + isModalOpen: !this.state.isModalOpen + }); + }, + + render() { + if (!this.state.isModalOpen || !(this.props.query.login === '')) { + return ; + } + + return ( + +
    + This modal is controlled by our custom trigger component. +
    +
    + +
    +
    + ); + } +}); + +export default LoginModalHandler; \ No newline at end of file diff --git a/js/components/password_reset_container.js b/js/components/password_reset_container.js new file mode 100644 index 00000000..2df20ef0 --- /dev/null +++ b/js/components/password_reset_container.js @@ -0,0 +1,30 @@ +'use strict'; + +import React from 'react'; +import Router from 'react-router'; + +import PasswordResetForm from './ascribe_forms/form_password_reset'; + +import GlobalNotificationModel from '../models/global_notification_model'; +import GlobalNotificationActions from '../actions/global_notification_actions'; + +let PasswordResetContainer = React.createClass({ + mixins: [Router.Navigation], + + handleSuccess(){ + this.transitionTo('pieces'); + let notification = new GlobalNotificationModel('password succesfully updated', 'success', 10000); + GlobalNotificationActions.appendGlobalNotification(notification); + }, + render() { + return ( + + ); + } +}); + +export default PasswordResetContainer; \ No newline at end of file diff --git a/js/constants/api_urls.js b/js/constants/api_urls.js index ed9432cd..eef55e3f 100644 --- a/js/constants/api_urls.js +++ b/js/constants/api_urls.js @@ -3,26 +3,31 @@ import AppConstants from './application_constants'; let apiUrls = { - 'user': AppConstants.apiEndpoint + 'users/', - 'piece': AppConstants.apiEndpoint + 'pieces/${piece_id}', - 'pieces_list': AppConstants.apiEndpoint + 'pieces/', - 'piece_extradata': AppConstants.apiEndpoint + 'pieces/${piece_id}/extradata/', 'edition': AppConstants.apiEndpoint + 'editions/${bitcoin_id}/', - 'editions_list': AppConstants.apiEndpoint + 'pieces/${piece_id}/editions/', 'edition_delete': AppConstants.apiEndpoint + 'editions/${edition_id}/', 'edition_remove_from_collection': AppConstants.apiEndpoint + 'ownership/shares/${edition_id}/', - 'ownership_shares_mail': AppConstants.apiEndpoint + 'ownership/shares/mail/', - 'ownership_transfers': AppConstants.apiEndpoint + 'ownership/transfers/', + 'editions_list': AppConstants.apiEndpoint + 'pieces/${piece_id}/editions/', + 'note_notes': AppConstants.apiEndpoint + 'note/notes/', 'ownership_consigns': AppConstants.apiEndpoint + 'ownership/consigns/', 'ownership_consigns_confirm': AppConstants.apiEndpoint + 'ownership/consigns/confirm/', 'ownership_consigns_deny': AppConstants.apiEndpoint + 'ownership/consigns/deny/', - 'ownership_unconsigns': AppConstants.apiEndpoint + 'ownership/unconsigns/', - 'ownership_unconsigns_request': AppConstants.apiEndpoint + 'ownership/unconsigns/request/', - 'ownership_unconsigns_deny': AppConstants.apiEndpoint + 'ownership/unconsigns/deny/', 'ownership_loans': AppConstants.apiEndpoint + 'ownership/loans/', 'ownership_loans_confirm': AppConstants.apiEndpoint + 'ownership/loans/confirm/', 'ownership_loans_deny': AppConstants.apiEndpoint + 'ownership/loans/deny/', - 'note_notes': AppConstants.apiEndpoint + 'note/notes/' + 'ownership_shares_mail': AppConstants.apiEndpoint + 'ownership/shares/mail/', + 'ownership_transfers': AppConstants.apiEndpoint + 'ownership/transfers/', + 'ownership_unconsigns': AppConstants.apiEndpoint + 'ownership/unconsigns/', + 'ownership_unconsigns_deny': AppConstants.apiEndpoint + 'ownership/unconsigns/deny/', + 'ownership_unconsigns_request': AppConstants.apiEndpoint + 'ownership/unconsigns/request/', + 'piece': AppConstants.apiEndpoint + 'pieces/${piece_id}', + 'piece_extradata': AppConstants.apiEndpoint + 'pieces/${piece_id}/extradata/', + 'pieces_list': AppConstants.apiEndpoint + 'pieces/', + 'user': AppConstants.apiEndpoint + 'users/', + 'users_login': AppConstants.apiEndpoint + 'users/login/', + 'users_logout': AppConstants.apiEndpoint + 'users/logout/', + 'users_password_reset': AppConstants.apiEndpoint + 'users/reset_password/', + 'users_password_reset_request': AppConstants.apiEndpoint + 'users/request_reset_password/', + 'users_signup': AppConstants.apiEndpoint + 'users/' }; export default apiUrls; diff --git a/js/constants/application_constants.js b/js/constants/application_constants.js index 321ee7af..b3fe92b2 100644 --- a/js/constants/application_constants.js +++ b/js/constants/application_constants.js @@ -5,11 +5,11 @@ let constants = { //FIXME: referring to a global variable in `window` is not // super pro. What if we render stuff on the server? - 'baseUrl': window.BASE_URL, + //'baseUrl': window.BASE_URL, 'apiEndpoint': window.API_ENDPOINT, - 'debugCredentialBase64': 'ZGltaUBtYWlsaW5hdG9yLmNvbTowMDAwMDAwMDAw', // dimi@mailinator:0000000000 + 'baseUrl': window.BASE_URL, 'aclList': ['edit', 'consign', 'consign_request', 'unconsign', 'unconsign_request', 'transfer', - 'loan', 'loan_request', 'share', 'download', 'view', 'delete', 'del_from_collection', 'add_to_collection'] + 'loan', 'loan_request', 'share', 'download', 'view', 'delete', 'del_from_collection', 'add_to_collection'] }; export default constants; diff --git a/js/routes.js b/js/routes.js index 5232f0d3..b5196a77 100644 --- a/js/routes.js +++ b/js/routes.js @@ -6,8 +6,9 @@ import Router from 'react-router'; import AscribeApp from './components/ascribe_app'; import PieceList from './components/piece_list'; import EditionContainer from './components/edition_container'; - +import PasswordResetContainer from './components/password_reset_container'; import AppConstants from './constants/application_constants'; +//import LoginModalHandler from './components/login_modal_handler'; let Route = Router.Route; let Redirect = Router.Redirect; @@ -18,6 +19,7 @@ let routes = ( + diff --git a/js/stores/edition_list_store.js b/js/stores/edition_list_store.js index 38cedfb5..8dbf9104 100644 --- a/js/stores/edition_list_store.js +++ b/js/stores/edition_list_store.js @@ -75,4 +75,4 @@ class EditionListStore { } } -export default alt.createStore(EditionListStore, 'EditionListStore'); \ No newline at end of file +export default alt.createStore(EditionListStore, 'EditionListStore');