Added web and email notifications. Tests. Fire page

This commit is contained in:
vjrj 2018-01-16 16:19:29 +01:00
parent df05b33e82
commit 3bf21c8caf
32 changed files with 696 additions and 88 deletions

View file

@ -0,0 +1,12 @@
import { Meteor } from 'meteor/meteor';
import createHistory from 'history/createBrowserHistory';
const history = createHistory();
history.listen((location) => { // , action ) => {
// console.log(location.pathname);
// console.log(action); // PUSH, etc
Meteor.Piwik.trackPage(location.pathname);
});
export default history;

View file

@ -0,0 +1,70 @@
/* eslint-disable import/no-absolute-path */
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { Bert } from 'meteor/themeteorchef:bert';
import moment from 'moment';
import Notifications from '/imports/api/Notifications/Notifications';
import Push from 'push.js/bin/push.min.js';
import i18n from '/imports/startup/client/i18n';
import history from '../../components/History/History';
import { trim } from './util.js';
function process(notif) {
// No already notified
if (Push.Permission.has()) {
if (!notif.webNotified) {
Push.create(i18n.t('AppName'), {
body: `${trim(notif.content)} (${i18n.t('fireDetected', { when: moment(notif.when).fromNow() })})`,
icon: '/n-fire-marker.png',
requireInteraction: true,
onClick: function onClickFocus() {
window.focus();
this.close();
history.push('/fires');
}
});
Meteor.call('notifications.sent', notif._id, (error) => {
if (error) {
Bert.alert(error.reason, 'danger');
}
});
}
}
}
// Observe for new notifications
Notifications.find().observe({
added: function notifAdded(notif) {
process(notif);
},
changed: function notifChanged(updatedNotif, oldNotif) {
process(updatedNotif);
}
});
Meteor.startup(() => {
if (Meteor.userId()) {
Meteor.subscribe('mynotifications');
// Check for notifications not processed at startup
Notifications.find({ webNotified: null }).forEach((notif) => {
process(notif);
});
}
Tracker.autorun(() => {
if (Meteor.userId()) {
Meteor.subscribe('mynotifications');
// console.log('Started notifications listener');
if (!Push.Permission.has()) {
Push.Permission.request(() => {
// on granted
Bert.alert(i18n.t('Perfecto, recibirás notificaciones de fuegos en este equipo y también por correo cuando no estés conectado'), 'success');
}, () => {
// on denied
Bert.alert(i18n.t('No recibirás notificaciones de fuegos en este equipo, solo por correo'), 'warning');
});
}
}
});
});

View file

@ -0,0 +1,4 @@
export const trim = function trimMess(message) {
// Removing utf8 suffix and prefix characters used in telegram but not useful here in browser
return message.replace(/^🔥 /, '').replace(/:$/, '');
};

View file

@ -1,5 +1,7 @@
import React from 'react';
import { translate } from 'react-i18next';
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import Blaze from 'meteor/gadicc:blaze-react-component';
const Reconnect = props => (
@ -7,3 +9,12 @@ const Reconnect = props => (
);
export default translate([], { wait: true })(Reconnect);
if (!Meteor.isProduction) {
// We clear the console on disconnect during development
Tracker.autorun(() => {
if (Meteor.status().status === 'waiting') {
// console.clear();
}
});
}

View file

@ -4,7 +4,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Router, Switch, Route } from 'react-router-dom';
import createHistory from 'history/createBrowserHistory';
import { Grid } from 'react-bootstrap';
import { I18nextProvider } from 'react-i18next';
import { Meteor } from 'meteor/meteor';
@ -41,17 +40,13 @@ import Privacy from '../../pages/Privacy/Privacy';
import License from '../../pages/License/License';
import Credits from '../../pages/Credits/Credits';
import ReSendEmail from '../../components/ReSendEmail/ReSendEmail';
import history from '../../components/History/History';
import '../../components/NotificationsObserver/NotificationsObserver';
import FiresMap from '../../pages/FiresMap/FiresMap';
import Fires from '../../pages/Fires/Fires';
import Sandbox from '../../pages/Sandbox/Sandbox';
import './App.scss';
const history = createHistory();
history.listen((location) => { // , action ) => {
// console.log(location.pathname);
// console.log(action); // PUSH, etc
Meteor.Piwik.trackPage(location.pathname);
});
const App = props => (
/* https://react.i18next.com/components/i18nextprovider.html */
<I18nextProvider i18n={i18n}>
@ -72,9 +67,9 @@ const App = 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} />
<Route path="/fire/:id" component={Fires} {...props} />
<Public path="/signup" component={Signup} {...props} />
<Public path="/login" component={Login} {...props} />
<Route path="/logout" component={Logout} {...props} />

