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 (
+
+ );
+ }
+});
+
+
+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 (
-
+
+
);
}
});
@@ -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 (
+
+
+
+ Download
+
+
+
+ Verify
+
+
+
+
+
+ );
+ }
+ 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;