import React, { Fragment } from 'react'; import { Route, Switch, Link } from 'react-router-dom'; import PropTypes from 'prop-types'; import { parse } from 'query-string'; import '../../node_modules/bootstrap/dist/css/bootstrap.min.css'; import './app.css'; import BuildingEdit from './building-edit'; import BuildingView from './building-view'; import ColouringMap from './map'; import Header from './header'; import Login from './login'; import MultiEdit from './multi-edit'; import Overview from './overview'; import SignUp from './signup'; import ForgottenPassword from './forgotten-password'; import PasswordReset from './password-reset'; import AboutPage from './pages/about'; import ContributorAgreementPage from './pages/contributor-agreement'; import MyAccountPage from './pages/my-account'; import PrivacyPolicyPage from './pages/privacy-policy'; import Welcome from './pages/welcome'; import { parseCategoryURL } from '../parse'; /** * App component * * This is the top-level stateful frontend component * - rendered from props, instantiated either server-side in server.js or client-side in * client.js * - state (including user, current building) is initialised from props * - callbacks to update top-level state are passed down to subcomponents * - render method wraps a react-router switch - this drives which version of the sidebar and * map or other pages are rendered, based on the URL. Use a react-router-dom in * child components to navigate without a full page reload. */ class App extends React.Component { // TODO: add proper types static propTypes = { // TODO: generate propTypes from TS user: PropTypes.object, building: PropTypes.object, building_like: PropTypes.bool } constructor(props) { super(props); // set building revision id, default 0 const rev = (props.building)? +props.building.revision_id : 0; this.state = { user: props.user, building: props.building, building_like: props.building_like, revision_id: rev }; this.login = this.login.bind(this); this.updateUser = this.updateUser.bind(this); this.logout = this.logout.bind(this); this.selectBuilding = this.selectBuilding.bind(this); this.colourBuilding = this.colourBuilding.bind(this); this.increaseRevision = this.increaseRevision.bind(this); } login(user) { if (user.error) { this.logout(); return } this.setState({user: user}); } updateUser(user){ this.setState({user: { ...this.state.user, ...user }}); } logout() { this.setState({user: undefined}); } increaseRevision(revisionId) { revisionId = +revisionId; // bump revision id, only ever increasing if (revisionId > this.state.revision_id){ this.setState({revision_id: revisionId}) } } selectBuilding(building) { this.increaseRevision(building.revision_id); // get UPRNs and update fetch(`/api/buildings/${building.building_id}/uprns.json`, { method: 'GET', headers:{ 'Content-Type': 'application/json' }, credentials: 'same-origin' }).then( res => res.json() ).then((res) => { if (res.error) { console.error(res); } else { building.uprns = res.uprns; this.setState({building: building}); } }).catch((err) => { console.error(err) this.setState({building: building}); }); // get if liked and update fetch(`/api/buildings/${building.building_id}/like.json`, { method: 'GET', headers:{ 'Content-Type': 'application/json' }, credentials: 'same-origin' }).then( res => res.json() ).then((res) => { if (res.error) { console.error(res); } else { this.setState({building_like: res.like}); } }).catch((err) => { console.error(err) this.setState({building_like: false}); }); } /** * Colour building * * Used in multi-edit mode to colour buildings on map click * * Pulls data from URL to form update * * @param {object} building */ colourBuilding(building) { const cat = parseCategoryURL(window.location.pathname); const q = parse(window.location.search); const data = (cat === 'like')? {like: true}: JSON.parse(q.data as string); // TODO: verify what happens if data is string[] if (cat === 'like'){ this.likeBuilding(building.building_id) } else { this.updateBuilding(building.building_id, data) } } likeBuilding(buildingId) { fetch(`/api/buildings/${buildingId}/like.json`, { method: 'POST', headers:{ 'Content-Type': 'application/json' }, credentials: 'same-origin', body: JSON.stringify({like: true}) }).then( res => res.json() ).then(function(res){ if (res.error) { console.error({error: res.error}) } else { this.increaseRevision(res.revision_id); } }.bind(this)).catch( (err) => console.error({error: err}) ); } updateBuilding(buildingId, data){ fetch(`/api/buildings/${buildingId}.json`, { method: 'POST', body: JSON.stringify(data), headers:{ 'Content-Type': 'application/json' }, credentials: 'same-origin' }).then( res => res.json() ).then(res => { if (res.error) { console.error({error: res.error}) } else { this.increaseRevision(res.revision_id); } }).catch( (err) => console.error({error: err}) ); } render() { return ( ( ) } /> ( ) } /> ( ) } /> ( ) } /> ( ) } /> ( ) } /> ); } } /** * Component to fall back on in case of 404 or no other match */ const NotFound = () => ( Page not found We can’t find that one anywhere. Back home ); export default App;
We can’t find that one anywhere.