Added subs management (wip)
This commit is contained in:
parent
6123977974
commit
316e14f0ea
21 changed files with 427 additions and 39 deletions
|
|
@ -48,3 +48,4 @@ ostrio:mailer # mailer
|
|||
ostrio:meteor-root
|
||||
mizzao:user-status
|
||||
babrahams:constellation
|
||||
percolate:migrations
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ peerlibrary:assert@0.2.5
|
|||
peerlibrary:fiber-utils@0.6.0
|
||||
peerlibrary:reactive-mongo@0.1.1
|
||||
peerlibrary:server-autorun@0.5.2
|
||||
percolate:migrations@1.0.2
|
||||
promise@0.10.0
|
||||
raix:eventemitter@0.1.3
|
||||
random@1.0.10
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { Mongo } from 'meteor/mongo';
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
|
||||
const ActiveFires = new Mongo.Collection('activefires');
|
||||
const ActiveFires = new Mongo.Collection('activefires', { idGeneration: 'MONGO' });
|
||||
|
||||
ActiveFires.allow({
|
||||
insert: () => false,
|
||||
|
|
|
|||
|
|
@ -3,18 +3,18 @@
|
|||
import { Mongo } from 'meteor/mongo';
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
|
||||
const FireAlerts = new Mongo.Collection('avisosfuego');
|
||||
const FireAlerts = new Mongo.Collection('avisosfuego', { idGeneration: 'MONGO' });
|
||||
|
||||
FireAlerts.allow({
|
||||
insert: () => false,
|
||||
update: () => false,
|
||||
remove: () => false,
|
||||
remove: () => false
|
||||
});
|
||||
|
||||
FireAlerts.deny({
|
||||
insert: () => true,
|
||||
update: () => true,
|
||||
remove: () => true,
|
||||
remove: () => true
|
||||
});
|
||||
|
||||
/* Sample:
|
||||
|
|
@ -42,9 +42,8 @@ FireAlerts.schema = new SimpleSchema({
|
|||
location: Object,
|
||||
'location.lat': SimpleSchema.Integer,
|
||||
'location.lon': SimpleSchema.Integer,
|
||||
aviso: Date,
|
||||
}
|
||||
);
|
||||
aviso: Date
|
||||
});
|
||||
|
||||
FireAlerts.attachSchema(FireAlerts.schema);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { Mongo } from 'meteor/mongo';
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
|
||||
const Subscriptions = new Mongo.Collection('subscriptions');
|
||||
const Subscriptions = new Mongo.Collection('subscriptions', { idGeneration: 'MONGO' });
|
||||
|
||||
Subscriptions.allow({
|
||||
insert: () => false,
|
||||
|
|
@ -41,9 +41,10 @@ Subscriptions.schema = new SimpleSchema({
|
|||
location: Object,
|
||||
'location.lat': SimpleSchema.Integer,
|
||||
'location.lon': SimpleSchema.Integer,
|
||||
distance: Number
|
||||
distance: Number,
|
||||
owner: String
|
||||
});
|
||||
|
||||
Subscriptions.attachSchema(Subscriptions.schema);
|
||||
// Subscriptions.attachSchema(Subscriptions.schema);
|
||||
|
||||
export default Subscriptions;
|
||||
|
|
|
|||
53
imports/api/Subscriptions/methods.js
Normal file
53
imports/api/Subscriptions/methods.js
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import { Meteor } from 'meteor/meteor';
|
||||
import { check, Match } from 'meteor/check';
|
||||
import Subscriptions from './Subscriptions';
|
||||
import rateLimit from '../../modules/rate-limit';
|
||||
|
||||
Meteor.methods({
|
||||
'subscriptions.insert': function subscriptionsInsert(doc) {
|
||||
check(doc, {
|
||||
location: Match.ObjectIncluding({ lat: Number, lon: Number }),
|
||||
distance: Number
|
||||
});
|
||||
|
||||
try {
|
||||
return Subscriptions.insert({ owner: this.userId, ...doc });
|
||||
} catch (exception) {
|
||||
throw new Meteor.Error('500', exception);
|
||||
}
|
||||
},
|
||||
'subscriptions.update': function subscriptionsUpdate(doc) {
|
||||
check(doc, {
|
||||
_id: String,
|
||||
location: Match.ObjectIncluding({ lat: Number, lon: Number }),
|
||||
distance: Number
|
||||
});
|
||||
|
||||
try {
|
||||
const subscriptionId = doc._id;
|
||||
Subscriptions.update(subscriptionId, { $set: doc });
|
||||
return subscriptionId; // Return _id so we can redirect to subscription after update.
|
||||
} catch (exception) {
|
||||
throw new Meteor.Error('500', exception);
|
||||
}
|
||||
},
|
||||
'subscriptions.remove': function subscriptionsRemove(subscriptionId) {
|
||||
check(subscriptionId, Meteor.Collection.ObjectID);
|
||||
|
||||
try {
|
||||
return Subscriptions.remove(subscriptionId);
|
||||
} catch (exception) {
|
||||
throw new Meteor.Error('500', exception);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
rateLimit({
|
||||
methods: [
|
||||
'subscriptions.insert',
|
||||
'subscriptions.update',
|
||||
'subscriptions.remove'
|
||||
],
|
||||
limit: 5,
|
||||
timeRange: 1000
|
||||
});
|
||||
|
|
@ -34,3 +34,13 @@ Meteor.publishTransformed('userSubsToFires', function transform() {
|
|||
return doc;
|
||||
});
|
||||
});
|
||||
|
||||
Meteor.publish('subscriptions', function subscriptions() {
|
||||
return Subscriptions.find({ owner: this.userId });
|
||||
});
|
||||
|
||||
// Note: subscriptions.view is also used when editing an existing subscription.
|
||||
Meteor.publish('subscriptions.view', function subscriptionsView(subscriptionId) {
|
||||
check(subscriptionId, String);
|
||||
return Subscriptions.find({ _id: subscriptionId, owner: this.userId });
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,5 +10,7 @@ import '../../api/Utility/server/methods';
|
|||
|
||||
import '../../api/ActiveFires/server/publications';
|
||||
import '../../api/FireAlerts/server/publications';
|
||||
|
||||
import '../../api/Subscriptions/methods';
|
||||
import '../../api/Subscriptions/server/publications';
|
||||
// TODO add rate-limit to these publications
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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 => ({
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -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} />
|
||||
|
|
|
|||
33
imports/ui/pages/EditSubscription/EditSubscription.js
Normal file
33
imports/ui/pages/EditSubscription/EditSubscription.js
Normal 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);
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
17
imports/ui/pages/NewSubscription/NewSubscription.js
Normal file
17
imports/ui/pages/NewSubscription/NewSubscription.js
Normal 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);
|
||||
105
imports/ui/pages/Subscriptions/Subscriptions.js
Normal file
105
imports/ui/pages/Subscriptions/Subscriptions.js
Normal 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));
|
||||
3
imports/ui/pages/Subscriptions/Subscriptions.scss
Normal file
3
imports/ui/pages/Subscriptions/Subscriptions.scss
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
.Subscriptions table tbody tr td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
65
imports/ui/pages/ViewSubscription/ViewSubscription.js
Normal file
65
imports/ui/pages/ViewSubscription/ViewSubscription.js
Normal 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);
|
||||
|
|
@ -104,5 +104,11 @@
|
|||
"Imágenes capturadas por los satélites de la NASA muestran el humo de grandes incendios que se extienden sobre el Océano Pacífico. La actividad del fuego está delineada en rojo.":
|
||||
"Images captured by NASA satellites show the smoke of large fires spreading over the Pacific Ocean. Fire activity is outlined in red.",
|
||||
"Colaboración masiva contra los incendios":
|
||||
"Corwdsourcing against wildfires"
|
||||
"Corwdsourcing against wildfires",
|
||||
"Todavía sin subscriptiones":
|
||||
"No subscriptions yet",
|
||||
"Suscripción añadida":
|
||||
"Subscription added",
|
||||
"Suscripción actualizada":
|
||||
"Subscription updated"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -131,5 +131,11 @@
|
|||
"Imágenes capturadas por los satélites de la NASA muestran el humo de grandes incendios que se extienden sobre el Océano Pacífico. La actividad del fuego está delineada en rojo.":
|
||||
"Imágenes capturadas por los satélites de la NASA muestran el humo de grandes incendios que se extienden sobre el Océano Pacífico. La actividad del fuego está delineada en rojo.",
|
||||
"Colaboración masiva contra los incendios":
|
||||
"Colaboración masiva contra los incendios"
|
||||
"Colaboración masiva contra los incendios",
|
||||
"Todavía sin subscriptiones":
|
||||
"Todavía sin subscriptiones",
|
||||
"Suscripción añadida":
|
||||
"Suscripción añadida",
|
||||
"Suscripción actualizada":
|
||||
"Suscripción actualizada"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue