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

localization

This commit is contained in:
ddejongh 2015-06-23 16:02:48 +02:00
parent 3183dae054
commit 95f6c08706
4 changed files with 555 additions and 1 deletions

View File

@ -0,0 +1,153 @@
'use strict';
import React from 'react';
import ReactAddons from 'react/addons';
import Button from 'react-bootstrap/lib/Button';
import requests from '../../utils/requests';
import AlertDismissable from './alert';
let Form = React.createClass({
propTypes: {
url: React.PropTypes.string,
handleSuccess: React.PropTypes.func,
getFormData: React.PropTypes.func,
children: React.PropTypes.oneOfType([
React.PropTypes.object,
React.PropTypes.array
])
},
getInitialState() {
return {
edited: false,
submitted: false,
errors: []
};
},
reset(){
for (let ref in this.refs){
if (typeof this.refs[ref].reset === 'function'){
this.refs[ref].reset();
}
}
},
submit(event){
if (event) {
event.preventDefault();
}
this.setState({submitted: true});
this.clearErrors();
let action = (this.httpVerb && this.httpVerb()) || 'post';
this[action]();
},
post(){
requests
.post(this.props.url, { body: this.getFormData() })
.then(this.handleSuccess)
.catch(this.handleError);
},
getFormData(){
if ('getFormData' in this.props){
return this.props.getFormData();
}
let data = {};
for (let ref in this.refs){
data[this.refs[ref].props.name] = this.refs[ref].state.value;
}
return data;
},
handleChangeChild(){
this.setState({edited: true});
},
handleSuccess(response){
if ('handleSuccess' in this.props){
this.props.handleSuccess(response);
}
for (var ref in this.refs){
if ('handleSuccess' in this.refs[ref]){
this.refs[ref].handleSuccess();
}
}
this.setState({edited: false, submitted: false});
},
handleError(err){
if (err.json) {
for (var input in err.json.errors){
if (this.refs && this.refs[input] && this.refs[input].state) {
this.refs[input].setErrors( err.json.errors[input]);
} else {
this.setState({errors: this.state.errors.concat(err.json.errors[input])});
}
}
}
else {
this.setState({errors: ['Something went wrong, please try again later']});
}
this.setState({submitted: false});
},
clearErrors(){
for (var ref in this.refs){
if ('clearErrors' in this.refs[ref]){
this.refs[ref].clearErrors();
}
}
this.setState({errors: []});
},
getButtons() {
if (this.state.submitted){
return this.props.spinner;
}
if (this.props.buttons){
return this.props.buttons;
}
let buttons = null;
if (this.state.edited){
buttons = (
<div className="row" style={{margin: 0}}>
<p className="pull-right">
<Button className="ascribe-btn" type="submit">Save</Button>
<Button className="ascribe-btn" onClick={this.reset}>Cancel</Button>
</p>
</div>
);
}
return buttons;
},
getErrors() {
let errors = null;
if (this.state.errors.length > 0){
errors = this.state.errors.map((error) => {
return <AlertDismissable error={error} key={error}/>;
});
}
return errors;
},
renderChildren() {
return ReactAddons.Children.map(this.props.children, (child) => {
return ReactAddons.addons.cloneWithProps(child, {
handleChange: this.handleChangeChild,
ref: child.props.name
});
});
},
render() {
return (
<form role="form" className="ascribe-form" onSubmit={this.submit} autoComplete="on">
{this.getErrors()}
{this.renderChildren()}
{this.getButtons()}
</form>
);
}
});
export default Form;

View File

