diff --git a/gulpfile.js b/gulpfile.js index 25af23bf..6f82da83 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -93,7 +93,8 @@ gulp.task('browser-sync', function() { browserSync({ files: config.filesToWatch, proxy: 'http://localhost:4000', - port: 3000 + port: 3000, + browser: "chromium-browser" }); }); diff --git a/js/actions/coa_actions.js b/js/actions/coa_actions.js new file mode 100644 index 00000000..b475aeb7 --- /dev/null +++ b/js/actions/coa_actions.js @@ -0,0 +1,36 @@ +'use strict'; + +import alt from '../alt'; +import CoaFetcher from '../fetchers/coa_fetcher'; + + +class CoaActions { + constructor() { + this.generateActions( + 'updateCoa' + ); + } + + fetchOne(id) { + CoaFetcher.fetchOne(id) + .then((res) => { + this.actions.updateCoa(res.coa); + console.log(res.coa); + }) + .catch((err) => { + console.log(err); + }); + } + create(edition) { + CoaFetcher.create(edition.bitcoin_id) + .then((res) => { + this.actions.updateCoa(res.coa); + console.log(res.coa); + }) + .catch((err) => { + console.log(err); + }); + } +} + +export default alt.createActions(CoaActions); diff --git a/js/components/ascribe_uploader/file_drag_and_drop.js b/js/components/ascribe_uploader/file_drag_and_drop.js index f08289f6..6e8843be 100644 --- a/js/components/ascribe_uploader/file_drag_and_drop.js +++ b/js/components/ascribe_uploader/file_drag_and_drop.js @@ -107,7 +107,7 @@ var FileDragAndDrop = React.createClass({ }, render: function () { - let hasFiles = this.props.filesToUpload.length > 0; + let hasFiles = this.props.filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled').length > 0; let className = hasFiles ? 'file-drag-and-drop has-files ' : 'file-drag-and-drop '; className += this.props.dropzoneInactive ? 'inactive-dropzone' : 'active-dropzone'; diff --git a/js/components/ascribe_uploader/file_drag_and_drop_preview_iterator.js b/js/components/ascribe_uploader/file_drag_and_drop_preview_iterator.js index b9b70039..4961ed0c 100644 --- a/js/components/ascribe_uploader/file_drag_and_drop_preview_iterator.js +++ b/js/components/ascribe_uploader/file_drag_and_drop_preview_iterator.js @@ -16,13 +16,17 @@ let FileDragAndDropPreviewIterator = React.createClass({ return (
{this.props.files.map((file, i) => { - return ( - - ); + if(file.status !== 'deleted' && file.status !== 'canceled') { + return ( + + ); + } else { + return null; + } })}
); diff --git a/js/components/ascribe_uploader/react_s3_fine_uploader.js b/js/components/ascribe_uploader/react_s3_fine_uploader.js index 584cbd5b..7afd4981 100644 --- a/js/components/ascribe_uploader/react_s3_fine_uploader.js +++ b/js/components/ascribe_uploader/react_s3_fine_uploader.js @@ -6,6 +6,9 @@ import promise from 'es6-promise'; promise.polyfill(); import fetch from 'isomorphic-fetch'; +import AppConstants from '../../constants/application_constants'; + +import { getCookie } from '../../utils/fetch_api_utils'; import fineUploader from 'fineUploader'; import FileDragAndDrop from './file_drag_and_drop'; @@ -18,7 +21,8 @@ var ReactS3FineUploader = React.createClass({ propTypes: { keyRoutine: React.PropTypes.shape({ url: React.PropTypes.string, - fileClass: React.PropTypes.string + fileClass: React.PropTypes.string, + bitcoinId: React.PropTypes.string }), createBlobRoutine: React.PropTypes.shape({ url: React.PropTypes.string @@ -79,8 +83,67 @@ var ReactS3FineUploader = React.createClass({ }) }, + getDefaultProps() { + return { + autoUpload: true, + debug: false, + objectProperties: { + acl: 'public-read', + bucket: 'ascribe0' + }, + request: { + endpoint: 'https://ascribe0.s3.amazonaws.com', + accessKey: 'AKIAIVCZJ33WSCBQ3QDA' + }, + uploadSuccess: { + params: { + isBrowserPreviewCapable: fineUploader.supportedFeatures.imagePreviews + } + }, + signature: { + endpoint: AppConstants.serverUrl + 's3/signature/', + customHeaders: { + 'X-CSRFToken': getCookie('csrftoken') + }, + }, + deleteFile: { + enabled: true, + method: 'DELETE', + endpoint: AppConstants.serverUrl + 's3/delete', + customHeaders: { + 'X-CSRFToken': getCookie('csrftoken') + } + }, + cors: { + expected: true, + sendCredentials: true + }, + chunking: { + enabled: true + }, + resume: { + enabled: true + }, + retry: { + enableAuto: false + }, + session: { + endpoint: null + }, + messages: { + unsupportedBrowser: '

Upload is not functional in IE7 as IE7 has no support for CORS!

' + }, + formatFileName: function(name){// fix maybe + if (name !== undefined && name.length > 26) { + name = name.slice(0, 15) + '...' + name.slice(-15); + } + return name; + }, + multiple: false + }; + }, + getInitialState() { - console.log(this.props); return { filesToUpload: [], uploader: new fineUploader.s3.FineUploaderBasic(this.propsToConfig()) @@ -143,7 +206,8 @@ var ReactS3FineUploader = React.createClass({ credentials: 'include', body: JSON.stringify({ 'filename': filename, - 'file_class': 'digitalwork' + 'file_class': this.props.keyRoutine.fileClass, + 'bitcoin_id': this.props.keyRoutine.bitcoinId }) }) .then((res) => { @@ -270,7 +334,7 @@ var ReactS3FineUploader = React.createClass({ // If multiple set and user already uploaded its work, // cancel upload - if(!this.props.multiple && this.state.filesToUpload.length > 0) { + if(!this.props.multiple && this.state.filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled').length > 0) { return; } @@ -313,6 +377,7 @@ var ReactS3FineUploader = React.createClass({ } } + // set the new file array let newState = React.addons.update(this.state, { filesToUpload: { $set: oldAndNewFiles } }); @@ -341,7 +406,7 @@ var ReactS3FineUploader = React.createClass({ handleDeleteFile={this.handleDeleteFile} handleCancelFile={this.handleCancelFile} multiple={this.props.multiple} - dropzoneInactive={!this.props.multiple && this.state.filesToUpload.length > 0} /> + dropzoneInactive={!this.props.multiple && this.state.filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled').length > 0} /> ); } diff --git a/js/components/coa_verify_container.js b/js/components/coa_verify_container.js new file mode 100644 index 00000000..b1721999 --- /dev/null +++ b/js/components/coa_verify_container.js @@ -0,0 +1,101 @@ +'use strict'; + +import React from 'react'; +import Router from 'react-router'; + +import GlobalNotificationModel from '../models/global_notification_model'; +import GlobalNotificationActions from '../actions/global_notification_actions'; + +import Form from './ascribe_forms/form'; +import Property from './ascribe_forms/property'; +import InputTextAreaToggable from './ascribe_forms/input_textarea_toggable'; + +import apiUrls from '../constants/api_urls'; + + +let CoaVerifyContainer = React.createClass({ + mixins: [Router.Navigation], + + render() { + return ( +
+
+
+ Verify your Certificate of Authenticity +
+ + +
+
+ ascribe is using the following public key for verification: +
+
+                -----BEGIN PUBLIC KEY-----
+                MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDddadqY31kKPFYk8PQA8BWSTbm
+                gaGf9KEYBALp2nWAJcwq80qBzGF+gfi0Z+yb4ooeKHl27GnuxZYValE1Z5ZujfeJ
+                TgO4li59ZMYiah8oXZp/OysrBwCvWw0PtWd8/D9Nc4PqyOz5gzEh6kFah5VsuAke
+                Znu2w7KmeLZ85SmwEQIDAQAB
+                -----END PUBLIC KEY-----
+                
+
+ ); + } +}); + + +let CoaVerifyForm = React.createClass({ + mixins: [Router.Navigation], + + handleSuccess(response){ + let notification = null; + if (response.verdict){ + notification = new GlobalNotificationModel('Certificate of Authenticity successfully verified', 'success'); + GlobalNotificationActions.appendGlobalNotification(notification); + } + }, + + render() { + return ( +
+
+ Verify your Certificate of Authenticity + } + spinner={ + + }> + + + + + + +
+
+
+ ); + } +}); + + +export default CoaVerifyContainer; \ No newline at end of file diff --git a/js/components/edition.js b/js/components/edition.js index 34975699..78f36628 100644 --- a/js/components/edition.js +++ b/js/components/edition.js @@ -1,6 +1,7 @@ 'use strict'; import React from 'react'; +import Router from 'react-router'; import Row from 'react-bootstrap/lib/Row'; import Col from 'react-bootstrap/lib/Col'; @@ -9,6 +10,8 @@ import Glyphicon from 'react-bootstrap/lib/Glyphicon'; import UserActions from '../actions/user_actions'; import UserStore from '../stores/user_store'; +import CoaActions from '../actions/coa_actions'; +import CoaStore from '../stores/coa_store'; import MediaPlayer from './ascribe_media/media_player'; @@ -24,12 +27,16 @@ import RequestActionForm from './ascribe_forms/form_request_action'; import EditionActions from '../actions/edition_actions'; import AclButtonList from './ascribe_buttons/acl_button_list'; +import fineUploader from 'fineUploader'; +import ReactS3FineUploader from './ascribe_uploader/react_s3_fine_uploader'; + import GlobalNotificationModel from '../models/global_notification_model'; import GlobalNotificationActions from '../actions/global_notification_actions'; -import requests from '../utils/requests'; import apiUrls from '../constants/api_urls'; +import AppConstants from '../constants/application_constants'; +let Link = Router.Link; /** * This is the component that implements display-specific functionality */ @@ -93,6 +100,7 @@ let Edition = React.createClass({ -1 || Object.keys(this.props.edition.extra_data).length > 0}> + -1}> + + + 0}> @@ -191,6 +206,9 @@ let EditionSummary = React.createClass({ edition: React.PropTypes.object }, + getTransferWithdrawData(){ + return {'bitcoin_id': this.props.edition.bitcoin_id}; + }, handleSuccess(){ EditionActions.fetchOne(this.props.edition.id); }, @@ -202,7 +220,24 @@ let EditionSummary = React.createClass({ render() { let status = null; if (this.props.edition.status.length > 0){ - status = ; + let statusStr = this.props.edition.status.join().replace(/_/, ' '); + status = ; + if (this.props.edition.pending_new_owner && this.props.edition.acl.indexOf('withdraw_transfer') > -1){ + status = ( +
+ + + +
+ ); + } } let actions = null; if (this.props.edition.request_action && this.props.edition.request_action.length > 0){ @@ -262,6 +297,18 @@ let EditionDetailProperty = React.createClass({ }, render() { + let value = this.props.value; + if (this.props.children){ + value = ( +
+
+ { this.props.value } +
+
+ { this.props.children } +
+
); + } return (
@@ -269,7 +316,7 @@ let EditionDetailProperty = React.createClass({
{ this.props.label + this.props.separator}
-
{ this.props.value }
+ {value}
@@ -284,19 +331,20 @@ let EditionDetailHistoryIterator = React.createClass({ render() { return ( -
+
{this.props.history.map((historicalEvent, i) => { return ( - + +
{ historicalEvent[1] }
+
); })} -
+
+ ); } }); @@ -415,10 +463,92 @@ let EditionFurtherDetails = React.createClass({ handleSuccess={this.showNotification} editable={editable} edition={this.props.edition} /> + ); } }); +let FileUploader = React.createClass( { + handleChange(){ + this.setState({other_data_key: this.refs.fineuploader.state.filesToUpload[0].key}); + }, + render() { + return ( + + ); + } +}); + +let CoaDetails = React.createClass({ + propTypes: { + edition: React.PropTypes.object + }, + + getInitialState() { + return CoaStore.getState(); + }, + + componentDidMount() { + CoaStore.listen(this.onChange); + if (this.props.edition.coa) { + CoaActions.fetchOne(this.props.edition.coa); + } + else{ + console.log('create coa'); + CoaActions.create(this.props.edition); + } + }, + + componentWillUnmount() { + CoaStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + }, + + render() { + if (this.state.coa.url_safe) { + return ( +
+

+ + + + + +

+
+ ); + } + return ( +
+ +
); + + } +}); + export default Edition; diff --git a/js/components/login_container.js b/js/components/login_container.js index d767b399..9e6dd10a 100644 --- a/js/components/login_container.js +++ b/js/components/login_container.js @@ -12,6 +12,7 @@ import Form from './ascribe_forms/form'; import Property from './ascribe_forms/property'; import apiUrls from '../constants/api_urls'; +import AppConstants from '../constants/application_constants'; let LoginContainer = React.createClass({ @@ -65,7 +66,7 @@ let LoginForm = React.createClass({ Users on Stack Overflow claim this is a bug in chrome and should be fixed in the future. Until then, we redirect the HARD way, but reloading the whole page using window.location */ - window.location = '/collection'; + window.location = AppConstants.baseUrl + 'collection'; }, render() { diff --git a/js/components/register_piece.js b/js/components/register_piece.js index e878d1f6..c234ff92 100644 --- a/js/components/register_piece.js +++ b/js/components/register_piece.js @@ -2,8 +2,8 @@ import React from 'react'; +import { getCookie } from '../utils/fetch_api_utils'; import AppConstants from '../constants/application_constants'; -import fineUploader from 'fineUploader'; import Router from 'react-router'; @@ -19,6 +19,7 @@ import ReactS3FineUploader from './ascribe_uploader/react_s3_fine_uploader'; import DatePicker from 'react-datepicker/dist/react-datepicker'; + let RegisterPiece = React.createClass( { mixins: [Router.Navigation], @@ -135,61 +136,10 @@ let FileUploader = React.createClass({ url: apiUrls.blob_digitalworks }} handleChange={this.props.handleChange} - autoUpload={true} - debug={false} - objectProperties={{ - acl: 'public-read', - bucket: 'ascribe0' - }} - request={{ - endpoint: 'https://ascribe0.s3.amazonaws.com', - accessKey: 'AKIAIVCZJ33WSCBQ3QDA' - }} - signature={{ - endpoint: AppConstants.serverUrl + 's3/signature/' - }} - uploadSuccess={{ - params: { - isBrowserPreviewCapable: fineUploader.supportedFeatures.imagePreviews - } - }} - cors={{ - expected: true - }} - chunking={{ - enabled: true - }} - resume={{ - enabled: true - }} - retry={{ - enableAuto: false - }} - deleteFile={{ - enabled: true, - method: 'DELETE', - endpoint: AppConstants.serverUrl + 's3/delete', - customHeaders: { - 'X-CSRFToken': 'asdasd' - } - }} validation={{ itemLimit: 100000, sizeLimit: '25000000000' - }} - session={{ - endpoint: null - }} - messages={{ - unsupportedBrowser: '

Upload is not functional in IE7 as IE7 has no support for CORS!

' - }} - formatFileName={(name) => {// fix maybe - if (name !== undefined && name.length > 26) { - name = name.slice(0, 15) + '...' + name.slice(-15); - } - return name; - }} - multiple={true}/> + }}/> ); } }); diff --git a/js/components/signup_container.js b/js/components/signup_container.js index a96f8c42..145df41f 100644 --- a/js/components/signup_container.js +++ b/js/components/signup_container.js @@ -137,7 +137,7 @@ let SignupForm = React.createClass({ name='promo_code' label="Promocode">
diff --git a/js/constants/api_urls.js b/js/constants/api_urls.js index 608893e1..e1e5d15e 100644 --- a/js/constants/api_urls.js +++ b/js/constants/api_urls.js @@ -6,6 +6,9 @@ let apiUrls = { 'applications': AppConstants.apiEndpoint + 'applications/', 'application_token_refresh': AppConstants.apiEndpoint + 'applications/refresh_token/', 'blob_digitalworks': AppConstants.apiEndpoint + 'blob/digitalworks/', + 'coa': AppConstants.apiEndpoint + 'coa/${id}/', + 'coa_create': AppConstants.apiEndpoint + 'coa/', + 'coa_verify': AppConstants.apiEndpoint + 'coa/verify_coa/', 'edition': AppConstants.apiEndpoint + 'editions/${bitcoin_id}/', 'edition_delete': AppConstants.apiEndpoint + 'editions/${edition_id}/', 'edition_remove_from_collection': AppConstants.apiEndpoint + 'ownership/shares/${edition_id}/', @@ -20,6 +23,7 @@ let apiUrls = { 'ownership_loans_deny': AppConstants.apiEndpoint + 'ownership/loans/deny/', 'ownership_shares': AppConstants.apiEndpoint + 'ownership/shares/', 'ownership_transfers': AppConstants.apiEndpoint + 'ownership/transfers/', + 'ownership_transfers_withdraw': AppConstants.apiEndpoint + 'ownership/transfers/withdraw/', 'ownership_unconsigns': AppConstants.apiEndpoint + 'ownership/unconsigns/', 'ownership_unconsigns_deny': AppConstants.apiEndpoint + 'ownership/unconsigns/deny/', 'ownership_unconsigns_request': AppConstants.apiEndpoint + 'ownership/unconsigns/request/', diff --git a/js/fetchers/coa_fetcher.js b/js/fetchers/coa_fetcher.js new file mode 100644 index 00000000..48ee9e73 --- /dev/null +++ b/js/fetchers/coa_fetcher.js @@ -0,0 +1,19 @@ +'use strict'; + +import requests from '../utils/requests'; + +let CoaFetcher = { + /** + * Fetch one user from the API. + * If no arg is supplied, load the current user + */ + fetchOne(id) { + return requests.get('coa', {'id': id}); + }, + create(bitcoinId) { + console.log(bitcoinId); + return requests.post('coa_create', {body: {'bitcoin_id': bitcoinId}}); + } +}; + +export default CoaFetcher; diff --git a/js/routes.js b/js/routes.js index 02ed27c5..7dc34687 100644 --- a/js/routes.js +++ b/js/routes.js @@ -12,6 +12,8 @@ import SignupContainer from './components/signup_container'; import PasswordResetContainer from './components/password_reset_container'; import SettingsContainer from './components/settings_container'; +import CoaVerifyContainer from './components/coa_verify_container'; + import AppConstants from './constants/application_constants'; import RegisterPiece from './components/register_piece'; @@ -28,6 +30,7 @@ let routes = ( + diff --git a/js/stores/coa_store.js b/js/stores/coa_store.js new file mode 100644 index 00000000..1aa762f3 --- /dev/null +++ b/js/stores/coa_store.js @@ -0,0 +1,18 @@ +'use strict'; + +import alt from '../alt'; +import CoaActions from '../actions/coa_actions'; + + +class CoaStore { + constructor() { + this.coa = {}; + this.bindActions(CoaActions); + } + + onUpdateCoa(coa) { + this.coa = coa; + } +} + +export default alt.createStore(CoaStore, 'CoaStore'); diff --git a/package.json b/package.json index 8e79cf8b..dbf65f45 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,6 @@ "devDependencies": { "babel-eslint": "^3.1.11", "babel-jest": "^5.2.0", - "browserify-shim": "^3.8.9", "jest-cli": "^0.4.0" }, "dependencies": { @@ -44,6 +43,7 @@ "bootstrap-sass": "^3.3.4", "browser-sync": "^2.7.5", "browserify": "^9.0.8", + "browserify-shim": "^3.8.9", "classnames": "^1.2.2", "compression": "^1.4.4", "envify": "^3.4.0", diff --git a/sass/ascribe_edition.scss b/sass/ascribe_edition.scss index acf4b529..f1af95d2 100644 --- a/sass/ascribe_edition.scss +++ b/sass/ascribe_edition.scss @@ -28,4 +28,20 @@ width:100%; margin-top: 1em; +} + +.coa-file-wrapper{ + display: table; + height: 200px; + overflow: hidden; + margin: 0 auto; + width: 100%; + padding: 1em; +} + +.coa-file { + display: table-cell; + vertical-align: middle; + border: 1px solid #CCC; + background-color: #F8F8F8; } \ No newline at end of file diff --git a/sass/ascribe_textarea.scss b/sass/ascribe_textarea.scss index 8d52ca64..2d368dbf 100644 --- a/sass/ascribe_textarea.scss +++ b/sass/ascribe_textarea.scss @@ -5,7 +5,8 @@ } .ascribe-textarea-editable:hover { - border: 1px solid #AAA; + //border: 1px solid #AAA;; + border: none; } .ascribe-pre{ diff --git a/sass/main.scss b/sass/main.scss index 0593624e..3ea92fb2 100644 --- a/sass/main.scss +++ b/sass/main.scss @@ -32,6 +32,12 @@ body { display: none; } +.no-margin{ + margin: 0; +} +.no-padding{ + padding: 0; +} .navbar-default { border: none; border-left:0;