Added subs management (wip)

This commit is contained in:
vjrj 2017-12-16 18:47:08 +01:00
parent 6123977974
commit 316e14f0ea
21 changed files with 427 additions and 39 deletions

View file

@ -19,6 +19,9 @@ const AuthenticatedNavigation = ({ name, history, props }) => (
<LinkContainer className="nav-item" anchorClassName="nav-link" to="/documents">
<NavItem eventKey={5} href="/documents">Documents</NavItem>
</LinkContainer>
<LinkContainer className="nav-item" anchorClassName="nav-link" to="/documents">
<NavItem eventKey={5.01} href="/subscriptions">Subscriptions</NavItem>
</LinkContainer>
<LinkContainer className="nav-item" anchorClassName="nav-link" to="/profile">
<NavItem eventKey={5.1} href="/profile">{name}</NavItem>
</LinkContainer>
@ -30,7 +33,7 @@ const AuthenticatedNavigation = ({ name, history, props }) => (
);
AuthenticatedNavigation.propTypes = {
name: PropTypes.string.isRequired,
name: PropTypes.string.isRequired
};

View file

@ -33,7 +33,7 @@ class SelectionMap extends Component {
this.updatePosition = this.updatePosition.bind(this);
this.fit = this.fit.bind(this);
this.addScale = this.addScale.bind(this);
this.doSubs = this.doSubs.bind(this);
this.onSubs = this.onSubs.bind(this);
}
componentDidMount() {
@ -57,6 +57,13 @@ class SelectionMap extends Component {
this.fit();
}
onSubs() {
this.props.onSubs({
location: { lat: this.state.center[0], lon: this.state.center[1] },
distance: this.state.distance
});
}
getMap() {
return this.selectionMap.leafletElement;
}
@ -92,13 +99,6 @@ class SelectionMap extends Component {
Leaflet.control.graphicScale([options]).addTo(map);
}
doSubs() { // event param not used
this.props.history.push(
'/login',
{ subscription: { center: this.state.center, distance: this.state.distance } }
);
}
isValidState() {
return this.state.center && this.state.center[0] && this.state.distance;
}
@ -152,9 +152,9 @@ class SelectionMap extends Component {
<ButtonToolbar>
<Button
bsStyle="success"
onClick={event => this.doSubs(event)}
onClick={event => this.onSubs(event)}
>
{this.props.t('Subscribirme a fuegos en este rádio')}
{this.props.subsBtn}
</Button>
</ButtonToolbar>
</Control>
@ -165,11 +165,12 @@ class SelectionMap extends Component {
}
SelectionMap.propTypes = {
history: PropTypes.object.isRequired,
t: PropTypes.func.isRequired,
center: PropTypes.array,
center: PropTypes.arrayOf(PropTypes.number),
distance: PropTypes.number,
onSelection: PropTypes.func.isRequired
onSelection: PropTypes.func.isRequired,
subsBtn: PropTypes.string.isRequired,
onSubs: PropTypes.func.isRequired
};
export default translate([], { wait: true })(withTracker(props => ({

View file

@ -0,0 +1,60 @@
/* eslint-disable max-len, no-return-assign */
/* eslint-disable react/jsx-indent */
/* eslint-disable react/jsx-indent-props */
/* eslint-disable import/no-absolute-path */
import React from 'react';
import PropTypes from 'prop-types';
import { Meteor } from 'meteor/meteor';
import { Bert } from 'meteor/themeteorchef:bert';
import FireSubscription from '/imports/ui/pages/FireSubscription/FireSubscription';
import { translate } from 'react-i18next';
class SubscriptionEditor extends React.Component {
onSubs(value) {
const { t, history } = this.props;
const existingSubscription = this.props.doc && this.props.doc._id;
const methodToCall = existingSubscription ? 'subscriptions.update' : 'subscriptions.insert';
const doc = {
location: value.location,
distance: value.distance
};
if (existingSubscription) doc._id = existingSubscription;
Meteor.call(methodToCall, doc, (error, subscriptionId) => {
if (error) {
Bert.alert(error.reason, 'danger');
} else {
const confirmation = existingSubscription ? t('Suscripción actualizada') : t('Suscripción añadida');
Bert.alert(confirmation, 'success');
history.push(`/subscriptions/${subscriptionId}`);
}
});
}
render() {
const { doc, t } = this.props;
const isEdit = doc && doc._id;
return (
<FireSubscription
center={[doc.location.lat, doc.location.lon]}
distance={doc.distance}
focusInput={!isEdit}
subsBtn={isEdit ? t('Actualizar') : t('Subscribirme a fuegos en este rádio')}
onSubs={state => this.onSubs(state)}
/>
);
}
}
SubscriptionEditor.defaultProps = {
doc: { location: { lat: null, lot: null }, distance: null }
};
SubscriptionEditor.propTypes = {
doc: PropTypes.object,
t: PropTypes.func.isRequired,
history: PropTypes.object.isRequired
};
export default translate([], { wait: true })(SubscriptionEditor);

View file

@ -28,6 +28,10 @@ import Documents from '../../pages/Documents/Documents';
import NewDocument from '../../pages/NewDocument/NewDocument';
import ViewDocument from '../../pages/ViewDocument/ViewDocument';
import EditDocument from '../../pages/EditDocument/EditDocument';
import Subscriptions from '../../pages/Subscriptions/Subscriptions';
import NewSubscription from '../../pages/NewSubscription/NewSubscription';
import ViewSubscription from '../../pages/ViewSubscription/ViewSubscription';
import EditSubscription from '../../pages/EditSubscription/EditSubscription';
import Signup from '../../pages/Signup/Signup';
import Login from '../../pages/Login/Login';
import Logout from '../../pages/Logout/Logout';
@ -69,13 +73,20 @@ const App = props => (
<Authenticated exact path="/documents/new" component={NewDocument} {...props} />
<Authenticated exact path="/documents/:_id" component={ViewDocument} {...props} />
<Authenticated exact path="/documents/:_id/edit" component={EditDocument} {...props} />
<Authenticated exact path="/subscriptions" component={Subscriptions} {...props} />
<Authenticated exact path="/subscriptions/new" component={NewSubscription} {...props} />
<Authenticated exact path="/subscriptions/:_id" component={ViewSubscription} {...props} />
<Authenticated exact path="/subscriptions/:_id/edit" component={EditSubscription} {...props} />
<Authenticated exact path="/profile" component={Profile} {...props} />
<Route path="/fires" component={FiresMap} {...props} />
<Public path="/signup" component={Signup} {...props} />
<Public path="/login" component={Login} {...props} />
<Route path="/logout" component={Logout} {...props} />
<Route path="/sandbox" component={Sandbox} {...props} />
<Route path="/subscriptions" render={props => <FireSubscription focusInput {...props} />} />
{/* <Route path="/subscriptions" render={props => <FireSubscription focusInput {...props} />} /> */}
<Route name="verify-email" path="/verify-email/:token" component={VerifyEmail} />
<Route name="recover-password" path="/recover-password" component={RecoverPassword} />
<Route name="reset-password" path="/reset-password/:token" component={ResetPassword} />

View file

@ -0,0 +1,33 @@
import React from 'react';
import PropTypes from 'prop-types';
import { createContainer } from 'meteor/react-meteor-data';
import { Meteor } from 'meteor/meteor';
import Subscriptions from '../../../api/Subscriptions/Subscriptions';
import SubscriptionEditor from '../../components/SubscriptionEditor/SubscriptionEditor';
import NotFound from '../NotFound/NotFound';
const EditSubscription = ({ doc, history }) => (doc ? (
<div className="EditSubscription">
<h4 className="page-header">{`Editing "${doc.title}"`}</h4>
<SubscriptionEditor doc={doc} history={history} />
</div>
) : <NotFound />);
EditSubscription.defaultProps = {
doc: null
};
EditSubscription.propTypes = {
doc: PropTypes.object,
history: PropTypes.object.isRequired
};
export default createContainer(({ match }) => {
const subscriptionId = match.params._id;
const subscription = Meteor.subscribe('subscriptions.view', subscriptionId);
return {
loading: !subscription.ready(),
doc: Subscriptions.findOne(subscriptionId)
};
}, EditSubscription);

View file

@ -3,7 +3,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Row, Col } from 'react-bootstrap';
import { Trans, translate } from 'react-i18next';
import { translate } from 'react-i18next';
import DistanceSlider from '/imports/ui/components/DistanceSlider/DistanceSlider';
import SelectionMap from '/imports/ui/components/SelectionMap/SelectionMap';
import Gkeys from '/imports/startup/client/Gkeys';
@ -14,7 +14,9 @@ class FireSubscription extends React.Component {
constructor(props) {
super(props);
this.state = {
init: false
init: false,
center: this.props.center,
distance: this.props.distance
};
// console.log(this.props.location.state);
}
@ -27,7 +29,7 @@ class FireSubscription extends React.Component {
}
onAutocompleteChange(value) {
this.setState({ lat: value.lat, lng: value.lng });
this.setState({ center: [value.lat, value.lng] });
}
onSliderChange(value) {
@ -35,11 +37,15 @@ class FireSubscription extends React.Component {
}
onSelection(value) {
this.setState({ lat: value.lat, lng: value.lng, distance: value.distance });
this.setState({ center: [value.lat, value.lng], distance: value.distance });
}
onSubs(value) {
this.props.onSubs(value);
}
centerOnUserLocation(value) {
this.setState({ lat: value.center[0], lng: value.center[1] });
this.setState({ center: value.center });
}
render() {
@ -54,7 +60,6 @@ class FireSubscription extends React.Component {
<Row>
<Col xs={12} sm={12} md={6} lg={6} >
<div>
<h4 className="page-header"><Trans parent="span">Suscríbete a alertas de fuegos</Trans></h4>
<SubsAutocomplete
focusInput={this.props.focusInput}
onChange={value => this.onAutocompleteChange(value)}
@ -70,10 +75,11 @@ class FireSubscription extends React.Component {
</Row>
<Row className="align-items-center justify-content-center">
<SelectionMap
center={[this.state.lat, this.state.lng]}
center={this.state.center}
distance={this.state.distance}
history={this.props.history}
subsBtn={this.props.subsBtn}
onSelection={state => this.onSelection(state)}
onSubs={state => this.onSubs(state)}
/>
</Row>
</div>
@ -82,8 +88,12 @@ class FireSubscription extends React.Component {
}
FireSubscription.propTypes = {
history: PropTypes.object.isRequired,
focusInput: PropTypes.bool.isRequired
t: PropTypes.func.isRequired,
center: PropTypes.arrayOf(PropTypes.number),
distance: PropTypes.number,
focusInput: PropTypes.bool.isRequired,
subsBtn: PropTypes.string.isRequired,
onSubs: PropTypes.func.isRequired
};
export default translate([], { wait: true })(FireSubscription);

View file

@ -12,7 +12,7 @@ import ReactResizeDetector from 'react-resize-detector';
import _ from 'lodash';
import { ScrollToTopOnMount, SectionsContainer, Section } from 'react-fullpage';
import 'html5-device-mockups/dist/device-mockups.min.css';
import FireSubscription from '/imports/ui/pages/FireSubscription/FireSubscription';
import SubscriptionEditor from '/imports/ui/components/SubscriptionEditor/SubscriptionEditor';
import FiresMap from '../FiresMap/FiresMap';
import './Index.scss';
@ -218,7 +218,8 @@ class Index extends Component {
<Section className="">
<div className="container">
<FireSubscription history={this.props.history} focusInput={false} />
<h4 className="page-header"><Trans parent="span">Suscríbete a alertas de fuegos</Trans></h4>
<SubscriptionEditor history={this.props.history} />
</div>
<div className="overlay" />
</Section>

View file

@ -0,0 +1,17 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Trans, translate } from 'react-i18next';
import SubscriptionEditor from '../../components/SubscriptionEditor/SubscriptionEditor';
const NewSubscription = ({ history }) => (
<div className="NewSubscription">
<h4 className="page-header"><Trans>Nueva suscripción</Trans></h4>
<SubscriptionEditor history={history} />
</div>
);
NewSubscription.propTypes = {
history: PropTypes.object.isRequired
};
export default translate([], { wait: true })(NewSubscription);

View file

@ -0,0 +1,105 @@
/* eslint-disable react/jsx-indent-props */
/* eslint-disable import/no-absolute-path */
/* eslint-disable react/jsx-indent */
import React from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import { Table, Alert, Button, Row } from 'react-bootstrap';
import { timeago, monthDayYearAtTime } from '@cleverbeagle/dates';
import { Meteor } from 'meteor/meteor';
import { withTracker } from 'meteor/react-meteor-data';
import { Trans, translate } from 'react-i18next';
import { Bert } from 'meteor/themeteorchef:bert';
import UserSubsToFiresCollection from '/imports/api/Subscriptions/Subscriptions';
import Loading from '../../components/Loading/Loading';
import './Subscriptions.scss';
const handleRemove = (subscriptionId) => {
if (confirm('Are you sure? This is permanent!')) {
Meteor.call('subscriptions.remove', subscriptionId, (error) => {
if (error) {
Bert.alert(error.reason, 'danger');
} else {
Bert.alert('Subscription deleted!', 'success');
}
});
}
};
const Subscriptions = ({
loading,
subscriptions,
match,
history
}) => (!loading ? (
<div className="Subscriptions">
<div className="page-header clearfix">
<h4 className="pull-left"><Trans>Suscripciones a fuegos en áreas de mi interés</Trans></h4>
<Link className="btn btn-success pull-right" to={`${match.url}/new`}><Trans>Añadir suscripción</Trans></Link>
</div>
<br />
{subscriptions.length ?
<Table responsive>
<thead>
<tr>
<th>Location</th>
<th>Last Updated</th>
<th>Created</th>
<th />
<th />
</tr>
</thead>
<tbody>
{subscriptions.map(({
_id,
location,
createdAt,
updatedAt
}) => (
<tr key={_id}>
<td>{location.lat},{location.lon}</td>
<td>{timeago(updatedAt)}</td>
<td>{monthDayYearAtTime(createdAt)}</td>
<td>
<Button
bsStyle="primary"
onClick={() => history.push(`${match.url}/${_id}`)}
block
>View
</Button>
</td>
<td>
<Button
bsStyle="danger"
onClick={() => handleRemove(_id)}
block
>Delete
</Button>
</td>
</tr>
))}
</tbody>
</Table> :
<Alert bsStyle="warning"><Trans>Todavía sin suscriptiones</Trans></Alert>
}
</div>
) : <Loading />);
Subscriptions.propTypes = {
loading: PropTypes.bool.isRequired,
subscriptions: PropTypes.arrayOf(PropTypes.object).isRequired,
match: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
};
export default translate([], { wait: true })(withTracker(() => {
const subscription = Meteor.subscribe('subscriptions');
// console.log(UserSubsToFiresCollection.find().fetch());
return {
loading: !subscription.ready(),
subscriptions: UserSubsToFiresCollection.find().fetch()
};
})(Subscriptions));

View file

@ -0,0 +1,3 @@
.Subscriptions table tbody tr td {
vertical-align: middle;
}

View file

@ -0,0 +1,65 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ButtonToolbar, ButtonGroup, Button } from 'react-bootstrap';
import { createContainer } from 'meteor/react-meteor-data';
import { Meteor } from 'meteor/meteor';
import { Bert } from 'meteor/themeteorchef:bert';
import Subscriptions from '../../../api/Subscriptions/Subscriptions';
import NotFound from '../NotFound/NotFound';
import Loading from '../../components/Loading/Loading';
const handleRemove = (subscriptionId, history) => {
if (confirm('Are you sure? This is permanent!')) {
Meteor.call('subscriptions.remove', subscriptionId, (error) => {
if (error) {
Bert.alert(error.reason, 'danger');
} else {
Bert.alert('Subscription deleted!', 'success');
history.push('/subscriptions');
}
});
}
};
const renderSubscription = (doc, match, history) => (doc ? (
<div className="ViewSubscription">
<div className="page-header clearfix">
<h4 className="pull-left">{ doc && doc.title }</h4>
<ButtonToolbar className="pull-right">
<ButtonGroup bsSize="small">
<Button onClick={() => history.push(`${match.url}/edit`)}>Edit</Button>
<Button onClick={() => handleRemove(doc._id, history)} className="text-danger">
Delete
</Button>
</ButtonGroup>
</ButtonToolbar>
</div>
{ doc && doc.body }
</div>
) : <NotFound />);
const ViewSubscription = ({
loading,
doc,
match,
history
}) => (
!loading ? renderSubscription(doc, match, history) : <Loading />
);
ViewSubscription.propTypes = {
loading: PropTypes.bool.isRequired,
doc: PropTypes.object,
match: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
};
export default createContainer(({ match }) => {
const subscriptionId = match.params._id;
const subscription = Meteor.subscribe('subscriptions.view', subscriptionId);
return {
loading: !subscription.ready(),
doc: Subscriptions.findOne(subscriptionId)
};
}, ViewSubscription);