@@ -255,9 +250,8 @@ let IkonotvRegisterPiece = React.createClass({
enableLocalHashing={false}
headerMessage={getLangText('Register work')}
submitMessage={getLangText('Register')}
- isFineUploaderActive={this.state.isFineUploaderActive}
+ isFineUploaderActive={true}
handleSuccess={this.handleRegisterSuccess}
- onLoggedOut={this.onLoggedOut}
location={this.props.location}/>
diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_landing.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_landing.js
new file mode 100644
index 00000000..e68b1781
--- /dev/null
+++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_landing.js
@@ -0,0 +1,84 @@
+'use strict';
+
+import React from 'react';
+
+import Button from 'react-bootstrap/lib/Button';
+import LinkContainer from 'react-router-bootstrap/lib/LinkContainer';
+
+import WhitelabelActions from '../../../../../actions/whitelabel_actions';
+import WhitelabelStore from '../../../../../stores/whitelabel_store';
+
+import { mergeOptions } from '../../../../../utils/general_utils';
+import { getLangText } from '../../../../../utils/lang_utils';
+import { setDocumentTitle } from '../../../../../utils/dom_utils';
+
+
+let LumenusLanding = React.createClass({
+
+ getInitialState() {
+ return mergeOptions(
+ WhitelabelStore.getState()
+ );
+ },
+
+ componentWillMount() {
+ setDocumentTitle('Lumenus Marketplace');
+ },
+
+ componentDidMount() {
+ WhitelabelStore.listen(this.onChange);
+ WhitelabelActions.fetchWhitelabel();
+ },
+
+ componentWillUnmount() {
+ WhitelabelStore.unlisten(this.onChange);
+ },
+
+ onChange(state) {
+ this.setState(state);
+ },
+
+ render() {
+ return (
+
+
+
+
+
+
+ {getLangText('Artwork from the Lumenus Marketplace is powered by') + ' '}
+
+
+
+
+
+
+
+
+ {getLangText('Existing ascribe user?')}
+
+
+
+
+
+
+
+ {getLangText('Do you need an account?')}
+
+
+
+
+
+
+
+
+
+ );
+ }
+});
+
+export default LumenusLanding;
diff --git a/js/components/whitelabel/wallet/components/market/market_buttons/market_acl_button_list.js b/js/components/whitelabel/wallet/components/market/market_buttons/market_acl_button_list.js
new file mode 100644
index 00000000..1dcdd4e5
--- /dev/null
+++ b/js/components/whitelabel/wallet/components/market/market_buttons/market_acl_button_list.js
@@ -0,0 +1,74 @@
+'use strict';
+
+import React from 'react';
+
+import MarketSubmitButton from './market_submit_button';
+
+import DeleteButton from '../../../../../ascribe_buttons/delete_button';
+import EmailButton from '../../../../../ascribe_buttons/acls/email_button';
+import TransferButton from '../../../../../ascribe_buttons/acls/transfer_button';
+import UnconsignButton from '../../../../../ascribe_buttons/acls/unconsign_button';
+
+import UserActions from '../../../../../../actions/user_actions';
+import UserStore from '../../../../../../stores/user_store';
+
+let MarketAclButtonList = React.createClass({
+ propTypes: {
+ availableAcls: React.PropTypes.object.isRequired,
+ className: React.PropTypes.string,
+ pieceOrEditions: React.PropTypes.array,
+ handleSuccess: React.PropTypes.func,
+ children: React.PropTypes.oneOfType([
+ React.PropTypes.arrayOf(React.PropTypes.element),
+ React.PropTypes.element
+ ])
+ },
+
+ getInitialState() {
+ return UserStore.getState();
+ },
+
+ componentDidMount() {
+ UserStore.listen(this.onChange);
+ UserActions.fetchCurrentUser();
+ },
+
+ componentWillUnmount() {
+ UserStore.unlisten(this.onChange);
+ },
+
+ onChange(state) {
+ this.setState(state);
+ },
+
+ render() {
+ let { availableAcls, className, pieceOrEditions, handleSuccess } = this.props;
+ return (
+
+
+
+
+
+ {this.props.children}
+
+ );
+ }
+});
+
+export default MarketAclButtonList;
diff --git a/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js b/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js
new file mode 100644
index 00000000..d8ef4c41
--- /dev/null
+++ b/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js
@@ -0,0 +1,160 @@
+'use strict';
+
+import React from 'react';
+import classNames from 'classnames';
+
+import MarketAdditionalDataForm from '../market_forms/market_additional_data_form';
+
+import AclFormFactory from '../../../../../ascribe_forms/acl_form_factory';
+import ConsignForm from '../../../../../ascribe_forms/form_consign';
+
+import ModalWrapper from '../../../../../ascribe_modal/modal_wrapper';
+
+import AclProxy from '../../../../../acl_proxy';
+
+import PieceActions from '../../../../../../actions/piece_actions';
+import WhitelabelActions from '../../../../../../actions/whitelabel_actions';
+import WhitelabelStore from '../../../../../../stores/whitelabel_store';
+
+import ApiUrls from '../../../../../../constants/api_urls';
+
+import { getAclFormMessage, getAclFormDataId } from '../../../../../../utils/form_utils';
+import { getLangText } from '../../../../../../utils/lang_utils';
+
+let MarketSubmitButton = React.createClass({
+ propTypes: {
+ availableAcls: React.PropTypes.object.isRequired,
+ currentUser: React.PropTypes.object,
+ editions: React.PropTypes.array.isRequired,
+ handleSuccess: React.PropTypes.func.isRequired,
+ className: React.PropTypes.string,
+ },
+
+ getInitialState() {
+ return WhitelabelStore.getState();
+ },
+
+ componentDidMount() {
+ WhitelabelStore.listen(this.onChange);
+
+ WhitelabelActions.fetchWhitelabel();
+ },
+
+ componentWillUnmount() {
+ WhitelabelStore.unlisten(this.onChange);
+ },
+
+ onChange(state) {
+ this.setState(state);
+ },
+
+ canEditionBeSubmitted(edition) {
+ if (edition && edition.extra_data && edition.other_data) {
+ const { extra_data, other_data } = edition;
+
+ if (extra_data.artist_bio && extra_data.work_description &&
+ extra_data.technology_details && extra_data.display_instructions &&
+ other_data.length > 0) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ getFormDataId() {
+ return getAclFormDataId(false, this.props.editions);
+ },
+
+ getAggregateEditionDetails() {
+ const { editions } = this.props;
+
+ // Currently, we only care if all the given editions are from the same parent piece
+ // and if they can be submitted
+ return editions.reduce((details, curEdition) => {
+ return {
+ solePieceId: details.solePieceId === curEdition.parent ? details.solePieceId : null,
+ canSubmit: details.canSubmit && this.canEditionBeSubmitted(curEdition)
+ };
+ }, {
+ solePieceId: editions.length > 0 ? editions[0].parent : null,
+ canSubmit: this.canEditionBeSubmitted(editions[0])
+ });
+ },
+
+ handleAdditionalDataSuccess(pieceId) {
+ // Fetch newly updated piece to update the views
+ PieceActions.fetchOne(pieceId);
+
+ this.refs.consignModal.show();
+ },
+
+ render() {
+ const { availableAcls, currentUser, className, editions, handleSuccess } = this.props;
+ const { whitelabel: { name: whitelabelName = 'Market', user: whitelabelAdminEmail } } = this.state;
+ const { solePieceId, canSubmit } = this.getAggregateEditionDetails();
+ const message = getAclFormMessage({
+ aclName: 'acl_consign',
+ entities: editions,
+ isPiece: false,
+ additionalMessage: getLangText('Suggested price:'),
+ senderName: currentUser.username
+ });
+
+ const triggerButton = (
+
+ );
+ const consignForm = (
+
+ );
+
+ if (solePieceId && !canSubmit) {
+ return (
+
+
+
+
+
+
+ {consignForm}
+
+
+ );
+ } else {
+ return (
+
+
+ {consignForm}
+
+
+ );
+ }
+ }
+});
+
+export default MarketSubmitButton;
diff --git a/js/components/whitelabel/wallet/components/market/market_detail/market_edition_container.js b/js/components/whitelabel/wallet/components/market/market_detail/market_edition_container.js
new file mode 100644
index 00000000..97284dbc
--- /dev/null
+++ b/js/components/whitelabel/wallet/components/market/market_detail/market_edition_container.js
@@ -0,0 +1,24 @@
+'use strict';
+
+import React from 'react';
+
+import MarketFurtherDetails from './market_further_details';
+
+import MarketAclButtonList from '../market_buttons/market_acl_button_list';
+
+import EditionContainer from '../../../../../ascribe_detail/edition_container';
+
+let MarketEditionContainer = React.createClass({
+ propTypes: EditionContainer.propTypes,
+
+ render() {
+ return (
+
+ );
+ }
+});
+
+export default MarketEditionContainer;
diff --git a/js/components/whitelabel/wallet/components/market/market_detail/market_further_details.js b/js/components/whitelabel/wallet/components/market/market_detail/market_further_details.js
new file mode 100644
index 00000000..4e1e3ee8
--- /dev/null
+++ b/js/components/whitelabel/wallet/components/market/market_detail/market_further_details.js
@@ -0,0 +1,23 @@
+'use strict';
+
+import React from 'react';
+
+import MarketAdditionalDataForm from '../market_forms/market_additional_data_form'
+
+let MarketFurtherDetails = React.createClass({
+ propTypes: {
+ pieceId: React.PropTypes.number,
+ handleSuccess: React.PropTypes.func,
+ },
+
+ render() {
+ return (
+
+ );
+ }
+});
+
+export default MarketFurtherDetails;
diff --git a/js/components/whitelabel/wallet/components/market/market_detail/market_piece_container.js b/js/components/whitelabel/wallet/components/market/market_detail/market_piece_container.js
new file mode 100644
index 00000000..d41ade56
--- /dev/null
+++ b/js/components/whitelabel/wallet/components/market/market_detail/market_piece_container.js
@@ -0,0 +1,21 @@
+'use strict';
+
+import React from 'react';
+
+import MarketFurtherDetails from './market_further_details';
+
+import PieceContainer from '../../../../../ascribe_detail/piece_container';
+
+let MarketPieceContainer = React.createClass({
+ propTypes: PieceContainer.propTypes,
+
+ render() {
+ return (
+
+ );
+ }
+});
+
+export default MarketPieceContainer;
diff --git a/js/components/whitelabel/wallet/components/market/market_forms/market_additional_data_form.js b/js/components/whitelabel/wallet/components/market/market_forms/market_additional_data_form.js
new file mode 100644
index 00000000..2a6e7add
--- /dev/null
+++ b/js/components/whitelabel/wallet/components/market/market_forms/market_additional_data_form.js
@@ -0,0 +1,242 @@
+'use strict';
+
+import React from 'react';
+
+import Form from '../../../../../ascribe_forms/form';
+import Property from '../../../../../ascribe_forms/property';
+
+import InputTextAreaToggable from '../../../../../ascribe_forms/input_textarea_toggable';
+
+import FurtherDetailsFileuploader from '../../../../../ascribe_detail/further_details_fileuploader';
+import AscribeSpinner from '../../../../../ascribe_spinner';
+
+import GlobalNotificationModel from '../../../../../../models/global_notification_model';
+import GlobalNotificationActions from '../../../../../../actions/global_notification_actions';
+
+import { formSubmissionValidation } from '../../../../../ascribe_uploader/react_s3_fine_uploader_utils';
+
+import PieceActions from '../../../../../../actions/piece_actions';
+import PieceStore from '../../../../../../stores/piece_store';
+
+import ApiUrls from '../../../../../../constants/api_urls';
+import AppConstants from '../../../../../../constants/application_constants';
+
+import requests from '../../../../../../utils/requests';
+import { mergeOptions } from '../../../../../../utils/general_utils';
+import { getLangText } from '../../../../../../utils/lang_utils';
+
+
+let MarketAdditionalDataForm = React.createClass({
+ propTypes: {
+ pieceId: React.PropTypes.oneOfType([
+ React.PropTypes.number,
+ React.PropTypes.string
+ ]),
+ editable: React.PropTypes.bool,
+ isInline: React.PropTypes.bool,
+ showHeading: React.PropTypes.bool,
+ showNotification: React.PropTypes.bool,
+ submitLabel: React.PropTypes.string,
+ handleSuccess: React.PropTypes.func
+ },
+
+ getDefaultProps() {
+ return {
+ editable: true,
+ submitLabel: getLangText('Register work')
+ };
+ },
+
+ getInitialState() {
+ const pieceStore = PieceStore.getState();
+
+ return mergeOptions(
+ pieceStore,
+ {
+ // Allow the form to be submitted if there's already an additional image uploaded
+ isUploadReady: this.isUploadReadyOnChange(pieceStore.piece),
+ forceUpdateKey: 0
+ });
+ },
+
+ componentDidMount() {
+ PieceStore.listen(this.onChange);
+
+ if (this.props.pieceId) {
+ PieceActions.fetchOne(this.props.pieceId);
+ }
+ },
+
+ componentWillUnmount() {
+ PieceStore.unlisten(this.onChange);
+ },
+
+ onChange(state) {
+ Object.assign({}, state, {
+ // Allow the form to be submitted if the updated piece already has an additional image uploaded
+ isUploadReady: this.isUploadReadyOnChange(state.piece),
+
+ /**
+ * Increment the forceUpdateKey to force the form to rerender on each change
+ *
+ * THIS IS A HACK TO MAKE SURE THE FORM ALWAYS DISPLAYS THE MOST RECENT STATE
+ * BECAUSE SOME OF OUR FORM ELEMENTS DON'T UPDATE FROM PROP CHANGES (ie.
+ * InputTextAreaToggable).
+ */
+ forceUpdateKey: this.state.forceUpdateKey + 1
+ });
+
+ this.setState(state);
+ },
+
+ getFormData() {
+ let extradata = {};
+ let formRefs = this.refs.form.refs;
+
+ // Put additional fields in extra data object
+ Object
+ .keys(formRefs)
+ .forEach((fieldName) => {
+ extradata[fieldName] = formRefs[fieldName].state.value;
+ });
+
+ return {
+ extradata: extradata,
+ piece_id: this.state.piece.id
+ };
+ },
+
+ isUploadReadyOnChange(piece) {
+ return piece && piece.other_data && piece.other_data.length > 0;
+ },
+
+ handleSuccessWithNotification() {
+ if (typeof this.props.handleSuccess === 'function') {
+ this.props.handleSuccess();
+ }
+
+ let notification = new GlobalNotificationModel(getLangText('Further details successfully updated'), 'success', 10000);
+ GlobalNotificationActions.appendGlobalNotification(notification);
+ },
+
+ uploadStarted() {
+ this.setState({
+ isUploadReady: false
+ });
+ },
+
+ setIsUploadReady(isReady) {
+ this.setState({
+ isUploadReady: isReady
+ });
+ },
+
+ render() {
+ const { editable, isInline, handleSuccess, showHeading, showNotification, submitLabel } = this.props;
+ const { piece } = this.state;
+ let buttons, heading;
+
+ let spinner =
;
+
+ if (!isInline) {
+ buttons = (
+
+ );
+
+ spinner = (
+
+ );
+
+ heading = showHeading ? (
+
+
+ {getLangText('Provide additional details')}
+
+
+ ) : null;
+ }
+
+ if (piece && piece.id) {
+ return (
+
+ );
+ } else {
+ return (
+
+ {spinner}
+
+ );
+ }
+ }
+});
+
+export default MarketAdditionalDataForm;
diff --git a/js/components/whitelabel/wallet/components/market/market_piece_list.js b/js/components/whitelabel/wallet/components/market/market_piece_list.js
new file mode 100644
index 00000000..1c74e6de
--- /dev/null
+++ b/js/components/whitelabel/wallet/components/market/market_piece_list.js
@@ -0,0 +1,90 @@
+'use strict';
+
+import React from 'react';
+
+import MarketAclButtonList from './market_buttons/market_acl_button_list';
+
+import PieceList from '../../../../piece_list';
+
+import UserActions from '../../../../../actions/user_actions';
+import UserStore from '../../../../../stores/user_store';
+import WhitelabelActions from '../../../../../actions/whitelabel_actions';
+import WhitelabelStore from '../../../../../stores/whitelabel_store';
+
+import { setDocumentTitle } from '../../../../../utils/dom_utils';
+import { mergeOptions } from '../../../../../utils/general_utils';
+import { getLangText } from '../../../../../utils/lang_utils';
+
+let MarketPieceList = React.createClass({
+ propTypes: {
+ customThumbnailPlaceholder: React.PropTypes.func,
+ location: React.PropTypes.object
+ },
+
+ getInitialState() {
+ return mergeOptions(
+ UserStore.getState(),
+ WhitelabelStore.getState()
+ );
+ },
+
+ componentWillMount() {
+ setDocumentTitle(getLangText('Collection'));
+ },
+
+ componentDidMount() {
+ UserStore.listen(this.onChange);
+ WhitelabelStore.listen(this.onChange);
+
+ UserActions.fetchCurrentUser();
+ WhitelabelActions.fetchWhitelabel();
+ },
+
+ componentWillUnmount() {
+ UserStore.unlisten(this.onChange);
+ WhitelabelStore.unlisten(this.onChange);
+ },
+
+ onChange(state) {
+ this.setState(state);
+ },
+
+ render() {
+ const { customThumbnailPlaceholder, location } = this.props;
+ const {
+ currentUser: { email: userEmail },
+ whitelabel: {
+ name: whitelabelName = 'Market',
+ user: whitelabelAdminEmail
+ } } = this.state;
+
+ let filterParams = null;
+ let canLoadPieceList = false;
+
+ if (userEmail && whitelabelAdminEmail) {
+ canLoadPieceList = true;
+ const isUserAdmin = userEmail === whitelabelAdminEmail;
+
+ filterParams = [{
+ label: getLangText('Show works I can'),
+ items: [{
+ key: isUserAdmin ? 'acl_transfer' : 'acl_consign',
+ label: getLangText(isUserAdmin ? 'transfer' : 'consign to %s', whitelabelName),
+ defaultValue: true
+ }]
+ }];
+ }
+
+ return (
+
+ );
+ }
+});
+
+export default MarketPieceList;
diff --git a/js/components/whitelabel/wallet/components/market/market_register_piece.js b/js/components/whitelabel/wallet/components/market/market_register_piece.js
new file mode 100644
index 00000000..eed17477
--- /dev/null
+++ b/js/components/whitelabel/wallet/components/market/market_register_piece.js
@@ -0,0 +1,174 @@
+'use strict';
+
+import React from 'react';
+import { History } from 'react-router';
+
+import Col from 'react-bootstrap/lib/Col';
+import Row from 'react-bootstrap/lib/Row';
+
+import MarketAdditionalDataForm from './market_forms/market_additional_data_form';
+
+import Property from '../../../../ascribe_forms/property';
+import RegisterPieceForm from '../../../../ascribe_forms/form_register_piece';
+
+import PieceActions from '../../../../../actions/piece_actions';
+import PieceListStore from '../../../../../stores/piece_list_store';
+import PieceListActions from '../../../../../actions/piece_list_actions';
+import UserStore from '../../../../../stores/user_store';
+import UserActions from '../../../../../actions/user_actions';
+import WhitelabelActions from '../../../../../actions/whitelabel_actions';
+import WhitelabelStore from '../../../../../stores/whitelabel_store';
+
+import SlidesContainer from '../../../../ascribe_slides_container/slides_container';
+
+import { getLangText } from '../../../../../utils/lang_utils';
+import { setDocumentTitle } from '../../../../../utils/dom_utils';
+import { mergeOptions } from '../../../../../utils/general_utils';
+
+let MarketRegisterPiece = React.createClass({
+ propTypes: {
+ location: React.PropTypes.object
+ },
+
+ mixins: [History],
+
+ getInitialState(){
+ return mergeOptions(
+ PieceListStore.getState(),
+ UserStore.getState(),
+ WhitelabelStore.getState(),
+ {
+ step: 0
+ });
+ },
+
+ componentDidMount() {
+ PieceListStore.listen(this.onChange);
+ UserStore.listen(this.onChange);
+ WhitelabelStore.listen(this.onChange);
+
+ UserActions.fetchCurrentUser();
+ WhitelabelActions.fetchWhitelabel();
+
+ // Reset the piece store to make sure that we don't display old data
+ // if the user repeatedly registers works
+ PieceActions.updatePiece({});
+ },
+
+ componentWillUnmount() {
+ PieceListStore.unlisten(this.onChange);
+ UserStore.unlisten(this.onChange);
+ WhitelabelStore.unlisten(this.onChange);
+ },
+
+ onChange(state) {
+ this.setState(state);
+ },
+
+ handleRegisterSuccess(response) {
+ this.refreshPieceList();
+
+ // Use the response's piece for the next step if available
+ let pieceId = null;
+ if (response && response.piece) {
+ pieceId = response.piece.id;
+ PieceActions.updatePiece(response.piece);
+ }
+
+ this.incrementStep();
+ this.refs.slidesContainer.nextSlide({ piece_id: pieceId });
+ },
+
+ handleAdditionalDataSuccess() {
+ this.refreshPieceList();
+
+ this.history.pushState(null, '/collection');
+ },
+
+ // We need to increase the step to lock the forms that are already filled out
+ incrementStep() {
+ this.setState({
+ step: this.state.step + 1
+ });
+ },
+
+ getPieceFromQueryParam() {
+ const queryParams = this.props.location.query;
+
+ // Since every step of this register process is atomic,
+ // we may need to enter the process at step 1 or 2.
+ // If this is the case, we'll need the piece number to complete submission.
+ // It is encoded in the URL as a queryParam and we're checking for it here.
+ return queryParams && queryParams.piece_id;
+ },
+
+ refreshPieceList() {
+ PieceListActions.fetchPieceList(
+ this.state.page,
+ this.state.pageSize,
+ this.state.searchTerm,
+ this.state.orderBy,
+ this.state.orderAsc,
+ this.state.filterBy
+ );
+ },
+
+ render() {
+ const {
+ isFineUploaderActive,
+ step,
+ whitelabel: {
+ name: whitelabelName = 'Market'
+ } } = this.state;
+
+ setDocumentTitle(getLangText('Register a new piece'));
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+});
+
+export default MarketRegisterPiece;
diff --git a/js/components/whitelabel/wallet/constants/wallet_api_urls.js b/js/components/whitelabel/wallet/constants/wallet_api_urls.js
index 2cdc0054..8ad2eb81 100644
--- a/js/components/whitelabel/wallet/constants/wallet_api_urls.js
+++ b/js/components/whitelabel/wallet/constants/wallet_api_urls.js
@@ -4,22 +4,30 @@ import walletConstants from './wallet_application_constants';
// gets subdomain as a parameter
function getWalletApiUrls(subdomain) {
- if (subdomain === 'cyland'){
+ if (subdomain === 'cyland') {
return {
'pieces_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/',
'piece': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/',
'piece_extradata': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/extradata/',
'user': walletConstants.walletApiEndpoint + subdomain + '/users/'
};
- }
- else if (subdomain === 'ikonotv'){
+ } else if (subdomain === 'ikonotv') {
return {
'pieces_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/',
'piece': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/',
'user': walletConstants.walletApiEndpoint + subdomain + '/users/'
};
+ } else if (subdomain === 'lumenus' || subdomain === '23vivi') {
+ return {
+ 'editions_list': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/pieces/${piece_id}/editions/',
+ 'edition': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/editions/${bitcoin_id}/',
+ 'pieces_list': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/pieces/',
+ 'piece': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/pieces/${piece_id}/',
+ 'piece_extradata': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/pieces/${piece_id}/extradata/',
+ 'user': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/users/'
+ };
}
return {};
}
-export default getWalletApiUrls;
\ No newline at end of file
+export default getWalletApiUrls;
diff --git a/js/components/whitelabel/wallet/wallet_app.js b/js/components/whitelabel/wallet/wallet_app.js
index 5056716a..c2810fd0 100644
--- a/js/components/whitelabel/wallet/wallet_app.js
+++ b/js/components/whitelabel/wallet/wallet_app.js
@@ -32,7 +32,7 @@ let WalletApp = React.createClass({
// if the path of the current activeRoute is not defined, then this is the IndexRoute
if ((!path || history.isActive('/login') || history.isActive('/signup') || history.isActive('/contract_notifications'))
- && (['ikonotv']).indexOf(subdomain) > -1) {
+ && (['cyland', 'ikonotv', 'lumenus', '23vivi']).indexOf(subdomain) > -1) {
header = (
);
} else {
header =
;
diff --git a/js/components/whitelabel/wallet/wallet_routes.js b/js/components/whitelabel/wallet/wallet_routes.js
index 8e4d5197..0a4e3a58 100644
--- a/js/components/whitelabel/wallet/wallet_routes.js
+++ b/js/components/whitelabel/wallet/wallet_routes.js
@@ -16,6 +16,8 @@ import SettingsContainer from '../../../components/ascribe_settings/settings_con
import ContractSettings from '../../../components/ascribe_settings/contract_settings';
import ErrorNotFoundPage from '../../../components/error_not_found_page';
+import CCRegisterPiece from './components/cc/cc_register_piece';
+
import CylandLanding from './components/cyland/cyland_landing';
import CylandPieceContainer from './components/cyland/cyland_detail/cyland_piece_container';
import CylandRegisterPiece from './components/cyland/cyland_register_piece';
@@ -23,12 +25,20 @@ import CylandPieceList from './components/cyland/cyland_piece_list';
import IkonotvLanding from './components/ikonotv/ikonotv_landing';
import IkonotvPieceList from './components/ikonotv/ikonotv_piece_list';
-import ContractAgreementForm from '../../../components/ascribe_forms/form_contract_agreement';
+import SendContractAgreementForm from '../../../components/ascribe_forms/form_send_contract_agreement';
import IkonotvRegisterPiece from './components/ikonotv/ikonotv_register_piece';
import IkonotvPieceContainer from './components/ikonotv/ikonotv_detail/ikonotv_piece_container';
import IkonotvContractNotifications from './components/ikonotv/ikonotv_contract_notifications';
-import CCRegisterPiece from './components/cc/cc_register_piece';
+import MarketPieceList from './components/market/market_piece_list';
+import MarketRegisterPiece from './components/market/market_register_piece';
+import MarketPieceContainer from './components/market/market_detail/market_piece_container';
+import MarketEditionContainer from './components/market/market_detail/market_edition_container';
+
+import LumenusLanding from './components/lumenus/lumenus_landing';
+
+import Vivi23Landing from './components/23vivi/23vivi_landing';
+import Vivi23PieceList from './components/23vivi/23vivi_piece_list';
import AuthProxyHandler from '../../../components/ascribe_routes/proxy_routes/auth_proxy_handler';
@@ -128,7 +138,7 @@ let ROUTES = {
component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(ContractSettings)}/>
+ ),
+ 'lumenus': (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ),
+ '23vivi': (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
)
};
-
function getRoutes(commonRoutes, subdomain) {
if(subdomain in ROUTES) {
return ROUTES[subdomain];
@@ -160,5 +240,4 @@ function getRoutes(commonRoutes, subdomain) {
}
}
-
export default getRoutes;
diff --git a/js/constants/api_urls.js b/js/constants/api_urls.js
index a07f29b1..e7f11141 100644
--- a/js/constants/api_urls.js
+++ b/js/constants/api_urls.js
@@ -72,6 +72,9 @@ let ApiUrls = {
'users_username': AppConstants.apiEndpoint + 'users/username/',
'users_profile': AppConstants.apiEndpoint + 'users/profile/',
'wallet_settings': AppConstants.apiEndpoint + 'users/wallet_settings/',
+ 'webhook': AppConstants.apiEndpoint + 'webhooks/${webhook_id}/',
+ 'webhooks': AppConstants.apiEndpoint + 'webhooks/',
+ 'webhooks_events': AppConstants.apiEndpoint + 'webhooks/events/',
'whitelabel_settings': AppConstants.apiEndpoint + 'whitelabel/settings/${subdomain}/',
'delete_s3_file': AppConstants.serverUrl + 's3/delete/',
'prize_list': AppConstants.apiEndpoint + 'prize/'
diff --git a/js/constants/application_constants.js b/js/constants/application_constants.js
index 79d00747..1a80c9b1 100644
--- a/js/constants/application_constants.js
+++ b/js/constants/application_constants.js
@@ -51,6 +51,20 @@ const constants = {
'permissions': ['register', 'edit', 'share', 'del_from_collection'],
'type': 'wallet'
},
+ {
+ 'subdomain': 'lumenus',
+ 'name': 'Lumenus',
+ 'logo': 'https://s3-us-west-2.amazonaws.com/ascribe0/whitelabel/lumenus/lumenus-logo.png',
+ 'permissions': ['register', 'edit', 'share', 'del_from_collection'],
+ 'type': 'wallet'
+ },
+ {
+ 'subdomain': '23vivi',
+ 'name': '23vivi',
+ 'logo': 'https://s3-us-west-2.amazonaws.com/ascribe0/whitelabel/23vivi/23vivi-logo.png',
+ 'permissions': ['register', 'edit', 'share', 'del_from_collection'],
+ 'type': 'wallet'
+ },
{
'subdomain': 'portfolioreview',
'name': 'Portfolio Review',
@@ -124,7 +138,12 @@ const constants = {
},
'twitter': {
'sdkUrl': 'https://platform.twitter.com/widgets.js'
- }
+ },
+
+ 'errorMessagesToIgnore': [
+ 'Authentication credentials were not provided.',
+ 'Informations d\'authentification non fournies.'
+ ]
};
export default constants;
diff --git a/js/mixins/react_error.js b/js/mixins/react_error.js
new file mode 100644
index 00000000..14f33a61
--- /dev/null
+++ b/js/mixins/react_error.js
@@ -0,0 +1,16 @@
+'use strict';
+
+import invariant from 'invariant';
+
+const ReactError = {
+ throws(err) {
+ if(!err.handler) {
+ invariant(err.handler, 'Error thrown to ReactError did not have a `handler` function');
+ console.logGlobal('Error thrown to ReactError did not have a `handler` function');
+ } else {
+ err.handler(this, err);
+ }
+ }
+};
+
+export default ReactError;
diff --git a/js/models/errors.js b/js/models/errors.js
new file mode 100644
index 00000000..4573afe4
--- /dev/null
+++ b/js/models/errors.js
@@ -0,0 +1,31 @@
+'use strict';
+
+import React from 'react';
+
+import ErrorNotFoundPage from '../components/error_not_found_page';
+
+
+export class ResourceNotFoundError extends Error {
+ constructor(message) {
+ super(message);
+ this.name = this.constructor.name;
+ this.message = message;
+
+ // `captureStackTrace` might not be available in IE:
+ // - http://stackoverflow.com/a/8460753/1263876
+ if(Error.captureStackTrace) {
+ Error.captureStackTrace(this, this.constructor.name);
+ }
+ }
+
+ handler(component, err) {
+ const monkeyPatchedKey = `_${this.name}MonkeyPatched`;
+
+ if(!component.state[monkeyPatchedKey]) {
+ component.render = () =>
;
+ component.setState({
+ [monkeyPatchedKey]: true
+ });
+ }
+ }
+}
diff --git a/js/sources/webhook_source.js b/js/sources/webhook_source.js
new file mode 100644
index 00000000..5351c89c
--- /dev/null
+++ b/js/sources/webhook_source.js
@@ -0,0 +1,46 @@
+'use strict';
+
+import requests from '../utils/requests';
+
+import WebhookActions from '../actions/webhook_actions';
+
+
+const WebhookSource = {
+ lookupWebhooks: {
+ remote() {
+ return requests.get('webhooks');
+ },
+ local(state) {
+ return state.webhooks && !Object.keys(state.webhooks).length ? state : {};
+ },
+ success: WebhookActions.successFetchWebhooks,
+ error: WebhookActions.errorWebhooks,
+ shouldFetch(state) {
+ return state.webhookMeta.invalidateCache || state.webhooks && !Object.keys(state.webhooks).length;
+ }
+ },
+
+ lookupWebhookEvents: {
+ remote() {
+ return requests.get('webhooks_events');
+ },
+ local(state) {
+ return state.webhookEvents && !Object.keys(state.webhookEvents).length ? state : {};
+ },
+ success: WebhookActions.successFetchWebhookEvents,
+ error: WebhookActions.errorWebhookEvents,
+ shouldFetch(state) {
+ return state.webhookEventsMeta.invalidateCache || state.webhookEvents && !Object.keys(state.webhookEvents).length;
+ }
+ },
+
+ performRemoveWebhook: {
+ remote(state) {
+ return requests.delete('webhook', {'webhook_id': state.webhookMeta.idToDelete });
+ },
+ success: WebhookActions.successRemoveWebhook,
+ error: WebhookActions.errorWebhooks
+ }
+};
+
+export default WebhookSource;
\ No newline at end of file
diff --git a/js/stores/edition_list_store.js b/js/stores/edition_list_store.js
index 4ccada4e..107f9af4 100644
--- a/js/stores/edition_list_store.js
+++ b/js/stores/edition_list_store.js
@@ -60,7 +60,7 @@ class EditionListStore {
* We often just have to refresh the edition list for a certain pieceId,
* this method provides exactly that functionality without any side effects
*/
- onRefreshEditionList({pieceId, filterBy}) {
+ onRefreshEditionList({pieceId, filterBy = {}}) {
// It may happen that the user enters the site logged in already
// through /editions
// If he then tries to delete a piece/edition and this method is called,
diff --git a/js/stores/edition_store.js b/js/stores/edition_store.js
index 14ee4fee..22e78d23 100644
--- a/js/stores/edition_store.js
+++ b/js/stores/edition_store.js
@@ -7,11 +7,17 @@ import EditionActions from '../actions/edition_actions';
class EditionStore {
constructor() {
this.edition = {};
+ this.editionError = null;
this.bindActions(EditionActions);
}
onUpdateEdition(edition) {
this.edition = edition;
+ this.editionError = null;
+ }
+
+ onEditionFailed(error) {
+ this.editionError = error;
}
}
diff --git a/js/stores/piece_store.js b/js/stores/piece_store.js
index ccef50b1..3b04736b 100644
--- a/js/stores/piece_store.js
+++ b/js/stores/piece_store.js
@@ -7,11 +7,13 @@ import PieceActions from '../actions/piece_actions';
class PieceStore {
constructor() {
this.piece = {};
+ this.pieceError = null;
this.bindActions(PieceActions);
}
onUpdatePiece(piece) {
this.piece = piece;
+ this.pieceError = null;
}
onUpdateProperty({key, value}) {
@@ -21,6 +23,10 @@ class PieceStore {
throw new Error('There is no piece defined in PieceStore or the piece object does not have the property you\'re looking for.');
}
}
+
+ onPieceFailed(err) {
+ this.pieceError = err;
+ }
}
export default alt.createStore(PieceStore, 'PieceStore');
diff --git a/js/stores/webhook_store.js b/js/stores/webhook_store.js
new file mode 100644
index 00000000..7dfcc61d
--- /dev/null
+++ b/js/stores/webhook_store.js
@@ -0,0 +1,88 @@
+'use strict';
+
+import { alt } from '../alt';
+import WebhookActions from '../actions/webhook_actions';
+
+import WebhookSource from '../sources/webhook_source';
+
+class WebhookStore {
+ constructor() {
+ this.webhooks = [];
+ this.webhookEvents = [];
+ this.webhookMeta = {
+ invalidateCache: false,
+ err: null,
+ idToDelete: null
+ };
+ this.webhookEventsMeta = {
+ invalidateCache: false,
+ err: null
+ };
+
+ this.bindActions(WebhookActions);
+ this.registerAsync(WebhookSource);
+ }
+
+ onFetchWebhooks(invalidateCache) {
+ this.webhookMeta.invalidateCache = invalidateCache;
+ this.getInstance().lookupWebhooks();
+ }
+
+ onSuccessFetchWebhooks({ webhooks }) {
+ this.webhookMeta.invalidateCache = false;
+ this.webhookMeta.err = null;
+ this.webhooks = webhooks;
+
+ this.webhookEventsMeta.invalidateCache = true;
+ this.getInstance().lookupWebhookEvents();
+ }
+
+ onFetchWebhookEvents(invalidateCache) {
+ this.webhookEventsMeta.invalidateCache = invalidateCache;
+ this.getInstance().lookupWebhookEvents();
+ }
+
+ onSuccessFetchWebhookEvents({ events }) {
+ this.webhookEventsMeta.invalidateCache = false;
+ this.webhookEventsMeta.err = null;
+
+ // remove all events that have already been used.
+ const usedEvents = this.webhooks
+ .reduce((tempUsedEvents, webhook) => {
+ tempUsedEvents.push(webhook.event.split('.')[0]);
+ return tempUsedEvents;
+ }, []);
+
+ this.webhookEvents = events.filter((event) => {
+ return usedEvents.indexOf(event) === -1;
+ });
+ }
+
+ onRemoveWebhook(id) {
+ this.webhookMeta.invalidateCache = true;
+ this.webhookMeta.idToDelete = id;
+
+ if(!this.getInstance().isLoading()) {
+ this.getInstance().performRemoveWebhook();
+ }
+ }
+
+ onSuccessRemoveWebhook() {
+ this.webhookMeta.idToDelete = null;
+ if(!this.getInstance().isLoading()) {
+ this.getInstance().lookupWebhooks();
+ }
+ }
+
+ onErrorWebhooks(err) {
+ console.logGlobal(err);
+ this.webhookMeta.err = err;
+ }
+
+ onErrorWebhookEvents(err) {
+ console.logGlobal(err);
+ this.webhookEventsMeta.err = err;
+ }
+}
+
+export default alt.createStore(WebhookStore, 'WebhookStore');
diff --git a/js/utils/acl_utils.js b/js/utils/acl_utils.js
index fc3987c1..dd39a380 100644
--- a/js/utils/acl_utils.js
+++ b/js/utils/acl_utils.js
@@ -4,7 +4,7 @@ import { sanitize, intersectLists } from './general_utils';
export function getAvailableAcls(editions, filterFn) {
let availableAcls = [];
- if (!editions || editions.constructor !== Array){
+ if (!editions || editions.constructor !== Array) {
return [];
}
// if you copy a javascript array of objects using slice, then
@@ -33,23 +33,23 @@ export function getAvailableAcls(editions, filterFn) {
});
// If no edition has been selected, availableActions is empty
- // If only one edition has been selected, their actions are available
- // If more than one editions have been selected, their acl properties are intersected
- if(editionsCopy.length >= 1) {
+ // If only one edition has been selected, its actions are available
+ // If more than one editions have been selected, intersect all their acl properties
+ if (editionsCopy.length >= 1) {
availableAcls = editionsCopy[0].acl;
- }
- if(editionsCopy.length >= 2) {
- for(let i = 1; i < editionsCopy.length; i++) {
- availableAcls = intersectLists(availableAcls, editionsCopy[i].acl);
+
+ if (editionsCopy.length >= 2) {
+ for (let i = 1; i < editionsCopy.length; i++) {
+ availableAcls = intersectLists(availableAcls, editionsCopy[i].acl);
+ }
}
}
// convert acls back to key-value object
let availableAclsObj = {};
- for(let i = 0; i < availableAcls.length; i++) {
+ for (let i = 0; i < availableAcls.length; i++) {
availableAclsObj[availableAcls[i]] = true;
}
-
return availableAclsObj;
-}
\ No newline at end of file
+}
diff --git a/js/utils/error_utils.js b/js/utils/error_utils.js
index 4e9de6e2..753bbf61 100644
--- a/js/utils/error_utils.js
+++ b/js/utils/error_utils.js
@@ -13,8 +13,8 @@ import AppConstants from '../constants/application_constants';
* @param {boolean} ignoreSentry Defines whether or not the error should be submitted to Sentry
* @param {string} comment Will also be submitted to Sentry, but will not be logged
*/
-function logGlobal(error, ignoreSentry, comment) {
-
+function logGlobal(error, ignoreSentry = AppConstants.errorMessagesToIgnore.indexOf(error.message) > -1,
+ comment) {
console.error(error);
if(!ignoreSentry) {
diff --git a/js/utils/form_utils.js b/js/utils/form_utils.js
index c15eb067..8d12a8c1 100644
--- a/js/utils/form_utils.js
+++ b/js/utils/form_utils.js
@@ -2,6 +2,8 @@
import { getLangText } from './lang_utils';
+import AppConstants from '../constants/application_constants';
+
/**
* Get the data ids of the given piece or editions.
* @param {boolean} isPiece Is the given entities parameter a piece? (False: array of editions)
@@ -70,6 +72,10 @@ export function getAclFormMessage(options) {
throw new Error('Your specified aclName did not match a an acl class.');
}
+ if (options.additionalMessage) {
+ message += '\n\n' + options.additionalMessage;
+ }
+
if (options.senderName) {
message += '\n\n';
message += getLangText('Truly yours,');
diff --git a/js/utils/general_utils.js b/js/utils/general_utils.js
index e717fa75..e81a806d 100644
--- a/js/utils/general_utils.js
+++ b/js/utils/general_utils.js
@@ -1,5 +1,11 @@
'use strict';
+/**
+ * Checks shallow equality
+ * Re-export of shallow from shallow-equals
+ */
+export { default as isShallowEqual } from 'shallow-equals';
+
/**
* Takes an object and returns a shallow copy without any keys
* that fail the passed in filter function.
@@ -109,7 +115,7 @@ function _doesObjectListHaveDuplicates(l) {
export function mergeOptions(...l) {
// If the objects submitted in the list have duplicates,in their key names,
// abort the merge and tell the function's user to check his objects.
- if(_doesObjectListHaveDuplicates(l)) {
+ if (_doesObjectListHaveDuplicates(l)) {
throw new Error('The objects you submitted for merging have duplicates. Merge aborted.');
}
diff --git a/js/utils/inject_utils.js b/js/utils/inject_utils.js
index 174ac8b6..e9430a5e 100644
--- a/js/utils/inject_utils.js
+++ b/js/utils/inject_utils.js
@@ -12,16 +12,16 @@ let mapTag = {
css: 'link'
};
+let tags = {};
+
function injectTag(tag, src) {
- return Q.Promise((resolve, reject) => {
- if (isPresent(tag, src)) {
- resolve();
- } else {
+ if(!tags[src]) {
+ tags[src] = Q.Promise((resolve, reject) => {
let attr = mapAttr[tag];
let element = document.createElement(tag);
if (tag === 'script') {
- element.onload = () => resolve();
- element.onerror = () => reject();
+ element.onload = resolve;
+ element.onerror = reject;
} else {
resolve();
}
@@ -30,14 +30,10 @@ function injectTag(tag, src) {
if (tag === 'link') {
element.rel = 'stylesheet';
}
- }
- });
-}
+ });
+ }
-function isPresent(tag, src) {
- let attr = mapAttr[tag];
- let query = `head > ${tag}[${attr}="${src}"]`;
- return document.querySelector(query);
+ return tags[src];
}
function injectStylesheet(src) {
@@ -65,7 +61,6 @@ export const InjectInHeadUtils = {
* you don't want to embed everything inside the build file.
*/
- isPresent,
injectStylesheet,
injectScript,
inject
diff --git a/js/utils/regex_utils.js b/js/utils/regex_utils.js
new file mode 100644
index 00000000..af948b2b
--- /dev/null
+++ b/js/utils/regex_utils.js
@@ -0,0 +1,7 @@
+'use strict'
+
+export function isEmail(string) {
+ // This is a bit of a weak test for an email, but you really can't win them all
+ // http://stackoverflow.com/questions/201323/using-a-regular-expression-to-validate-an-email-address
+ return !!string && string.match(/.*@.*\..*/);
+}
diff --git a/js/utils/requests.js b/js/utils/requests.js
index a7300634..9195661d 100644
--- a/js/utils/requests.js
+++ b/js/utils/requests.js
@@ -30,6 +30,15 @@ class Requests {
reject(error);
} else if(body && body.detail) {
reject(new Error(body.detail));
+ } else if(!body.success) {
+ let error = new Error('Client Request Error');
+ error.json = {
+ status: response.status,
+ statusText: response.statusText,
+ type: response.type,
+ url: response.url
+ };
+ reject(error);
} else {
resolve(body);
}
@@ -100,8 +109,7 @@ class Requests {
return newUrl;
}
- request(verb, url, options) {
- options = options || {};
+ request(verb, url, options = {}) {
let merged = Object.assign({}, this.httpOptions, options);
let csrftoken = getCookie(AppConstants.csrftoken);
if (csrftoken) {
@@ -129,13 +137,10 @@ class Requests {
}
_putOrPost(url, paramsAndBody, method) {
- let paramsCopy = Object.assign({}, paramsAndBody);
let params = omitFromObject(paramsAndBody, ['body']);
let newUrl = this.prepareUrl(url, params);
- let body = null;
- if (paramsCopy && paramsCopy.body) {
- body = JSON.stringify(paramsCopy.body);
- }
+ let body = paramsAndBody && paramsAndBody.body ? JSON.stringify(paramsAndBody.body)
+ : null;
return this.request(method, newUrl, { body });
}
diff --git a/package.json b/package.json
index 63c6d1e0..be5c1202 100644
--- a/package.json
+++ b/package.json
@@ -66,7 +66,7 @@
"gulp-uglify": "^1.2.0",
"gulp-util": "^3.0.4",
"harmonize": "^1.4.2",
- "history": "^1.11.1",
+ "history": "^1.13.1",
"invariant": "^2.1.1",
"isomorphic-fetch": "^2.0.2",
"jest-cli": "^0.4.0",
@@ -80,11 +80,12 @@
"react": "0.13.2",
"react-bootstrap": "0.25.1",
"react-datepicker": "^0.12.0",
- "react-router": "^1.0.0-rc3",
+ "react-router": "1.0.0",
"react-router-bootstrap": "^0.19.0",
"react-star-rating": "~1.3.2",
"react-textarea-autosize": "^2.5.2",
"reactify": "^1.1.0",
+ "shallow-equals": "0.0.0",
"shmui": "^0.1.0",
"spark-md5": "~1.0.0",
"uglifyjs": "^2.4.10",
diff --git a/sass/ascribe-fonts/ascribe-fonts.scss b/sass/ascribe-fonts/ascribe-fonts.scss
index 11b42851..42ce729a 100644
--- a/sass/ascribe-fonts/ascribe-fonts.scss
+++ b/sass/ascribe-fonts/ascribe-fonts.scss
@@ -89,26 +89,4 @@
.btn-glyph-ascribe {
font-size: 18px;
padding: 4px 12px 0 10px
-}
-
-.ascribe-logo-circle {
- border: 6px solid #F6F6F6;
- border-radius: 10em;
- position: relative;
- top: 10%;
- left: 10%;
-
- display: block;
- width: 80%;
- height: 80%;
-
- > span {
- color: #F6F6F6;
- position: absolute;
- top: -.29em;
- left: .16em;
-
- font-size: 5em;
- font-weight: normal;
- }
}
\ No newline at end of file
diff --git a/sass/ascribe_accordion_list.scss b/sass/ascribe_accordion_list.scss
index c0b81096..791743fc 100644
--- a/sass/ascribe_accordion_list.scss
+++ b/sass/ascribe_accordion_list.scss
@@ -60,6 +60,34 @@ $ascribe-accordion-list-item-height: 100px;
background-size: cover;
}
+ .ascribe-logo-circle {
+ border: 6px solid #F6F6F6;
+ border-radius: 10em;
+ position: relative;
+ top: 10%;
+ left: 10%;
+
+ display: block;
+ width: 80%;
+ height: 80%;
+
+ > span {
+ color: #F6F6F6;
+ position: absolute;
+ top: -.29em;
+ left: .16em;
+
+ font-size: 5em;
+ font-weight: normal;
+ }
+ }
+
+ .ascribe-thumbnail-placeholder {
+ color: #F6F6F6;
+ font-size: 5em;
+ font-weight: normal;
+ }
+
//&::before {
// content: ' ';
// display: inline-block;
@@ -211,10 +239,6 @@ $ascribe-accordion-list-item-height: 100px;
-ms-user-select: none;
-webkit-user-select: none;
- &:hover {
- color: $ascribe-dark-blue;
- }
-
.glyphicon {
font-size: .8em;
top: 1px !important;
diff --git a/sass/ascribe_acl_information.scss b/sass/ascribe_acl_information.scss
index 063c8ae6..5a4708f0 100644
--- a/sass/ascribe_acl_information.scss
+++ b/sass/ascribe_acl_information.scss
@@ -22,4 +22,4 @@
.example {
color: #616161;
}
-}
\ No newline at end of file
+}
diff --git a/sass/ascribe_custom_style.scss b/sass/ascribe_custom_style.scss
index 3adbd3ca..96b97783 100644
--- a/sass/ascribe_custom_style.scss
+++ b/sass/ascribe_custom_style.scss
@@ -68,10 +68,15 @@ hr {
.dropdown-menu {
background-color: $ascribe--nav-bg-color;
}
+ .navbar-nav > li > .dropdown-menu {
+ padding: 0;
+ }
.dropdown-menu > li > a {
color: $ascribe--nav-fg-prim-color;
font-weight: $ascribe--font-weight-light;
+ padding-bottom: 9px;
+ padding-top: 9px;
}
.dropdown-menu > li > a:hover,
@@ -79,6 +84,10 @@ hr {
background-color: rgba($ascribe--button-default-color, .05);
}
+ .dropdown-menu > .divider {
+ margin: 0;
+ }
+
.notification-menu {
.dropdown-menu {
background-color: white;
diff --git a/sass/ascribe_notification_list.scss b/sass/ascribe_notification_list.scss
index a09f7049..b5f46a4c 100644
--- a/sass/ascribe_notification_list.scss
+++ b/sass/ascribe_notification_list.scss
@@ -2,8 +2,9 @@ $break-small: 764px;
$break-medium: 991px;
$break-medium: 1200px;
-.notification-header,.notification-wrapper {
- width: 350px;
+.notification-header, .notification-wrapper {
+ min-width: 350px;
+ width: 100%;
}
.notification-header {
@@ -81,4 +82,4 @@ $break-medium: 1200px;
border: 1px solid #cccccc;
background-color: white;
margin-top: 1px;
-}
\ No newline at end of file
+}
diff --git a/sass/ascribe_notification_page.scss b/sass/ascribe_notification_page.scss
index 955609d2..7bb37446 100644
--- a/sass/ascribe_notification_page.scss
+++ b/sass/ascribe_notification_page.scss
@@ -31,16 +31,11 @@
margin-top: .5em;
margin-bottom: 1em;
- .loan-form {
- margin-top: .5em;
+ &.embed-form {
height: 45vh;
}
}
- .loan-form {
- height: 40vh;
- }
-
.notification-contract-pdf-download {
text-align: left;
margin-left: 1em;
@@ -69,4 +64,8 @@
padding-left: 0;
width: 100%;
}
+}
+
+.ascribe-property.contract-appendix-form {
+ padding-left: 0;
}
\ No newline at end of file
diff --git a/sass/ascribe_panel.scss b/sass/ascribe_panel.scss
index 0f675605..f4b70a80 100644
--- a/sass/ascribe_panel.scss
+++ b/sass/ascribe_panel.scss
@@ -31,7 +31,7 @@
vertical-align: middle;
&:first-child {
- word-break: break-all;
+ word-break: break-word;
font-size: .9em;
}
}
diff --git a/sass/ascribe_piece_list_toolbar.scss b/sass/ascribe_piece_list_toolbar.scss
index f033ee81..06cbd1a7 100644
--- a/sass/ascribe_piece_list_toolbar.scss
+++ b/sass/ascribe_piece_list_toolbar.scss
@@ -81,4 +81,8 @@
top: 2px;
}
}
+
+ .dropdown-menu {
+ min-width: 170px;
+ }
}
diff --git a/sass/ascribe_spinner.scss b/sass/ascribe_spinner.scss
index 7f02a383..543b1463 100644
--- a/sass/ascribe_spinner.scss
+++ b/sass/ascribe_spinner.scss
@@ -52,6 +52,13 @@ $ascribe--spinner-size-sm: 15px;
}
}
+.spinner-wrapper-white {
+ color: $ascribe-white;
+ .spinner-circle {
+ border-color: $ascribe-white;
+ }
+}
+
.spinner-wrapper-lg {
width: $ascribe--spinner-size-lg;
height: $ascribe--spinner-size-lg;
@@ -146,4 +153,4 @@ $ascribe--spinner-size-sm: 15px;
40% { color: $ascribe-blue; }
60% { color: $ascribe-light-blue; }
80% { color: $ascribe-pink; }
-}
\ No newline at end of file
+}
diff --git a/sass/main.scss b/sass/main.scss
index 8a732e96..5cc91e9a 100644
--- a/sass/main.scss
+++ b/sass/main.scss
@@ -350,7 +350,7 @@ hr {
> span {
font-size: 1.1em;
- font-weight: 600;
+ font-weight: normal;
color: #616161;
padding-left: .3em;
diff --git a/sass/whitelabel/wallet/23vivi/23vivi_custom_style.scss b/sass/whitelabel/wallet/23vivi/23vivi_custom_style.scss
new file mode 100644
index 00000000..32783a7a
--- /dev/null
+++ b/sass/whitelabel/wallet/23vivi/23vivi_custom_style.scss
@@ -0,0 +1,363 @@
+/** Sass cannot use a number as the first character of a variable, so we'll have to settle with vivi23 **/
+$vivi23--fg-color: black;
+$vivi23--bg-color: white;
+$vivi23--nav-fg-prim-color: $vivi23--fg-color;
+$vivi23--nav-fg-sec-color: #3a3a3a;
+$vivi23--nav-bg-color: $vivi23--bg-color;
+$vivi23--nav-highlight-color: #f8f8f8;
+$vivi23--button-default-color: $vivi23--fg-color;
+$vivi23--highlight-color: #de2600;
+
+
+.client--23vivi {
+ /** Landing page **/
+ .route--landing {
+ display: table;
+
+ > .container {
+ display: table-cell;
+ padding-bottom: 100px;
+ vertical-align: middle;
+ }
+
+ .vivi23-landing {
+ font-weight: normal;
+ text-align: center;
+ }
+
+ .vivi23-landing--header {
+ background-color: $vivi23--fg-color;
+ border: 1px solid $vivi23--fg-color;
+ color: $vivi23--bg-color;
+ padding: 2em;
+
+ .vivi23-landing--header-logo {
+ margin-top: 1em;
+ margin-bottom: 2em;
+ height: 75px;
+ }
+ }
+
+ .vivi23-landing--content {
+ border: 1px solid darken($vivi23--bg-color, 20%);
+ border-top: none;
+ padding: 2em;
+ }
+ }
+
+ /** Navbar **/
+ .navbar-default {
+ background-color: $vivi23--nav-fg-prim-color;
+
+ .navbar-brand .icon-ascribe-logo {
+ color: $vivi23--bg-color;
+ &:hover {
+ color: darken($vivi23--bg-color, 20%);
+ }
+ }
+
+ }
+
+ .navbar-nav > li > a,
+ .navbar-nav > li > a:focus,
+ .navbar-nav > li > .active a,
+ .navbar-nav > li > .active a:focus {
+ color: $vivi23--nav-bg-color;
+ }
+
+ .navbar-nav > li > a:hover {
+ color: darken($vivi23--nav-bg-color, 20%);
+ }
+
+ .navbar-nav > .active a,
+ .navbar-nav > .active a:hover,
+ .navbar-nav > .active a:focus {
+ background-color: $vivi23--nav-fg-prim-color;
+ border-bottom-color: $vivi23--nav-bg-color;
+ color: $vivi23--nav-bg-color;
+ }
+
+ .navbar-nav > .open > a,
+ .dropdown-menu > .active > a,
+ .dropdown-menu > li > a {
+ color: $vivi23--nav-fg-prim-color;
+ background-color: $vivi23--nav-bg-color;
+ }
+
+ .navbar-nav > .open > a:hover,
+ .navbar-nav > .open > a:focus,
+ .dropdown-menu > .active > a:hover,
+ .dropdown-menu > .active > a:focus,
+ .dropdown-menu > li > a:hover,
+ .dropdown-menu > li > a:focus {
+ color: lighten($vivi23--nav-fg-prim-color, 20%);
+ background-color: $vivi23--nav-highlight-color;
+ }
+
+ .navbar-collapse.collapsing,
+ .navbar-collapse.collapse.in {
+ background-color: $vivi23--nav-bg-color;
+
+ .navbar-nav > .open > a,
+ .navbar-nav > .active > a {
+ background-color: $vivi23--nav-highlight-color;
+ }
+ }
+
+ .navbar-collapse.collapsing li a,
+ .navbar-collapse.collapse.in li a {
+ color: $vivi23--nav-fg-prim-color;
+ }
+ .navbar-collapse.collapse.in li a:not(.ascribe-powered-by):hover {
+ color: lighten($vivi23--nav-fg-prim-color, 20%);
+ background-color: $vivi23--nav-highlight-color;
+ }
+
+ .navbar-toggle {
+ border-color: $vivi23--highlight-color;
+
+ .icon-bar {
+ background-color: $vivi23--highlight-color;
+ }
+ }
+
+ .navbar-toggle:hover,
+ .navbar-toggle:focus {
+ border-color: lighten($vivi23--highlight-color, 10%);
+ background-color: $vivi23--nav-fg-prim-color;
+
+ .icon-bar {
+ background-color: lighten($vivi23--highlight-color, 10%);
+ }
+ }
+
+ .notification-menu {
+ .dropdown-menu {
+ background-color: $vivi23--nav-bg-color;
+ }
+
+ .notification-header {
+ background-color: $vivi23--nav-fg-sec-color;
+ border-top-width: 0;
+ color: $vivi23--nav-bg-color;
+ }
+
+ .notification-action {
+ color: $vivi23--highlight-color;
+ }
+ }
+
+ /** Buttons **/
+ // reset disabled button styling for btn-default
+ .btn-default.disabled,
+ .btn-default.disabled:hover,
+ .btn-default.disabled:focus,
+ .btn-default.disabled.focus,
+ .btn-default.disabled:active,
+ .btn-default.disabled.active,
+ .btn-default[disabled],
+ .btn-default[disabled]:hover,
+ .btn-default[disabled]:focus,
+ .btn-default[disabled].focus,
+ .btn-default[disabled]:active,
+ .btn-default[disabled].active,
+ fieldset[disabled] .btn-default,
+ fieldset[disabled] .btn-default:hover,
+ fieldset[disabled] .btn-default:focus,
+ fieldset[disabled] .btn-default.focus,
+ fieldset[disabled] .btn-default:active,
+ fieldset[disabled] .btn-default.active {
+ background-color: lighten($vivi23--button-default-color, 30%);
+ border-color: lighten($vivi23--button-default-color, 30%);
+ }
+
+ .btn-default {
+ background-color: $vivi23--button-default-color;
+ border-color: $vivi23--button-default-color;
+
+ &:hover,
+ &:active,
+ &:focus,
+ &:active:hover,
+ &:active:focus,
+ &:active.focus,
+ &.active:hover,
+ &.active:focus,
+ &.active.focus {
+ background-color: lighten($vivi23--button-default-color, 30%);
+ border-color: lighten($vivi23--button-default-color, 30%);
+ }
+ }
+
+ // disabled buttons
+ .btn-secondary.disabled,
+ .btn-secondary.disabled:hover,
+ .btn-secondary.disabled:focus,
+ .btn-secondary.disabled.focus,
+ .btn-secondary.disabled:active,
+ .btn-secondary.disabled.active,
+ .btn-secondary[disabled],
+ .btn-secondary[disabled]:hover,
+ .btn-secondary[disabled]:focus,
+ .btn-secondary[disabled].focus,
+ .btn-secondary[disabled]:active,
+ .btn-secondary[disabled].active,
+ fieldset[disabled] .btn-secondary,
+ fieldset[disabled] .btn-secondary:hover,
+ fieldset[disabled] .btn-secondary:focus,
+ fieldset[disabled] .btn-secondary.focus,
+ fieldset[disabled] .btn-secondary:active,
+ fieldset[disabled] .btn-secondary.active {
+ background-color: darken($vivi23--bg-color, 20%);
+ border-color: $vivi23--button-default-color;
+ }
+
+ .btn-secondary {
+ border-color: $vivi23--button-default-color;
+
+ &:hover,
+ &:active,
+ &:focus,
+ &:active:hover,
+ &:active:focus,
+ &:active.focus,
+ &.active:hover,
+ &.active:focus,
+ &.active.focus {
+ background-color: $vivi23--button-default-color;
+ border-color: $vivi23--button-default-color;
+ }
+ }
+
+ .btn-tertiary {
+ &:hover,
+ &:active,
+ &ctive:hover,
+ &.active:hover{
+ background-color: $vivi23--highlight-color;
+ border-color: $vivi23--highlight-color;
+ color: $vivi23--highlight-color;
+ }
+ }
+
+ /** Other components **/
+ .ascribe-piece-list-toolbar .btn-ascribe-add {
+ display: none;
+ }
+
+ .ascribe-footer {
+ display: none;
+ }
+
+ .ascribe-accordion-list-table-toggle:hover {
+ color: $vivi23--fg-color;
+ }
+
+ .request-action-badge {
+ color: $vivi23--fg-color;
+ }
+
+ .acl-information-dropdown-list .title {
+ color: $vivi23--fg-color;
+ }
+
+ // filter widget
+ .ascribe-piece-list-toolbar-filter-widget button {
+ background-color: transparent !important;
+ border-color: transparent !important;
+ color: $vivi23--button-default-color !important;
+
+ &:hover,
+ &:active {
+ background-color: $vivi23--button-default-color !important;
+ border-color: $vivi23--button-default-color !important;
+ color: $vivi23--bg-color !important;
+ }
+ }
+
+ .icon-ascribe-search {
+ color: $vivi23--fg-color;
+ }
+
+ // forms
+ .ascribe-property-wrapper:hover {
+ border-left-color: rgba($vivi23--fg-color, 0.5);
+ }
+
+ .ascribe-property textarea,
+ .ascribe-property input,
+ .search-bar > .form-group > .input-group input {
+ &::-webkit-input-placeholder {
+ color: rgba($vivi23--fg-color, 0.5);
+ }
+ &::-moz-placeholder {
+ color: rgba($vivi23--fg-color, 0.5);
+ }
+ &:-ms-input-placeholder {
+ color: rgba($vivi23--fg-color, 0.5);
+ }
+ &:-moz-placeholder {
+ color: rgba($vivi23--fg-color, 0.5);
+ }
+ }
+
+ .ascribe-property {
+ > div,
+ > input,
+ > pre,
+ > select,
+ > span:not(.glyphicon),
+ > p,
+ > p > span,
+ > textarea {
+ color: $vivi23--fg-color;
+ }
+ }
+
+ // global notification
+ .ascribe-global-notification-success {
+ background-color: lighten($vivi23--fg-color, 20%);
+ }
+
+ // uploader progress
+ .ascribe-progress-bar > .progress-bar {
+ background-color: lighten($vivi23--fg-color, 20%);
+ }
+
+ // spinner
+ .spinner-circle {
+ border-color: $vivi23--fg-color;
+ }
+ .spinner-inner {
+ display: none;
+ }
+ .btn-secondary .spinner-circle {
+ border-color: $vivi23--bg-color;
+ }
+
+ // video player
+ .ascribe-media-player .vjs-default-skin {
+ .vjs-play-progress,
+ .vjs-volume-level {
+ background-color: $vivi23--highlight-color;
+ }
+ }
+
+ // pager
+ .pager li > a,
+ .pager li > span {
+ background-color: $vivi23--fg-color;
+ border-color: $vivi23--fg-color;
+ }
+ .pager .disabled > a,
+ .pager .disabled > span {
+ background-color: $vivi23--fg-color !important;
+ border-color: $vivi23--fg-color;
+ }
+
+ // intercom
+ #intercom-container .intercom-launcher-button {
+ background-color: $vivi23--button-default-color !important;
+ border-color: $vivi23--button-default-color !important;
+ }
+}
diff --git a/sass/whitelabel/wallet/cyland/cyland_custom_style.scss b/sass/whitelabel/wallet/cyland/cyland_custom_style.scss
index 46898e53..5077e4e1 100644
--- a/sass/whitelabel/wallet/cyland/cyland_custom_style.scss
+++ b/sass/whitelabel/wallet/cyland/cyland_custom_style.scss
@@ -59,8 +59,12 @@ $cyland--button-color: $cyland--nav-fg-prim-color;
display: none;
}
+<<<<<<< HEAD
.client--cyland .icon-ascribe-search {
+=======
+.client--cyland .icon-ascribe-search{
+>>>>>>> master
color: $cyland--button-color;
}
@@ -143,6 +147,7 @@ $cyland--button-color: $cyland--nav-fg-prim-color;
}
}
+<<<<<<< HEAD
// disabled buttons
.client--cyland {
.btn-default.disabled,
@@ -165,6 +170,23 @@ $cyland--button-color: $cyland--nav-fg-prim-color;
fieldset[disabled] .btn-default.active {
background-color: darken($cyland--button-color, 20%);
border-color: darken($cyland--button-color, 20%);
+=======
+// landing page
+.client--cyland {
+ .route--landing {
+ display: table;
+
+ > .container {
+ display: table-cell;
+ padding-bottom: 100px;
+ vertical-align: middle;
+ }
+
+ .cyland-landing {
+ font-weight: normal;
+ text-align: center;
+ }
+>>>>>>> master
}
}
@@ -203,6 +225,7 @@ $cyland--button-color: $cyland--nav-fg-prim-color;
.client--cyland .acl-information-dropdown-list .title {
color: $cyland--button-color;
}
+<<<<<<< HEAD
.client--cyland .action-file.icon-ascribe-ok,
.client--cyland .action-file.icon-ascribe-ok:hover {
@@ -218,4 +241,6 @@ $cyland--button-color: $cyland--nav-fg-prim-color;
.client--cyland .upload-button-wrapper > span {
color: $cyland--button-color;
-}
\ No newline at end of file
+}
+=======
+>>>>>>> master
diff --git a/sass/whitelabel/wallet/index.scss b/sass/whitelabel/wallet/index.scss
index 024fb3cc..01c374d9 100644
--- a/sass/whitelabel/wallet/index.scss
+++ b/sass/whitelabel/wallet/index.scss
@@ -1,6 +1,7 @@
@import 'cc/cc_custom_style';
@import 'cyland/cyland_custom_style';
@import 'ikonotv/ikonotv_custom_style';
+@import '23vivi/23vivi_custom_style';
.ascribe-wallet-app {
border-radius: 0;