@ -0,0 +1,279 @@
'use strict';
import React from 'react';
import Router from 'react-router';
import UserActions from '../actions/user_actions';
import UserStore from '../stores/user_store';
import WalletSettingsActions from '../actions/wallet_settings_actions';
import WalletSettingsStore from '../stores/wallet_settings_store';
import ApplicationActions from '../actions/application_actions';
import ApplicationStore from '../stores/application_store';
import GlobalNotificationModel from '../models/global_notification_model';
import GlobalNotificationActions from '../actions/global_notification_actions';
import CollapsibleParagraph from './ascribe_collapsible/collapsible_paragraph';
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 SettingsContainer = React.createClass({
mixins: [Router.Navigation],
render() {
return (
<div>
<AccountSettings />
<BitcoinWalletSettings />
<APISettings />
</div>
);
}
});
let AccountSettings = React.createClass({
getInitialState() {
return UserStore.getState();
},
componentDidMount() {
UserStore.listen(this.onChange);
UserActions.fetchCurrentUser();
},
componentWillUnmount() {
UserStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
},
handleSuccess(){
UserActions.fetchCurrentUser();
let notification = new GlobalNotificationModel('username succesfully updated', 'success', 5000);
GlobalNotificationActions.appendGlobalNotification(notification);
},
render() {
let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
if (this.state.currentUser.username) {
content = (
<Form
url={apiUrls.users_username}
handleSuccess={this.handleSuccess}>
<Property
name='username'
label="Username">
<input
type="text"
defaultValue={this.state.currentUser.username}
placeholder="Enter your username"
required/>
</Property>
<Property
name='email'
label="Email"
editable={false}>
<input
type="text"
defaultValue={this.state.currentUser.email}
placeholder="Enter your username"
required/>
</Property>
<hr />
</Form>
);
}
return (
<CollapsibleParagraph
title="Account"
show={true}
defaultExpanded={true}>
{content}
<Form
url={AppConstants.serverUrl + 'api/users/set_language/'}>
<Property
name='language'
label="Choose your Language"
editable={true}>
<select id="select-lang" name="language">
<option value="fr">
Fran&ccedil;ais
</option>
<option value="en"
selected="selected">
English
</option>
</select>
</Property>
<hr />
</Form>
</CollapsibleParagraph>
);
}
});
let BitcoinWalletSettings = React.createClass({
getInitialState() {
return WalletSettingsStore.getState();
},
componentDidMount() {
WalletSettingsStore.listen(this.onChange);
WalletSettingsActions.fetchWalletSettings();
},
componentWillUnmount() {
WalletSettingsStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
},
render() {
let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
if (this.state.walletSettings.btc_public_key) {
content = (
<Form >
<Property
name='btc_public_key'
label="Bitcoin public key"
editable={false}>
<pre className="ascribe-pre">{this.state.walletSettings.btc_public_key}</pre>
</Property>
<Property
name='btc_root_address'
label="Root Address"
editable={false}>
<pre className="ascribe-pre">{this.state.walletSettings.btc_root_address}</pre>
</Property>
<hr />
</Form>);
}
return (
<CollapsibleParagraph
title="Crypto Wallet"
show={true}
defaultExpanded={true}>
{content}
</CollapsibleParagraph>
);
}
});
let ContractSettings = React.createClass({
propTypes: {
currentUser: React.PropTypes.object
},
render() {
return (
<div>
<div>Username: {this.props.currentUser.username}</div>
<div>Email: {this.props.currentUser.email}</div>
</div>
);
}
});
let APISettings = React.createClass({
getInitialState() {
return ApplicationStore.getState();
},
componentDidMount() {
ApplicationStore.listen(this.onChange);
ApplicationActions.fetchApplication();
},
componentWillUnmount() {
ApplicationStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
},
handleCreateSuccess: function(){
ApplicationActions.fetchApplication();
let notification = new GlobalNotificationModel('Application successfully created', 'success', 5000);
GlobalNotificationActions.appendGlobalNotification(notification);
},
handleTokenRefresh: function(event){
let applicationName = event.target.getAttribute('data-id');
ApplicationActions.refreshApplicationToken(applicationName);
let notification = new GlobalNotificationModel('Token refreshed', 'success', 2000);
GlobalNotificationActions.appendGlobalNotification(notification);
},
render() {
let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
if (this.state.applications.length > -1) {
content = this.state.applications.map(function(app) {
return (
<Property
name={app.name}
label={app.name}>
<div className="row-same-height">
<div className="col-xs-6 col-xs-height col-middle">
{'Bearer ' + app.bearer_token.token}
</div>
<div className="col-xs-6 col-xs-height">
<button
className="pull-right btn btn-default btn-sm"
onClick={this.handleTokenRefresh}
data-id={app.name}>
REFRESH
</button>
</div>
</div>
</Property>);
}, this);
content = (
<div>
<Form>
{content}
<hr />
</Form>
</div>);
}
return (
<CollapsibleParagraph
title="API Integration"
show={true}
defaultExpanded={true}>
<Form
url={apiUrls.applications}
handleSuccess={this.handleCreateSuccess}>
<Property
name='name'
label='Application Name'>
<input
type="text"
placeholder="Enter the name of your app"
required/>
</Property>
<hr />
</Form>
<pre>
Usage: curl &lt;url&gt; -H 'Authorization: Bearer &lt;token&gt;'
</pre>
{content}
</CollapsibleParagraph>
);
}
});
export default SettingsContainer;

View File

@ -7,7 +7,6 @@ import EditionContainer from './components/edition_container';
let Route = Router.Route; let Route = Router.Route;
let routes = ( let routes = (
<Route name="app" handler={AscribeApp}> <Route name="app" handler={AscribeApp}>
<Route name="pieces" path="/" handler={PieceList}> <Route name="pieces" path="/" handler={PieceList}>

123
sass/ascribe_settings.scss Normal file
View File

@ -0,0 +1,123 @@
.ascribe-settings-wrapper {
width: 100%;
text-align: center;
padding-bottom: 1em;
background-color: white;
border-left: 3px solid rgba(0,0,0,0);
&div:last-of-type {
border-bottom: 1px solid rgba(0,0,0,.2);
}
&:hover{
border-left: 3px solid rgba(2, 182, 163, 0.4);
}
}
.is-hidden{
display: none;
}
.is-focused {
background-color: rgba(2, 182, 163, 0.05);
border-left: 3px solid rgba(2, 182, 163, 1)!important;
}
.is-error {
background-color: rgba(169, 68, 66, 0.05);
border-left: 3px solid rgba(169, 68, 66, 1);
div {
span {
color: rgba(169, 68, 66, 1);
font-size: 0.9em;
margin-right: 1em;
}
input, textarea {
color: #666;
}
}
&:hover{
border-left: 3px solid rgba(169, 68, 66, 1);
}
}
.is-fixed {
cursor: default;
div {
cursor: default;
span {
cursor: default;
}
input, div, pre, textarea {
cursor: default;
color: #666;
}
}
}
.ascribe-settings-property {
display: inline-block;
width: 100%;
text-align: left;
border-top: 1px solid rgba(0,0,0,.2);
padding-top: 1em;
padding-left: 1.5em;
cursor:pointer;
input, div, span, pre, textarea, select {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
span {
font-weight: normal;
font-size: 0.9em;
color: rgba(0,0,0,.7);
}
div {
margin-top: 10px;
div {
padding-left: 0;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: normal;
font-size: 1.1em;
cursor: default;
color: rgba(0, 0, 0, .7);
}
}
input, pre, textarea, select {
font-weight: 400;
font-size: 1.1em;
width:100%;
margin-top: .5em;
border: 0;
background-color: rgba(0,0,0,0);
color: #38BAAD;
padding-left: 0;
&:focus {
border:0;
outline:0;
box-shadow: none;
}
&::selection {
color: white;
background-color: rgba(0,0,0,1);
}
}
textarea{
color: #666;
margin-top: 1em;
padding: 0;
}
}