More work with titles & descriptions

This commit is contained in:
vjrj 2018-02-07 15:27:12 +01:00
parent a1138c8c43
commit 464af6b9d1
21 changed files with 178 additions and 51 deletions

View file

@ -24,11 +24,29 @@ const options = {
const geocoder = NodeGeocoder(options);
const unseal = async (obj) => {
try {
const unsealed = await urlEnc.decrypt(obj);
return unsealed;
} catch (error) {
// console.warn(error);
return undefined;
}
};
const unsealW = obj => unseal(obj);
Meteor.publish('fireFromHash', function fireFromHash(fireEnc) {
check(fireEnc, String);
try {
// console.log(fireEnc);
const unsealed = Promise.await(urlEnc.decrypt(fireEnc));
// const unsealed = Promise.await(urlEnc.decrypt(fireEnc));
const unsealed = Promise.await(unsealW(fireEnc));
if (unsealed === undefined) {
console.info(`Wrong fire: ${fireEnc}`);
// https://guide.meteor.com/data-loading.html
return this.ready();
}
const w = unsealed.when;
unsealed.when = new Date(w);
const c = unsealed.createdAt;
@ -36,7 +54,6 @@ Meteor.publish('fireFromHash', function fireFromHash(fireEnc) {
const u = unsealed.updatedAt;
unsealed.updatedAt = !u ? new Date() : new Date(u);
// console.log(unsealed);
// FIXME:
if (typeof unsealed.confidence === 'string') {
unsealed.confidence = Number.parseInt(unsealed.confidence, 10);
@ -55,7 +72,7 @@ Meteor.publish('fireFromHash', function fireFromHash(fireEnc) {
}
// console.log(unsealed.address);
} catch (reve) {
console.error(reve);
console.warn(reve);
}
}
if (fire.count() === 0) {
@ -65,9 +82,9 @@ Meteor.publish('fireFromHash', function fireFromHash(fireEnc) {
}
return findFire(unsealed);
/* console.log(`fires: ${fire.count()}`);
* return fire; */
* return fire; */
} catch (e) {
console.error(e);
throw new Meteor.Error('500', e);
console.warn(e);
return this.ready();
}
});

View file

@ -0,0 +1,4 @@
/* eslint-env jquery */
// remove static description so it's not duplicated
$('meta[name=description]').remove();

View file

@ -15,7 +15,7 @@ const backOpts = {
jsonIndent: 2
};
const forceDebug = true;
const forceDebug = false;
const shouldDebug = (forceDebug && !Meteor.isProduction);
const i18nOpts = {

View file

@ -0,0 +1,13 @@
import { Meteor } from 'meteor/meteor';
import { ReactiveVar } from 'meteor/reactive-var';
export const location = new ReactiveVar();
export const currentLocation = () => {
if (Meteor.isClient) {
return window.location.pathname;
}
return location.get();
};
export const isHome = () => currentLocation() === '/';

View file

@ -14,6 +14,7 @@ import { Roles } from 'meteor/alanning:roles';
import Blaze from 'meteor/gadicc:blaze-react-component';
// i18n
import i18n, { i18nReady } from '/imports/startup/client/i18n';
import '/imports/startup/client/meta';
import '/imports/startup/client/ravenLogger';
import '/imports/startup/client/geolocation';
import '/imports/startup/client/piwik-start.js';

View file

@ -25,21 +25,25 @@ class Fire extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: props.loading,
notfound: props.notfound,
when: props.when
};
}
componentWillReceiveProps(nextProps) {
if (this.props.when !== nextProps.when) {
if (this.props.when !== nextProps.when || this.props.loading !== nextProps.loading || this.props.notfound !== nextProps.notfound) {
// console.log(`Next when ${nextProps.when}`);
this.setState({
loading: nextProps.loading,
notfound: nextProps.notfound,
when: nextProps.when
});
}
}
shouldComponentUpdate(nextProps, nextState) {
return !(nextState.when === this.state.when);
return !(nextState.when === this.state.when && nextState.loading === this.state.loading && this.state.notfound === nextState.notfound);
}
onTypeSelect(key) {
@ -55,23 +59,27 @@ class Fire extends React.Component {
}
render() {
const { loading, fire, t } = this.props;
const {
notfound, loading, fire, t
} = this.props;
/* console.log(`loading fire: ${loading}`);
* console.log(`Not found fire: ${notfound}`); */
if (fire && fire.when) {
this.dateLongFormat = dateLongFormat(fire.when);
this.title = fire.address ?
t('Información adicional sobre fuego detectado en {{where}} el {{when}}', { where: fire.address, when: this.dateLongFormat }) :
t('Información adicional sobre fuego detectado el {{when}}', { when: this.dateLongFormat });
}
const title = fire.address ?
t('Información adicional sobre fuego detectado en {{where}} el {{when}}', { where: fire.address, when: this.dateLongFormat }) :
t('Información adicional sobre fuego detectado el {{when}}', { when: this.dateLongFormat });
return (fire ? (
return (fire && !loading ? (
<div className="ViewFire">
<Helmet>
<meta charSet="utf-8" />
<title>{t('AppName')}: {t('Información adicional sobre fuego')}</title>
<meta name="description" content={title} />
<meta name="description" content={this.title} />
</Helmet>
{!loading &&
<Fragment>
<h4 className="page-header">{title}</h4>
<h4 className="page-header">{this.title}</h4>
<Map
ref={(map) => {
this.fireMap = map;
@ -147,13 +155,14 @@ class Fire extends React.Component {
</Fragment>
}
</div>
) : <NotFound />);
) : <Fragment>{ notfound && <NotFound /> }</Fragment>);
}
}
Fire.propTypes = {
t: PropTypes.func.isRequired,
loading: PropTypes.bool.isRequired,
notfound: PropTypes.bool.isRequired,
when: PropTypes.instanceOf(Date),
fire: PropTypes.object
};
@ -167,10 +176,15 @@ 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())}`);
const loading = !subscription.ready();
const notfound = !loading && FiresCollection.find().count() === 0;
/* console.log(`loading fire: ${loading}`);
* console.log(`Not found fire: ${notfound}`); */
return {
loading: !subscription.ready(),
loading,
fire: FiresCollection.findOne(),
when: subscription.ready() ? FiresCollection.findOne().when : null
notfound,
when: subscription.ready() && FiresCollection.findOne() ? FiresCollection.findOne().when : null
};
})(Fire);

View file

@ -29,6 +29,7 @@ import FireAlertsCollection from '/imports/api/FireAlerts/FireAlerts';
import SiteSettings from '/imports/api/SiteSettings/SiteSettings';
import UserSubsToFiresCollection from '/imports/api/Subscriptions/Subscriptions';
import { isNotHomeAndMobile, isChrome } from '/imports/ui/components/Utils/isMobile';
import { isHome } from '/imports/ui/components/Utils/location';
import './FiresMap.scss';
@ -110,18 +111,17 @@ class FiresMap extends React.Component {
}
handleLeafletLoad(map) {
// console.log(map);
if (map) {
// console.log('Firesmap loading');
if (map && map.leafletElement) {
const lmap = map.leafletElement;
try {
const bounds = this.getMap().getBounds();
const bounds = lmap.getBounds();
mapSize.set([bounds.getNorthEast(), bounds.getSouthWest()]);
if (!this.state.scaleAdded) {
this.addScale();
this.state.scaleAdded = true;
}
} catch (e) {
console.log('Failed to set map bounds and scale');
console.warn('Failed to set map bounds and scale');
}
this.state.union = subsUnion(this.state.union, {
map,
@ -134,15 +134,14 @@ class FiresMap extends React.Component {
render() {
const { t } = this.props;
console.log(`Rendering ${this.props.loading ? 'loading' : 'LOADED'} map ${this.props.activefires.length + this.props.firealerts.length} of ${this.props.activefirestotal} total. Subs users ready ${this.props.subsready}, reactive ${this.state.viewport.zoom >= MAXZOOMREACTIVE}`);
console.log(`Rendering ${this.props.loading ? 'loading' : 'LOADED'} map ${this.props.activefires.length + this.props.firealerts.length} of ${this.props.activefirestotal} total. Subs users ready ${this.props.subsready} (${this.props.userSubs.length}), reactive ${this.state.viewport.zoom >= MAXZOOMREACTIVE}`);
return (
/* Large number of markers:
https://stackoverflow.com/questions/43015854/large-dataset-of-markers-or-dots-in-leaflet/43019740#43019740 */
<div
ref={(divElement) => { this.divElement = divElement; }}
>
{ window.location.pathname !== '/' &&
{ !isHome() &&
<Helmet>
<title>{t('AppName')}: {t('Fuegos activos')}</title>
<meta name="description" content={t('Fuegos activos en el mundo actualizados en tiempo real')} />
@ -199,7 +198,7 @@ class FiresMap extends React.Component {
onClick={this.onClickReset}
viewport={this.state.viewport}
onViewportChanged={this.onViewportChanged}
sleep={window.location.pathname === '/' && !isChrome}
sleep={isHome() && !isChrome}
sleepTime={10750}
wakeTime={750}
sleepNote

View file

@ -6,6 +6,7 @@ import { Row, FormGroup, ControlLabel, Button } from 'react-bootstrap';
import { Link } from 'react-router-dom';
import { Meteor } from 'meteor/meteor';
import { Bert } from 'meteor/themeteorchef:bert';
import { Helmet } from 'react-helmet';
import { translate } from 'react-i18next';
import { T9n } from 'meteor-accounts-t9n';
import { testId } from '/imports/ui/components/Utils/TestUtils';
@ -68,6 +69,9 @@ class Login extends React.Component {
render() {
return (
<div className="Login">
<Helmet>
<title>{this.t('AppName')}: {this.t('Iniciar sesión')}</title>
</Helmet>
<Row className="align-items-center justify-content-center">
<Col xs={12} sm={6} md={5} lg={4}>
<h4 className="page-header">{this.t('Iniciar sesión')}</h4>

View file

@ -1,6 +1,8 @@
import React from 'react';
import { Meteor } from 'meteor/meteor';
import Icon from '../../components/Icon/Icon';
import { translate, Trans } from 'react-i18next';
import { Helmet } from 'react-helmet';
import './Logout.scss';
@ -12,6 +14,9 @@ class Logout extends React.Component {
render() {
return (
<div className="Logout">
<Helmet>
<title>{this.props.t('AppName')}: {this.props.t('Logout')}</title>
</Helmet>
<h1><Trans parent="span">Gracias por Participar</Trans></h1>
<p><Trans parent="span">También puedes seguirnos en la web</Trans></p>
<ul className="FollowUsElsewhere">

View file

@ -1,9 +1,13 @@
import React from 'react';
import { Alert } from 'react-bootstrap';
import { translate, Trans } from 'react-i18next';
import { Helmet } from 'react-helmet';
const NotFound = () => (
<div className="NotFound">
<Helmet>
<title>This page doesn't exist"</title>
</Helmet>
<Alert bsStyle="danger">
<p>
<Trans i18nKey="not-found">Upppps: Esta página no existe</Trans>

View file

@ -15,7 +15,6 @@ const Page = ({ title, subtitle, content }) => (
<Helmet>
<meta charSet="utf-8" />
<title>{i18n.t('AppName')}: {title}</title>
<meta name="description" content={content.substr(0, 100).lastIndexOf(' ')} />
</Helmet>
<PageHeader title={title} subtitle={subtitle} />
<Content content={content} />

View file

@ -5,6 +5,7 @@ import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import { Accounts } from 'meteor/accounts-base';
import { Bert } from 'meteor/themeteorchef:bert';
import { Helmet } from 'react-helmet';
import AccountPageFooter from '../../components/AccountPageFooter/AccountPageFooter';
import validate from '../../../modules/validate';
import { translate } from 'react-i18next';
@ -24,16 +25,16 @@ class RecoverPassword extends React.Component {
rules: {
emailAddress: {
required: true,
email: true,
},
email: true
}
},
messages: {
emailAddress: {
required: this.t("Necesitamos un correo aquí."),
email: this.t("¿Es este correo correcto?"),
},
required: this.t('Necesitamos un correo aquí.'),
email: this.t('¿Es este correo correcto?')
}
},
submitHandler() { component.handleSubmit(); },
submitHandler() { component.handleSubmit(); }
});
}
@ -46,7 +47,7 @@ class RecoverPassword extends React.Component {
if (error) {
Bert.alert(T9n.get(`error.accounts.${error.reason}`), 'danger');
} else {
Bert.alert(t("checkResetEmail", {email: email}), 'success');
Bert.alert(t('checkResetEmail', { email }), 'success');
history.push('/login');
}
});
@ -55,14 +56,17 @@ class RecoverPassword extends React.Component {
render() {
return (<div className="RecoverPassword">
<Row className="align-items-center justify-content-center">
<Helmet>
<title>{this.t('AppName')}: {this.t('Recupera tu contraseña')}</title>
</Helmet>
<Col xs={12} sm={6} md={5} lg={4}>
<h4 className="page-header">{this.t("Recupera tu contraseña")}</h4>
<h4 className="page-header">{this.t('Recupera tu contraseña')}</h4>
<Alert bsStyle="info">
{this.t("Introduce tu correo abajo para recibir un enlace para resetear tu contraseña.")}
{this.t('Introduce tu correo abajo para recibir un enlace para resetear tu contraseña.')}
</Alert>
<form ref={form => (this.form = form)} onSubmit={event => event.preventDefault()}>
<FormGroup>
<ControlLabel>{this.t("Correo electrónico")}</ControlLabel>
<ControlLabel>{this.t('Correo electrónico')}</ControlLabel>
<input
type="email"
name="emailAddress"
@ -70,9 +74,9 @@ class RecoverPassword extends React.Component {
className="form-control"
/>
</FormGroup>
<Button type="submit" bsStyle="success">{this.t("Recupera tu contraseña")}</Button>
<Button type="submit" bsStyle="success">{this.t('Recupera tu contraseña')}</Button>
<AccountPageFooter>
<p>{this.t("¿Recuerdas tu contraseña?")} <Link to="/login">{this.t("Iniciar sesión")}</Link>.</p>
<p>{this.t('¿Recuerdas tu contraseña?')} <Link to="/login">{this.t('Iniciar sesión')}</Link>.</p>
</AccountPageFooter>
</form>
</Col>
@ -82,7 +86,7 @@ class RecoverPassword extends React.Component {
}
RecoverPassword.propTypes = {
history: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
};
export default translate([], { wait: true })(RecoverPassword);

View file

@ -4,6 +4,7 @@ import { Row, Alert, FormGroup, ControlLabel, Button } from 'react-bootstrap';
import Col from '../../components/Col/Col';
import { Accounts } from 'meteor/accounts-base';
import { Bert } from 'meteor/themeteorchef:bert';
import { Helmet } from 'react-helmet';
import validate from '../../../modules/validate';
import { translate } from 'react-i18next';
import { T9n } from 'meteor-accounts-t9n';
@ -61,6 +62,9 @@ class ResetPassword extends React.Component {
render() {
return (<div className="ResetPassword">
<Row className="align-items-center justify-content-center">
<Helmet>
<title>{this.t('AppName')}: {this.t('Resetea tu contraseña')}</title>
</Helmet>
<Col xs={12} sm={6} md={4}>
<h4 className="page-header">{this.t("Resetea tu contraseña")}</h4>
<Alert bsStyle="info">

View file

@ -1,22 +1,24 @@
/* eslint-disable react/jsx-indent-props */
/* eslint-disable import/no-absolute-path */
import React from 'react';
import { Row, FormGroup, ControlLabel, Button, Checkbox } from 'react-bootstrap';
import Col from '../../components/Col/Col';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import { Bert } from 'meteor/themeteorchef:bert';
import { translate, Trans } from 'react-i18next';
import { T9n } from 'meteor-accounts-t9n';
import { Helmet } from 'react-helmet';
import { testId } from '/imports/ui/components/Utils/TestUtils';
import Col from '../../components/Col/Col';
import Icon from '../../components/Icon/Icon';
import OAuthLoginButtons from '../../components/OAuthLoginButtons/OAuthLoginButtons';
import InputHint from '../../components/InputHint/InputHint';
import AccountPageFooter from '../../components/AccountPageFooter/AccountPageFooter';
import validate from '../../../modules/validate';
import './Signup.scss';
import { translate, Trans } from 'react-i18next';
import { T9n } from 'meteor-accounts-t9n';
class Signup extends React.Component {
constructor(props) {
@ -120,6 +122,9 @@ class Signup extends React.Component {
const { t, history } = this.props;
return (<div className="Signup">
<Row className="align-items-center justify-content-center">
<Helmet>
<title>{this.t('AppName')}: {this.t('Registrarse')}</title>
</Helmet>
<Col xs={12} sm={6} md={5} lg={4}>
<h4 className="page-header">{t('Registrarse')}</h4>
<Row>

View file

@ -7,6 +7,7 @@ import PropTypes from 'prop-types';
import { translate } from 'react-i18next';
import { withTracker } from 'meteor/react-meteor-data';
import Blaze from 'meteor/gadicc:blaze-react-component';
import { Helmet } from 'react-helmet';
/*
https://github.com/meteor/docs/blob/version-NEXT/long-form/oplog-observe-driver.md
@ -26,6 +27,9 @@ class Status extends Component {
render() {
return (
<Fragment>
<Helmet>
<title>{this.t('AppName')}: Status</title>
</Helmet>
<h4 className="page-header">Status</h4>
<Blaze template="serverFacts" />
</Fragment>

View file

@ -9,6 +9,7 @@ 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 { Helmet } from 'react-helmet';
import UserSubsToFiresCollection from '/imports/api/Subscriptions/Subscriptions';
import SelectionMap, { action } from '/imports/ui/components/SelectionMap/SelectionMap';
import confirm from '/imports/ui/components/Prompt/Confirm';
@ -84,6 +85,9 @@ class Subscriptions extends Component {
const firstBtnTitle = ['Añadir zona', '', 'Terminar']; // view, add, edit
return (!loading ? (
<div className="Subscriptions">
<Helmet>
<title>{t('AppName')}: {t('Suscripciones a alertas de fuegos en zonas de mi interés')}</title>
</Helmet>
<h4 className="page-header"><Trans>Suscripciones a alertas de fuegos en zonas de mi interés</Trans></h4>
{ subscriptions.length === 0 ?
<Alert bsStyle="warning"><Trans>No estás suscrito a fuegos en ninguna zona</Trans></Alert> :

View file

@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Alert } from 'react-bootstrap';
import { Meteor } from 'meteor/meteor';
import { Helmet } from 'react-helmet';
import { Accounts } from 'meteor/accounts-base';
import { Bert } from 'meteor/themeteorchef:bert';
import { translate } from 'react-i18next';
@ -32,6 +33,9 @@ class VerifyEmail extends React.Component {
render() {
return (<div className="VerifyEmail">
<Helmet>
<title>{this.t('AppName')}: {this.t('Verifica tu dirección de correo')}</title>
</Helmet>
<Alert bsStyle={!this.state.error ? 'info' : 'danger'}>
{!this.state.error ? this.t('Verificando...') : this.state.error}
</Alert>