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/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..68febdd7 100644
--- a/js/components/edition.js
+++ b/js/components/edition.js
@@ -6,9 +6,12 @@ import Row from 'react-bootstrap/lib/Row';
import Col from 'react-bootstrap/lib/Col';
import Button from 'react-bootstrap/lib/Button';
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
+import Panel from 'react-bootstrap/lib/Panel';
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';
@@ -29,6 +32,7 @@ import GlobalNotificationActions from '../actions/global_notification_actions';
import requests from '../utils/requests';
import apiUrls from '../constants/api_urls';
+import AppConstants from '../constants/application_constants';
/**
* This is the component that implements display-specific functionality
@@ -109,13 +113,20 @@ let Edition = React.createClass({
-1 || Object.keys(this.props.edition.extra_data).length > 0}>
+ -1}>
+
+
+
0}>
@@ -421,4 +432,55 @@ let EditionFurtherDetails = React.createClass({
}
});
+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/signup_container.js b/js/components/signup_container.js
index b8849669..65dfe217 100644
--- a/js/components/signup_container.js
+++ b/js/components/signup_container.js
@@ -115,7 +115,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..e6bbd3af 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}/',
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/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{