1
0
mirror of https://github.com/ascribe/onion.git synced 2024-12-22 17:33:14 +01:00

piece + share

acl first cut
This commit is contained in:
diminator 2015-07-13 14:10:46 +02:00
parent 88ff4b55d6
commit a07bc9e0e2
16 changed files with 131 additions and 370 deletions

View File

@ -14,18 +14,21 @@ import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions'; import GlobalNotificationActions from '../../actions/global_notification_actions';
import { getLangText } from '../../utils/lang_utils.js'; import { getLangText } from '../../utils/lang_utils.js';
import apiUrls from '../../constants/api_urls';
let AclButton = React.createClass({ let AclButton = React.createClass({
propTypes: { propTypes: {
action: React.PropTypes.oneOf(AppConstants.aclList).isRequired, action: React.PropTypes.oneOf(AppConstants.aclList).isRequired,
availableAcls: React.PropTypes.array.isRequired, availableAcls: React.PropTypes.array.isRequired,
pieceOrEditions: React.PropTypes.array.isRequired, pieceOrEditions: React.PropTypes.object.isRequired,
currentUser: React.PropTypes.object, currentUser: React.PropTypes.object,
handleSuccess: React.PropTypes.func.isRequired, handleSuccess: React.PropTypes.func.isRequired,
className: React.PropTypes.string className: React.PropTypes.string
}, },
isPiece(){
return !(this.props.pieceOrEditions.constructor === Array);
},
actionProperties(){ actionProperties(){
if (this.props.action === 'consign'){ if (this.props.action === 'consign'){
return { return {
@ -62,16 +65,46 @@ let AclButton = React.createClass({
return { return {
title: getLangText('Share artwork'), title: getLangText('Share artwork'),
tooltip: getLangText('Share the artwork'), tooltip: getLangText('Share the artwork'),
form: <ShareForm currentUser={ this.props.currentUser } editions={ this.props.pieceOrEditions }/>, form: (
<ShareForm
message={this.getShareMessage()}
id={this.getFormDataId()}
url={this.isPiece() ? apiUrls.ownership_shares_pieces : apiUrls.ownership_shares_editions }/>
),
handleSuccess: this.showNotification handleSuccess: this.showNotification
}; };
} }
}, },
showNotification(response){ showNotification(response){
this.props.handleSuccess(); this.props.handleSuccess();
let notification = new GlobalNotificationModel(response.notification, 'success'); let notification = new GlobalNotificationModel(response.notification, 'success');
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
}, },
getTitlesString(){
if (this.isPiece()){
return '\"' + this.props.pieceOrEditions.title + '\"';
}
else {
return this.props.pieceOrEditions.map(function(edition) {
return '- \"' + edition.title + ', ' + getLangText('edition') + ' ' + edition.edition_number + '\"\n';
}).join('');
}
},
getFormDataId(){
if (this.isPiece()) {
return {piece_id: this.props.pieceOrEditions.id};
}
else {
return {bitcoin_id: this.props.pieceOrEditions.map(function(edition){
return edition.bitcoin_id;
}).join()};
}
},
render() { render() {
let shouldDisplay = this.props.availableAcls.indexOf(this.props.action) > -1; let shouldDisplay = this.props.availableAcls.indexOf(this.props.action) > -1;
let aclProps = this.actionProperties(); let aclProps = this.actionProperties();
@ -88,6 +121,18 @@ let AclButton = React.createClass({
{ aclProps.form } { aclProps.form }
</ModalWrapper> </ModalWrapper>
); );
},
getShareMessage(){
return (
`${getLangText('Hi')},
${getLangText('I am sharing')} :
${this.getTitlesString()}${getLangText('with you')}.
${getLangText('Truly yours')},
${this.props.currentUser.username}`
);
} }
}); });

View File

@ -7,11 +7,12 @@ import UserStore from '../../stores/user_store';
import AclButton from '../ascribe_buttons/acl_button'; import AclButton from '../ascribe_buttons/acl_button';
import DeleteButton from '../ascribe_buttons/delete_button'; import DeleteButton from '../ascribe_buttons/delete_button';
import CreateEditionButton from '../ascribe_buttons/create_editions_button';
let AclButtonList = React.createClass({ let AclButtonList = React.createClass({
propTypes: { propTypes: {
className: React.PropTypes.string, className: React.PropTypes.string,
editions: React.PropTypes.array, editions: React.PropTypes.object,
availableAcls: React.PropTypes.array, availableAcls: React.PropTypes.array,
handleSuccess: React.PropTypes.func handleSuccess: React.PropTypes.func
}, },
@ -66,7 +67,10 @@ let AclButtonList = React.createClass({
pieceOrEditions={this.props.editions} pieceOrEditions={this.props.editions}
currentUser={this.state.currentUser} currentUser={this.state.currentUser}
handleSuccess={this.props.handleSuccess} /> handleSuccess={this.props.handleSuccess} />
<DeleteButton editions={this.props.editions}/> <DeleteButton
editions={this.props.editions}/>
<CreateEditionButton
piece={this.props.editions}/>
</div> </div>
); );
} }

View File

@ -0,0 +1,44 @@
'use strict';
import React from 'react';
import Router from 'react-router';
import Button from 'react-bootstrap/lib/Button';
import EditionDeleteForm from '../ascribe_forms/form_delete_edition';
import EditionRemoveFromCollectionForm from '../ascribe_forms/form_remove_editions_from_collection';
import ModalWrapper from '../ascribe_modal/modal_wrapper';
import AccordionListItemCreateEditions from './../ascribe_accordion_list/accordion_list_item_create_editions';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
import { getAvailableAcls } from '../../utils/acl_utils';
import { getLangText } from '../../utils/lang_utils.js'
import EditionListActions from '../../actions/edition_list_actions';
let CreateEditionsButton = React.createClass({
propTypes: {
piece: React.PropTypes.object.isRequired
},
mixins: [Router.Navigation],
render: function () {
if (this.props.piece.constructor === Array){
return null;
}
let availableAcls = getAvailableAcls([this.props.piece]);
if (availableAcls.indexOf('editions') === -1){
return null;
}
return (
<button className="btn btn-default btn-sm">
CREATE EDITIONS
</button>
);
}
});
export default CreateEditionsButton;

View File

@ -218,7 +218,6 @@ let EditionSummary = React.createClass({
<EditionDetailProperty label={getLangText('ID')} value={ this.props.edition.bitcoin_id } /> <EditionDetailProperty label={getLangText('ID')} value={ this.props.edition.bitcoin_id } />
<EditionDetailProperty label={getLangText('OWNER')} value={ this.props.edition.owner } /> <EditionDetailProperty label={getLangText('OWNER')} value={ this.props.edition.owner } />
{this.getStatus()} {this.getStatus()}
<br/>
{this.getActions()} {this.getActions()}
<hr/> <hr/>
</div> </div>

View File

@ -70,7 +70,8 @@ let Piece = React.createClass({
content={this.props.piece}/> content={this.props.piece}/>
<PieceSummary <PieceSummary
currentUser={this.state.currentUser} currentUser={this.state.currentUser}
piece={this.props.piece} /> piece={this.props.piece}
handleSuccess={this.props.loadPiece}/>
<CollapsibleParagraph <CollapsibleParagraph
title="Further Details" title="Further Details"
show={this.props.piece.acl.indexOf('edit') > -1 show={this.props.piece.acl.indexOf('edit') > -1
@ -95,26 +96,24 @@ let PieceSummary = React.createClass({
propTypes: { propTypes: {
piece: React.PropTypes.object piece: React.PropTypes.object
}, },
getActions(){ getActions(){
//let actions = ( let actions = (
// <Row> <Row>
// <Col md={12}> <Col md={12}>
// <AclButtonList <AclButtonList
// className="text-center ascribe-button-list" className="text-center ascribe-button-list"
// availableAcls={this.props.piece.acl} availableAcls={this.props.piece.acl}
// editions={[this.props.piece]} editions={this.props.piece}
// handleSuccess={this.handleSuccess} /> handleSuccess={this.props.handleSuccess} />
// </Col> </Col>
// </Row>); </Row>);
//return actions; return actions;
return null; //return null;
}, },
render() { render() {
return ( return (
<div className="ascribe-detail-header"> <div className="ascribe-detail-header">
<DetailProperty label={getLangText('REGISTREE')} value={ this.props.piece.user_registered } /> <DetailProperty label={getLangText('REGISTREE')} value={ this.props.piece.user_registered } />
<br/>
{this.getActions()} {this.getActions()}
<hr/> <hr/>
</div> </div>

View File

@ -1,64 +0,0 @@
'use strict';
import React from 'react';
import apiUrls from '../../constants/api_urls';
import FormMixin from '../../mixins/form_mixin';
import InputText from './input_text';
import ButtonSubmitOrClose from '../ascribe_buttons/button_submit_close';
import SignupModal from '../ascribe_modal/modal_signup';
import PasswordResetRequestModal from '../ascribe_modal/modal_password_request_reset';
import { getLangText } from '../../utils/lang_utils.js';
let LoginForm = React.createClass({
mixins: [FormMixin],
url() {
return apiUrls.users_login;
},
getFormData() {
return {
email: this.refs.email.state.value,
password: this.refs.password.state.value
};
},
renderForm() {
return (
<form id="login_modal_content" role="form" onSubmit={this.submit}>
<input className="invisible" type="email" name="fake_consignee"/>
<input className="invisible" type="password" name="fake_password"/>
<InputText
ref="email"
placeHolder={getLangText('Email')}
required="required"
type="email"
submitted={this.state.submitted}/>
<InputText
ref="password"
placeHolder={getLangText('Password')}
required="required"
type="password"
submitted={this.state.submitted}/>
<div>
{getLangText('Forgot your password')}&#63;
<PasswordResetRequestModal
button={<a className="button" href="#"> {getLangText('Reset password')}</a>}/>
</div>
<div>
{getLangText('Not a member yet')}&#63;
<SignupModal
button={<a className="button" href="#"> {getLangText('Sign up')}</a>}/>
</div>
<ButtonSubmitOrClose
text={getLangText('LOGIN')}
onClose={this.props.onRequestHide}
submitted={this.state.submitted} />
</form>
);
}
});
export default LoginForm;

View File

@ -1,43 +0,0 @@
'use strict';
import React from 'react';
import apiUrls from '../../constants/api_urls';
import FormMixin from '../../mixins/form_mixin';
import InputTextAreaToggable from './input_textarea_toggable';
let EditionNoteForm = React.createClass({
mixins: [FormMixin],
url() {
return apiUrls.note_edition;
},
getFormData() {
return {
bitcoin_id: this.getBitcoinIds().join(),
note: this.refs.personalNote.state.value
};
},
renderForm() {
return (
<form id="personal_note_content" role="form" key="personal_note_content">
<InputTextAreaToggable
ref="personalNote"
className="form-control"
defaultValue={this.props.editions[0].public_note}
rows={3}
editable={this.props.editable}
required=""
onSubmit={this.submit}
/>
</form>
);
}
});
export default EditionNoteForm;

View File

@ -1,43 +0,0 @@
'use strict';
import React from 'react';
import apiUrls from '../../constants/api_urls';
import FormMixin from '../../mixins/form_mixin';
import InputTextAreaToggable from './input_textarea_toggable';
let PersonalNoteForm = React.createClass({
mixins: [FormMixin],
url() {
return apiUrls.note_notes;
},
getFormData() {
return {
bitcoin_id: this.getBitcoinIds().join(),
note: this.refs.personalNote.state.value
};
},
renderForm() {
return (
<form id="personal_note_content" role="form" key="personal_note_content">
<InputTextAreaToggable
ref="personalNote"
className="form-control"
defaultValue={this.props.editions[0].note_from_user}
rows={3}
editable={this.props.editable}
required=""
onSubmit={this.submit}
/>
</form>
);
}
});
export default PersonalNoteForm;

View File

@ -1,51 +0,0 @@
'use strict';
import React from 'react';
import apiUrls from '../../constants/api_urls';
import FormMixin from '../../mixins/form_mixin';
import InputText from './input_text';
import ButtonSubmit from '../ascribe_buttons/button_submit';
import { getLangText } from '../../utils/lang_utils.js'
let PasswordResetForm = React.createClass({
mixins: [FormMixin],
url() {
return apiUrls.users_password_reset;
},
getFormData() {
return {
email: this.props.email,
token: this.props.token,
password: this.refs.password.state.value,
password_confirm: this.refs.password_confirm.state.value
};
},
renderForm() {
return (
<form id="reset_password_modal_content" role="form" onSubmit={this.submit}>
<div>Reset the password for {this.props.email}:</div>
<InputText
ref="password"
placeHolder={getLangText('Choose a password')}
required="required"
type="password"
submitted={this.state.submitted}/>
<InputText
ref="password_confirm"
placeHolder={getLangText('Confirm password')}
required="required"
type="password"
submitted={this.state.submitted}/>
<ButtonSubmit
text={getLangText('RESET PASSWORD')}
submitted={this.state.submitted} />
</form>
);
}
});
export default PasswordResetForm;

View File

@ -1,42 +0,0 @@
'use strict';
import React from 'react';
import apiUrls from '../../constants/api_urls';
import FormMixin from '../../mixins/form_mixin';
import InputText from './input_text';
import ButtonSubmitOrClose from '../ascribe_buttons/button_submit_close';
import { getLangText } from '../../utils/lang_utils.js'
let PasswordResetRequestForm = React.createClass({
mixins: [FormMixin],
url() {
return apiUrls.users_password_reset_request;
},
getFormData() {
return {
email: this.refs.email.state.value
};
},
renderForm() {
return (
<form id="request_reset_password_modal_content" role="form" onSubmit={this.submit}>
<InputText
ref="email"
placeHolder={getLangText('Email')}
required="required"
type="email"
submitted={this.state.submitted}/>
<ButtonSubmitOrClose
text={getLangText('RESET PASSWORD')}
onClose={this.props.onRequestHide}
submitted={this.state.submitted} />
</form>
);
}
});
export default PasswordResetRequestForm;

View File

@ -24,8 +24,9 @@ let EditionRemoveFromCollectionForm = React.createClass({
<p>{getLangText('Are you sure you would like to remove these editions from your collection')}&#63;</p> <p>{getLangText('Are you sure you would like to remove these editions from your collection')}&#63;</p>
<p>{getLangText('This is an irrevocable action%s', '.')}</p> <p>{getLangText('This is an irrevocable action%s', '.')}</p>
<div className="modal-footer"> <div className="modal-footer">
<button type="submit" className="btn btn-ascribe-inv" onClick={this.submit}>{getLangText('YES, REMOVE')}</button> <button type="submit" className="btn btn-danger btn-delete btn-sm ascribe-margin-1px" onClick={this.submit}>{getLangText('YES, REMOVE')}</button>
<button className="btn btn-ascribe" onClick={this.props.onRequestHide}>{getLangText('CLOSE')}</button> <button className="btn btn-default btn-sm ascribe-margin-1px" style={{marginLeft: '0'}}
onClick={this.props.onRequestHide}>{getLangText('CLOSE')}</button>
</div> </div>
</div> </div>
); );

View File

@ -2,7 +2,7 @@
import React from 'react'; import React from 'react';
import ApiUrls from '../../constants/api_urls';
import Form from './form'; import Form from './form';
import Property from './property'; import Property from './property';
@ -15,49 +15,27 @@ import { getLangText } from '../../utils/lang_utils.js';
let ShareForm = React.createClass({ let ShareForm = React.createClass({
propTypes: { propTypes: {
url: React.PropTypes.string,
id: React.PropTypes.string,
message: React.PropTypes.string,
editions: React.PropTypes.array, editions: React.PropTypes.array,
currentUser: React.PropTypes.object currentUser: React.PropTypes.object,
onRequestHide: React.PropTypes.func,
handleSuccess: React.PropTypes.func
}, },
getFormData() { getFormData(){
return { return this.props.id;
bitcoin_id: this.getBitcoinIds().join()
};
},
handleSuccess(response){
if ('handleSuccess' in this.props){
this.props.handleSuccess(response);
}
},
getBitcoinIds(){
return this.props.editions.map(function(edition){
return edition.bitcoin_id;
});
},
getTitlesString(){
return this.props.editions.map(function(edition){
return '- \"' + edition.title + ', ' + getLangText('edition') + ' ' + edition.edition_number + '\"\n';
});
}, },
render() { render() {
let title = this.getTitlesString().join('');
let username = this.props.currentUser.username;
let message =
`${getLangText('Hi')},
${getLangText('I am sharing')} :
${title}${getLangText('with you')}.
${getLangText('Truly yours')},
${username}`;
return ( return (
<Form <Form
ref='form' ref='form'
url={ApiUrls.ownership_shares_editions} url={this.props.url}
getFormData={this.getFormData} getFormData={this.getFormData}
handleSuccess={this.handleSuccess} handleSuccess={this.props.handleSuccess}
buttons={ buttons={
<div className="modal-footer"> <div className="modal-footer">
<p className="pull-right"> <p className="pull-right">
@ -73,9 +51,7 @@ ${username}`;
spinner={ spinner={
<div className="modal-footer"> <div className="modal-footer">
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} /> <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} />
</div> </div>}>
}
>
<Property <Property
name='share_emails' name='share_emails'
label={getLangText('Emails')}> label={getLangText('Emails')}>
@ -91,7 +67,7 @@ ${username}`;
<InputTextAreaToggable <InputTextAreaToggable
rows={1} rows={1}
editable={true} editable={true}
defaultValue={message} defaultValue={this.props.message}
placeholder={getLangText('Enter a message...')} placeholder={getLangText('Enter a message...')}
required="required"/> required="required"/>
</Property> </Property>

View File

@ -1,33 +0,0 @@
'use strict';
import React from 'react';
import ModalWrapper from './modal_wrapper';
import LoginForm from '../ascribe_forms/form_login';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
import { getLangText } from '../../utils/lang_utils.js'
let LoginModal = React.createClass({
handleLoginSuccess(){
this.props.handleSuccess();
let notificationText = getLangText('Login successful');
let notification = new GlobalNotificationModel(notificationText, 'success');
GlobalNotificationActions.appendGlobalNotification(notification);
},
render() {
return (
<ModalWrapper
button={this.props.button}
title={getLangText('Log in to ascribe')}
handleSuccess={this.handleLoginSuccess}
tooltip={getLangText('Log in to ascribe')}>
<LoginForm />
</ModalWrapper>
);
}
});
export default LoginModal;

View File

@ -1,32 +0,0 @@
'use strict';
import React from 'react';
import ModalWrapper from './modal_wrapper';
import SignupForm from '../ascribe_forms/form_signup';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
import { getLangText } from '../../utils/lang_utils.js'
let SignupModal = React.createClass({
handleSignupSuccess(response){
let notificationText = getLangText('We sent an email to your address') + ' ' + response.user.email + ', ' + getLangText('please confirm') + '.';
let notification = new GlobalNotificationModel(notificationText, 'success', 50000);
GlobalNotificationActions.appendGlobalNotification(notification);
},
render() {
return (
<ModalWrapper
button={this.props.button}
title={getLangText('Create an account')}
handleSuccess={this.handleSignupSuccess}
tooltip={getLangText('Sign up to ascribe')}>
<SignupForm />
</ModalWrapper>
);
}
});
export default SignupModal;

View File

@ -12,7 +12,7 @@ let apiUrls = {
'coa_verify': AppConstants.apiEndpoint + 'coa/verify_coa/', 'coa_verify': AppConstants.apiEndpoint + 'coa/verify_coa/',
'edition': AppConstants.apiEndpoint + 'editions/${bitcoin_id}/', 'edition': AppConstants.apiEndpoint + 'editions/${bitcoin_id}/',
'edition_delete': AppConstants.apiEndpoint + 'editions/${edition_id}/', 'edition_delete': AppConstants.apiEndpoint + 'editions/${edition_id}/',
'edition_remove_from_collection': AppConstants.apiEndpoint + 'ownership/shares/${edition_id}/', 'edition_remove_from_collection': AppConstants.apiEndpoint + 'ownership/shares/editions/${edition_id}/',
'editions': AppConstants.apiEndpoint + 'editions/', // this should be moved to the one below 'editions': AppConstants.apiEndpoint + 'editions/', // this should be moved to the one below
'editions_list': AppConstants.apiEndpoint + 'pieces/${piece_id}/editions/', 'editions_list': AppConstants.apiEndpoint + 'pieces/${piece_id}/editions/',
'licenses': AppConstants.apiEndpoint + 'ownership/licenses/', 'licenses': AppConstants.apiEndpoint + 'ownership/licenses/',
@ -26,6 +26,7 @@ let apiUrls = {
'ownership_loans_deny': AppConstants.apiEndpoint + 'ownership/loans/deny/', 'ownership_loans_deny': AppConstants.apiEndpoint + 'ownership/loans/deny/',
'ownership_loans_contract': AppConstants.apiEndpoint + 'ownership/loans/contract/', 'ownership_loans_contract': AppConstants.apiEndpoint + 'ownership/loans/contract/',
'ownership_shares_editions': AppConstants.apiEndpoint + 'ownership/shares/editions/', 'ownership_shares_editions': AppConstants.apiEndpoint + 'ownership/shares/editions/',
'ownership_shares_pieces': AppConstants.apiEndpoint + 'ownership/shares/pieces/',
'ownership_transfers': AppConstants.apiEndpoint + 'ownership/transfers/', 'ownership_transfers': AppConstants.apiEndpoint + 'ownership/transfers/',
'ownership_transfers_withdraw': AppConstants.apiEndpoint + 'ownership/transfers/withdraw/', 'ownership_transfers_withdraw': AppConstants.apiEndpoint + 'ownership/transfers/withdraw/',
'ownership_unconsigns': AppConstants.apiEndpoint + 'ownership/unconsigns/', 'ownership_unconsigns': AppConstants.apiEndpoint + 'ownership/unconsigns/',

View File

@ -93,7 +93,7 @@ $ascribe-accordion-list-font: 'Source Sans Pro';
} }
} }
border-left: 3px solid rgba(0,0,0,0); border-left: 3px solid rgba(0,0,0,0);
border-top: 1px solid rgba(0,0,0,.1); //border-top: 1px solid rgba(0,0,0,.1);
border-bottom: 1px solid rgba(0,0,0,.05); border-bottom: 1px solid rgba(0,0,0,.05);
} }
tbody { tbody {