View file

@ -0,0 +1,116 @@
/* eslint-disable import/no-absolute-path */
/* eslint-disable react/jsx-indent-props */
/* eslint-disable react/jsx-indent */
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { withTracker } from 'meteor/react-meteor-data';
import { translate } from 'react-i18next';
import { Col, Row } from 'react-bootstrap';
import { Meteor } from 'meteor/meteor';
import { Map, CircleMarker, Circle } from 'react-leaflet';
import DefMapLayers from '/imports/ui/components/Maps/DefMapLayers';
import moment from 'moment';
import FiresCollection from '/imports/api/Fires/Fires';
class Fire extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
/* componentDidUpdate() {
* const map = this.firemap.leafletElement;
* map.invalidateSize();
* } */
handleLeafletLoad(map) {
console.log(map);
if (map) {
// map.leafletELement.invalidateSize();
// map.invalidateSize();
}
}
render() {
const { loading, fire, t } = this.props;
return (
<div className="ViewFire">
{!loading &&
<Fragment>
<h4 className="page-header">
{t('Información sobre fuego detectado el día {{when}}', { when: moment(fire.when).format('LLLL') })}
</h4>
<Row>
<Col xs={12} sm={6} md={6} lg={6} >
{(fire.type === 'modis' || fire.type === 'virrs') &&
<p>{t('Detectado por satélites de la NASA')}
</p>
}
<h5>Comentarios</h5>
<p>{t('Añade un comentario si tienes información adicional sobre este fuego (por ejemplo, si está aún activo cómo acceder a él, o si conoces el motivo por el que comenzó el fuego, o si quieres denunciar algún tipo de ilegalidad relacionada con el fuego)')}
</p>
</Col>
<Col xs={12} sm={6} md={6} lg={6} >
<Map
ref={(map) => {
this.fireMap = map;
this.handleLeafletLoad(map);
}}
animate
sleep={false}
center={[fire.lat, fire.lon]}
zoom={8}
>
<Fragment>
<CircleMarker
center={[fire.lat, fire.lon]}
color="red"
stroke={false}
fillOpacity="1"
fill
radius={3}
/>
<Circle
center={[fire.lat, fire.lon]}
color="red"
fillColor="red"
fillOpacity={0.1}
radius={fire.scan * 1000}
/>
</Fragment>
<DefMapLayers />
</Map>
</Col>
</Row>
</Fragment>
}
</div>
);
}
}
Fire.propTypes = {
t: PropTypes.func.isRequired,
loading: PropTypes.bool.isRequired,
fire: PropTypes.object // .isRequired
};
Fire.defaultProps = {
};
// export default translate([], { wait: true })(withTracker((props) => {
const FireContainer = withTracker(({ match }) => {
const fireEncrypt = match.params.id;
const subscription = Meteor.subscribe('fireFromHash', fireEncrypt);
// console.log(`Subs ready: ${subscription.ready()}, fire: ${JSON.stringify(FiresCollection.findOne())}`);
return {
loading: !subscription.ready(),
fire: FiresCollection.findOne()
};
})(Fire);
// export default FireContainer;
export default translate([], { wait: true })(FireContainer);

View file

@ -53,7 +53,7 @@ class ResetPassword extends React.Component {
if (error) {
Bert.alert(T9n.get(`error.accounts.${error.reason}`), 'danger');
} else {
history.push('/documents');
history.push('/subscriptions');
}
});
}

View file

@ -19,12 +19,12 @@ class VerifyEmail extends React.Component {
Accounts.verifyEmail(match.params.token, (error) => {
if (error) {
Bert.alert(T9n.get(`error.accounts.${error.reason}`), 'danger');
this.setState({ error: T9n.get(`error.accounts.${error.reason}`) + ". " + this.t("Por favor, inténtalo otra vez.")});
this.setState({ error: `${T9n.get(`error.accounts.${error.reason}`)}. ${this.t('Por favor, inténtalo otra vez.')}` });
// this.setState({ error: `${error.reason}. Please try again.` });
} else {
setTimeout(() => {
Bert.alert(this.t("¡Listo, gracias!"), 'success');
history.push('/documents');
Bert.alert(this.t('¡Listo, gracias!'), 'success');
history.push('/subscriptions');
}, 1500);
}
});
@ -41,7 +41,7 @@ class VerifyEmail extends React.Component {
VerifyEmail.propTypes = {
match: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
};
export default translate([], { wait: true })(VerifyEmail);