Initial commit
This commit is contained in:
commit
80c1729b2f
26 changed files with 311019 additions and 0 deletions
3
.eslintignore
Normal file
3
.eslintignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
build/
|
||||
node_modules/
|
||||
serviceWorker.js
|
16
.eslintrc.js
Normal file
16
.eslintrc.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
module.exports = {
|
||||
parser: 'babel-eslint',
|
||||
extends: ['airbnb', 'prettier', 'plugin:flowtype/recommended'],
|
||||
plugins: ['react', 'jsx-a11y', 'import', 'flowtype'],
|
||||
rules: {
|
||||
'react/jsx-filename-extension': [1, { extensions: ['.js', '.jsx'] }],
|
||||
'no-underscore-dangle': ["error", { "allow": ["_ne", "_sw"] }],
|
||||
"react/jsx-props-no-spreading": [1, {
|
||||
"exceptions": ["ReactMapGL"]
|
||||
}]
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es6: true,
|
||||
}
|
||||
};
|
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
3
README.md
Normal file
3
README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
Ce projet a simplement pour but de répertorier l'ensemble des stations essences en France.
|
||||
|
||||
Les données sont basées sur les [Open Data](https://www.prix-carburants.gouv.fr/rubrique/opendata/).
|
64
package.json
Normal file
64
package.json
Normal file
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"name": "e85map",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"bootstrap": "^4.4.1",
|
||||
"eslint-config-prettier": "^6.10.0",
|
||||
"eslint-plugin-prettier": "^3.1.2",
|
||||
"mapbox-gl": "^1.8.1",
|
||||
"moment": "^2.24.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^16.13.0",
|
||||
"react-bootstrap": "^1.0.0-beta.17",
|
||||
"react-dom": "^16.13.0",
|
||||
"react-map-gl": "^5.2.3",
|
||||
"react-moment": "^0.9.7",
|
||||
"react-scripts": "3.4.0",
|
||||
"react-toast-notifications": "^2.4.0",
|
||||
"xml-reader": "^2.4.3"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build && ssh porto 'rm -r www/darkou.fr/carburants/static' && scp -r build/* porto:www/darkou.fr/carburants",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.js": [
|
||||
"./node_modules/.bin/eslint --fix",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-airbnb": "^18.0.1",
|
||||
"eslint-plugin-import": "^2.20.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.2.3",
|
||||
"eslint-plugin-react": "^7.18.3",
|
||||
"eslint-plugin-react-hooks": "^1.7.0"
|
||||
}
|
||||
}
|
BIN
public/car.png
Normal file
BIN
public/car.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
BIN
public/gas-station.png
Normal file
BIN
public/gas-station.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 991 B |
298705
public/gasStations.xml
Normal file
298705
public/gasStations.xml
Normal file
File diff suppressed because it is too large
Load diff
21
public/index.html
Normal file
21
public/index.html
Normal file
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="description" content="Web site created using create-react-app" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<title>Carte des stations e85 - un service proposé par DarKou.fr</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
BIN
public/logo192.png
Normal file
BIN
public/logo192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
public/logo512.png
Normal file
BIN
public/logo512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
25
public/manifest.json
Normal file
25
public/manifest.json
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"short_name": "Prix Carburant",
|
||||
"name": "Prix des carburant - un service DarKou.fr",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
3
public/robots.txt
Normal file
3
public/robots.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
BIN
public/waze.png
Normal file
BIN
public/waze.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
24
src/App.css
Normal file
24
src/App.css
Normal file
|
@ -0,0 +1,24 @@
|
|||
.mapContainer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.sidebarStyle {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin: 12px;
|
||||
background-color: #404040;
|
||||
color: #ffffff;
|
||||
z-index: 1 !important;
|
||||
padding: 6px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.locationIcon {
|
||||
width: 32px;
|
||||
}
|
90
src/App.js
Normal file
90
src/App.js
Normal file
|
@ -0,0 +1,90 @@
|
|||
import React from 'react';
|
||||
import { ToastProvider } from 'react-toast-notifications';
|
||||
import Footer from './Components/Footer';
|
||||
import GasStation from "./Components/GasStation";
|
||||
import Map from "./Components/Map";
|
||||
|
||||
import 'mapbox-gl/dist/mapbox-gl.css';
|
||||
|
||||
import './App.css';
|
||||
|
||||
class Application extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
showModal: false,
|
||||
selectedGasStation: {},
|
||||
selectedGasType: 'E85'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode permettant de sélectionner le type de carburant à afficher
|
||||
* @param {Object} e
|
||||
*/
|
||||
selectGasType = (e) => {
|
||||
const {
|
||||
value,
|
||||
} = e.target;
|
||||
this.setState(prevState => ({
|
||||
...prevState,
|
||||
selectedGasType: value
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode permettant de fermer la modale contenant les détails de la station
|
||||
* @param {Boolean} goToWaze
|
||||
*/
|
||||
hideModal = (goToWaze) => {
|
||||
const {
|
||||
selectedGasStation,
|
||||
} = this.state;
|
||||
if (goToWaze) {
|
||||
window.open(`https://www.waze.com/livemap/directions?navigate=yes&latlng=${selectedGasStation.latitude}%2C${selectedGasStation.longitude}&zoom=17`);
|
||||
}
|
||||
this.setState(prevState => ({
|
||||
...prevState,
|
||||
selectedGasStation: {},
|
||||
showModal: false
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode permettant de sélectionner et d'afficher le détails d'une station
|
||||
* @param {Object} selectedGasStation
|
||||
*/
|
||||
showGasStation = (selectedGasStation) => {
|
||||
this.setState(prevState => ({
|
||||
...prevState,
|
||||
showModal: true,
|
||||
selectedGasStation
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode gérant le rendu de la vue
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
selectedGasType,
|
||||
showModal,
|
||||
selectedGasStation,
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<ToastProvider>
|
||||
<GasStation
|
||||
showModal={showModal}
|
||||
hideModal={this.hideModal}
|
||||
selectedGasStation={selectedGasStation}
|
||||
/>
|
||||
<Map selectedGasType={selectedGasType} showGasStation={this.showGasStation} />
|
||||
<Footer selectedGasType={selectedGasType} selectGasType={this.selectGasType} />
|
||||
</ToastProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Application;
|
25
src/Components/Footer.js
Normal file
25
src/Components/Footer.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
import React from 'react';
|
||||
import { Navbar } from "react-bootstrap";
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import GasTypes from "./GasTypes";
|
||||
|
||||
const Footer = (props) => {
|
||||
const {
|
||||
selectGasType,
|
||||
selectedGasType,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Navbar bg="light" variant="light" fixed="bottom">
|
||||
<GasTypes selectedGasType={selectedGasType} selectGasType={selectGasType} />
|
||||
</Navbar>
|
||||
);
|
||||
};
|
||||
|
||||
Footer.propTypes = {
|
||||
selectedGasType: PropTypes.string.isRequired,
|
||||
selectGasType: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Footer;
|
79
src/Components/GasStation.js
Normal file
79
src/Components/GasStation.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
import React from 'react';
|
||||
import { Modal, Button, ListGroup } from "react-bootstrap";
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
class GasStation extends React.Component {
|
||||
renderPrices = () => {
|
||||
const {
|
||||
selectedGasStation,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<ListGroup variant="flush">
|
||||
{selectedGasStation.prices ? selectedGasStation.prices.map(price => {
|
||||
return (
|
||||
<ListGroup.Item key={price.type}>
|
||||
{`${price.type} : ${price.price} € `}
|
||||
</ListGroup.Item>
|
||||
);
|
||||
}) : (null)}
|
||||
</ListGroup>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
showModal,
|
||||
hideModal,
|
||||
selectedGasStation,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
size="xl"
|
||||
aria-labelledby="contained-modal-title-vcenter"
|
||||
centered
|
||||
show={showModal}
|
||||
onHide={hideModal}
|
||||
>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
{`${selectedGasStation.address} - ${selectedGasStation.city}`}
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
{this.renderPrices()}
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="primary" onClick={() => hideModal(true)}>
|
||||
<img
|
||||
className="locationIcon"
|
||||
src="/waze.png"
|
||||
alt="S'y rendre"
|
||||
/>
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
GasStation.defaultProps = {
|
||||
selectedGasStation: {
|
||||
address: null,
|
||||
city: null,
|
||||
prices: []
|
||||
}
|
||||
};
|
||||
|
||||
GasStation.propTypes = {
|
||||
showModal: PropTypes.bool.isRequired,
|
||||
hideModal: PropTypes.func.isRequired,
|
||||
selectedGasStation: PropTypes.shape({
|
||||
address: PropTypes.string,
|
||||
city: PropTypes.string,
|
||||
prices: PropTypes.array
|
||||
}),
|
||||
};
|
||||
|
||||
export default GasStation;
|
64
src/Components/GasTypes.js
Normal file
64
src/Components/GasTypes.js
Normal file
|
@ -0,0 +1,64 @@
|
|||
import React from 'react';
|
||||
import { Form, Row, Col } from "react-bootstrap";
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
class GasTypes extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
gasTypes: [
|
||||
{
|
||||
name: "Ethanol e85",
|
||||
type: 'E85',
|
||||
},
|
||||
{
|
||||
name: "Sans plomb 95 E10",
|
||||
type: "E10"
|
||||
},
|
||||
{
|
||||
name: "Sans plomb 95",
|
||||
type: "SP95"
|
||||
},
|
||||
{
|
||||
name: "Sans plomb 98",
|
||||
type: "SP98"
|
||||
},
|
||||
{
|
||||
name: "Gazole",
|
||||
type: "Gazole"
|
||||
},
|
||||
{
|
||||
name: "GPL",
|
||||
type: "GPLc"
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
gasTypes,
|
||||
} = this.state;
|
||||
const {
|
||||
selectGasType,
|
||||
selectedGasType,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Row style={{ width: "100%" }}>
|
||||
<Col>
|
||||
<Form.Control as="select" value={selectedGasType} onChange={selectGasType}>
|
||||
{gasTypes.map(gasType => (<option key={gasType.type} value={gasType.type}>{gasType.name}</option>))}
|
||||
</Form.Control>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
GasTypes.propTypes = {
|
||||
selectedGasType: PropTypes.string.isRequired,
|
||||
selectGasType: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default GasTypes;
|
187
src/Components/Map.js
Normal file
187
src/Components/Map.js
Normal file
|
@ -0,0 +1,187 @@
|
|||
import React from 'react';
|
||||
import ReactMapGL, { Marker } from 'react-map-gl';
|
||||
import { Button } from "react-bootstrap";
|
||||
import XmlReader from 'xml-reader';
|
||||
import { withToastManager } from 'react-toast-notifications';
|
||||
import PropTypes from 'prop-types';
|
||||
import { haveSelectedGas, extractGasStationFromXml } from '../helpers';
|
||||
|
||||
import 'mapbox-gl/dist/mapbox-gl.css';
|
||||
|
||||
const mapboxToken = 'pk.eyJ1IjoiZGFya291IiwiYSI6ImNrNzkwdmlsdTBtMmwzZnM0ZmI4Z3h4czIifQ.GU2CdcMiKiApHNhI0ylGtQ';
|
||||
|
||||
class Map extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
viewport: {
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
latitude: 44.837789,
|
||||
longitude: -0.57918,
|
||||
zoom: 11,
|
||||
},
|
||||
userLocation: {
|
||||
// latitude: 44.837789,
|
||||
// longitude: -0.57918,
|
||||
},
|
||||
gasStations: [],
|
||||
};
|
||||
|
||||
this.mapRef = React.createRef();
|
||||
|
||||
this.loadGasStations();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setUserLocation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode permettant de mettre à jour la position de la map
|
||||
* @param {Object} viewport
|
||||
*/
|
||||
onViewportChange = (viewport) => {
|
||||
this.setState({ viewport });
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode permettant de sauvegarder la position de l'utilisateur
|
||||
*/
|
||||
setUserLocation() {
|
||||
navigator.geolocation.getCurrentPosition((position) => {
|
||||
const setUserLocation = {
|
||||
latitude: position.coords.latitude,
|
||||
longitude: position.coords.longitude,
|
||||
};
|
||||
const newViewport = {
|
||||
height: '100vh',
|
||||
width: '100vw',
|
||||
latitude: position.coords.latitude,
|
||||
longitude: position.coords.longitude,
|
||||
zoom: 10,
|
||||
};
|
||||
this.setState({
|
||||
viewport: newViewport,
|
||||
userLocation: setUserLocation,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode permettant de filter sur la liste des stations affichables sur la carte
|
||||
* @param {Object} gasStation
|
||||
* @return {Boolean}
|
||||
*/
|
||||
displayThisGasStation = (gasStation) => {
|
||||
const mapGL = this.mapRef.getMap();
|
||||
const bounds = mapGL.getBounds();
|
||||
|
||||
return (bounds._ne.lat > gasStation.latitude && bounds._sw.lat < gasStation.latitude)
|
||||
&& (bounds._ne.lng > gasStation.longitude && bounds._sw.lng < gasStation.longitude);
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode permettant de charger le fichier xml contenant la liste des stations
|
||||
*/
|
||||
loadGasStations() {
|
||||
const {
|
||||
toastManager,
|
||||
} = this.props;
|
||||
|
||||
fetch('/gasStations.xml')
|
||||
.then((response) => response.text())
|
||||
.then((response) => {
|
||||
const reader = XmlReader.create();
|
||||
|
||||
reader.on('done', (data) => {
|
||||
const pdv = data.children;
|
||||
|
||||
const gasStations = [];
|
||||
|
||||
for (let i = 0; i < pdv.length; i += 1) {
|
||||
const currentPdv = pdv[i];
|
||||
gasStations.push(extractGasStationFromXml(currentPdv));
|
||||
}
|
||||
|
||||
this.setState((prevState) => ({
|
||||
...prevState,
|
||||
gasStations,
|
||||
}));
|
||||
});
|
||||
reader.parse(response);
|
||||
}).catch(() => {
|
||||
toastManager.add('Erreur lors du chargement de la liste des stations', { appearance: 'error', autoDismiss: true });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode gérant le rendu de la vue
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
viewport,
|
||||
userLocation,
|
||||
gasStations,
|
||||
} = this.state;
|
||||
|
||||
const {
|
||||
showGasStation,
|
||||
selectedGasType,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ReactMapGL
|
||||
{...viewport}
|
||||
mapStyle="mapbox://styles/mapbox/outdoors-v11"
|
||||
onViewportChange={this.onViewportChange}
|
||||
mapboxApiAccessToken={mapboxToken}
|
||||
ref={(map) => { if (map) this.mapRef = map; }}
|
||||
>
|
||||
{
|
||||
Object.keys(userLocation).length !== 0 ? (
|
||||
<Marker
|
||||
latitude={userLocation.latitude}
|
||||
longitude={userLocation.longitude}
|
||||
>
|
||||
<img className="locationIcon" src="/car.png" alt="My position" />
|
||||
</Marker>
|
||||
) : (null)
|
||||
}
|
||||
{gasStations.filter(this.displayThisGasStation).filter((station) => haveSelectedGas(station, selectedGasType)).map((gasStation) => (
|
||||
<Marker
|
||||
key={gasStation.id}
|
||||
latitude={gasStation.latitude}
|
||||
longitude={gasStation.longitude}
|
||||
>
|
||||
<Button
|
||||
variant="link"
|
||||
onClick={() => showGasStation(gasStation)}
|
||||
onFocus={() => { }}
|
||||
onBlur={() => { }}
|
||||
>
|
||||
<img
|
||||
className="locationIcon"
|
||||
src="/gas-station.png"
|
||||
alt={`${gasStation.id}`}
|
||||
/>
|
||||
</Button>
|
||||
|
||||
</Marker>
|
||||
))}
|
||||
</ReactMapGL>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Map.propTypes = {
|
||||
selectedGasType: PropTypes.string.isRequired,
|
||||
showGasStation: PropTypes.func.isRequired,
|
||||
toastManager: PropTypes.shape({
|
||||
add: PropTypes.func,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default withToastManager(Map);
|
63
src/helpers.js
Normal file
63
src/helpers.js
Normal file
|
@ -0,0 +1,63 @@
|
|||
export const getFuelPrices = (pdv) => {
|
||||
const prices = []
|
||||
if (!pdv.children || pdv.children.length === 0) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < pdv.children.length; i += 1) {
|
||||
const currentChildren = pdv.children[i];
|
||||
if (currentChildren.type === 'element' && currentChildren.name === 'prix') {
|
||||
prices.push({
|
||||
type: currentChildren.attributes.nom,
|
||||
price: parseInt(currentChildren.attributes.valeur, 10) / 1000,
|
||||
updatedAt: currentChildren.attributes.maj,
|
||||
});
|
||||
}
|
||||
}
|
||||
return prices;
|
||||
};
|
||||
|
||||
export const getPlvInformation = (pdv, name) => {
|
||||
if (!pdv.children || pdv.children.length === 0) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < pdv.children.length; i += 1) {
|
||||
const currentChildren = pdv.children[i];
|
||||
if (currentChildren.type === 'element' && currentChildren.name === name) {
|
||||
|
||||
if ( currentChildren.children && currentChildren.children.length > 0 ) {
|
||||
return currentChildren.children[0].value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const formatPosition = (value) => value / 100000;
|
||||
|
||||
export const haveSelectedGas = (station, gas) => {
|
||||
if (!station.prices || station.prices.length === 0 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0 ; i < station.prices.length ; i +=1 ){
|
||||
if (station.prices[i].type === gas ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export const extractGasStationFromXml = (currentPdv ) => {
|
||||
return {
|
||||
id: currentPdv.attributes.id,
|
||||
latitude: formatPosition(currentPdv.attributes.latitude),
|
||||
longitude: formatPosition(currentPdv.attributes.longitude),
|
||||
prices: getFuelPrices(currentPdv),
|
||||
postCode: currentPdv.attributes.cp,
|
||||
address: getPlvInformation(currentPdv, 'adresse'),
|
||||
city: getPlvInformation(currentPdv, 'ville')
|
||||
}
|
||||
}
|
||||
|
12
src/index.js
Normal file
12
src/index.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
import * as serviceWorker from './serviceWorker';
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
||||
|
||||
// If you want your app to work offline and load faster, you can change
|
||||
// unregister() to register() below. Note this comes with some pitfalls.
|
||||
// Learn more about service workers: https://bit.ly/CRA-PWA
|
||||
serviceWorker.register();
|
129
src/serviceWorker.js
Normal file
129
src/serviceWorker.js
Normal file
|
@ -0,0 +1,129 @@
|
|||
const isLocalhost = Boolean(
|
||||
window.location.hostname === 'localhost' ||
|
||||
// [::1] is the IPv6 localhost address.
|
||||
window.location.hostname === '[::1]' ||
|
||||
// 127.0.0.0/8 are considered localhost for IPv4.
|
||||
window.location.hostname.match(
|
||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||
)
|
||||
);
|
||||
|
||||
export function register(config) {
|
||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||
// The URL constructor is available in all browsers that support SW.
|
||||
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
|
||||
if (publicUrl.origin !== window.location.origin) {
|
||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||
// from what our page is served on. This might happen if a CDN is used to
|
||||
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||
|
||||
if (isLocalhost) {
|
||||
// This is running on localhost. Let's check if a service worker still exists or not.
|
||||
checkValidServiceWorker(swUrl, config);
|
||||
|
||||
// Add some additional logging to localhost, pointing developers to the
|
||||
// service worker/PWA documentation.
|
||||
navigator.serviceWorker.ready.then(() => {
|
||||
console.log(
|
||||
'This web app is being served cache-first by a service ' +
|
||||
'worker. To learn more, visit https://bit.ly/CRA-PWA'
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// Is not localhost. Just register service worker
|
||||
registerValidSW(swUrl, config);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function registerValidSW(swUrl, config) {
|
||||
navigator.serviceWorker
|
||||
.register(swUrl)
|
||||
.then(registration => {
|
||||
registration.onupdatefound = () => {
|
||||
const installingWorker = registration.installing;
|
||||
if (installingWorker == null) {
|
||||
return;
|
||||
}
|
||||
installingWorker.onstatechange = () => {
|
||||
if (installingWorker.state === 'installed') {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
// At this point, the updated precached content has been fetched,
|
||||
// but the previous service worker will still serve the older
|
||||
// content until all client tabs are closed.
|
||||
console.log(
|
||||
'New content is available and will be used when all ' +
|
||||
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
|
||||
);
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onUpdate) {
|
||||
config.onUpdate(registration);
|
||||
}
|
||||
} else {
|
||||
// At this point, everything has been precached.
|
||||
// It's the perfect time to display a
|
||||
// "Content is cached for offline use." message.
|
||||
console.log('Content is cached for offline use.');
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onSuccess) {
|
||||
config.onSuccess(registration);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error during service worker registration:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function checkValidServiceWorker(swUrl, config) {
|
||||
// Check if the service worker can be found. If it can't reload the page.
|
||||
fetch(swUrl, {
|
||||
headers: { 'Service-Worker': 'script' }
|
||||
})
|
||||
.then(response => {
|
||||
// Ensure service worker exists, and that we really are getting a JS file.
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (
|
||||
response.status === 404 ||
|
||||
(contentType != null && contentType.indexOf('javascript') === -1)
|
||||
) {
|
||||
// No service worker found. Probably a different app. Reload the page.
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister().then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Service worker found. Proceed as normal.
|
||||
registerValidSW(swUrl, config);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
console.log(
|
||||
'No internet connection found. App is running in offline mode.'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function unregister() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready
|
||||
.then(registration => {
|
||||
registration.unregister();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error.message);
|
||||
});
|
||||
}
|
||||
}
|
5
src/setupTests.js
Normal file
5
src/setupTests.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom/extend-expect';
|
Loading…
Reference in a new issue