diff --git a/package-lock.json b/package-lock.json index b1f1d655..c08a1676 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "stengttunnel", "version": "0.1.0", "dependencies": { + "@auth0/auth0-react": "^1.8.0", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^12.1.2", "@testing-library/user-event": "^13.2.1", @@ -28,6 +29,32 @@ "prettier": "2.4.1" } }, + "node_modules/@auth0/auth0-react": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@auth0/auth0-react/-/auth0-react-1.8.0.tgz", + "integrity": "sha512-tlrabVy4mB07Jbw41S2Ze2a7eGLyL32xs5OURGHXrCCxPXOYpBMeV9FdPssIK6vbAVRiUgwoeiemFAGhLRLZ0w==", + "dependencies": { + "@auth0/auth0-spa-js": "^1.18.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17", + "react-dom": "^16.11.0 || ^17" + } + }, + "node_modules/@auth0/auth0-spa-js": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@auth0/auth0-spa-js/-/auth0-spa-js-1.18.0.tgz", + "integrity": "sha512-Z8ZIzrZJKBHa3yFY/vRJYqg9CbLPwb2sRnouBdfPj8Pas08fwig63mfJKaTusIJC3gQ8xCIwfw4QFo83wpmFkg==", + "dependencies": { + "abortcontroller-polyfill": "^1.7.3", + "browser-tabs-lock": "^1.2.14", + "core-js": "^3.16.3", + "es-cookie": "^1.3.2", + "fast-text-encoding": "^1.0.3", + "promise-polyfill": "^8.2.0", + "unfetch": "^4.2.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.15.8", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", @@ -3754,6 +3781,11 @@ "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==" }, + "node_modules/abortcontroller-polyfill": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz", + "integrity": "sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q==" + }, "node_modules/accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -4979,6 +5011,15 @@ "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" }, + "node_modules/browser-tabs-lock": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/browser-tabs-lock/-/browser-tabs-lock-1.2.15.tgz", + "integrity": "sha512-J8K9vdivK0Di+b8SBdE7EZxDr88TnATing7XoLw6+nFkXMQ6sVBh92K3NQvZlZU91AIkFRi0w3sztk5Z+vsswA==", + "hasInstallScript": true, + "dependencies": { + "lodash": ">=4.17.21" + } + }, "node_modules/browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", @@ -7192,6 +7233,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-cookie": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/es-cookie/-/es-cookie-1.3.2.tgz", + "integrity": "sha512-UTlYYhXGLOy05P/vKVT2Ui7WtC7NiRzGtJyAKKn32g5Gvcjn7KAClLPWlipCtxIus934dFg9o9jXiBL0nP+t9Q==" + }, "node_modules/es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", @@ -8463,6 +8509,11 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, + "node_modules/fast-text-encoding": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", + "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==" + }, "node_modules/fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", @@ -15318,6 +15369,11 @@ "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" }, + "node_modules/promise-polyfill": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.2.0.tgz", + "integrity": "sha512-k/TC0mIcPVF6yHhUvwAp7cvL6I2fFV7TzF1DuGPI8mBh4QQazf36xCKEHKTZKRysEoTQoQdKyP25J8MPJp7j5g==" + }, "node_modules/prompts": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz", @@ -19042,6 +19098,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/unfetch": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", + "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==" + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -21264,6 +21325,28 @@ } }, "dependencies": { + "@auth0/auth0-react": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@auth0/auth0-react/-/auth0-react-1.8.0.tgz", + "integrity": "sha512-tlrabVy4mB07Jbw41S2Ze2a7eGLyL32xs5OURGHXrCCxPXOYpBMeV9FdPssIK6vbAVRiUgwoeiemFAGhLRLZ0w==", + "requires": { + "@auth0/auth0-spa-js": "^1.18.0" + } + }, + "@auth0/auth0-spa-js": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@auth0/auth0-spa-js/-/auth0-spa-js-1.18.0.tgz", + "integrity": "sha512-Z8ZIzrZJKBHa3yFY/vRJYqg9CbLPwb2sRnouBdfPj8Pas08fwig63mfJKaTusIJC3gQ8xCIwfw4QFo83wpmFkg==", + "requires": { + "abortcontroller-polyfill": "^1.7.3", + "browser-tabs-lock": "^1.2.14", + "core-js": "^3.16.3", + "es-cookie": "^1.3.2", + "fast-text-encoding": "^1.0.3", + "promise-polyfill": "^8.2.0", + "unfetch": "^4.2.0" + } + }, "@babel/code-frame": { "version": "7.15.8", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", @@ -23993,6 +24076,11 @@ "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==" }, + "abortcontroller-polyfill": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz", + "integrity": "sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q==" + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -24968,6 +25056,14 @@ "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" }, + "browser-tabs-lock": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/browser-tabs-lock/-/browser-tabs-lock-1.2.15.tgz", + "integrity": "sha512-J8K9vdivK0Di+b8SBdE7EZxDr88TnATing7XoLw6+nFkXMQ6sVBh92K3NQvZlZU91AIkFRi0w3sztk5Z+vsswA==", + "requires": { + "lodash": ">=4.17.21" + } + }, "browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", @@ -26727,6 +26823,11 @@ "unbox-primitive": "^1.0.1" } }, + "es-cookie": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/es-cookie/-/es-cookie-1.3.2.tgz", + "integrity": "sha512-UTlYYhXGLOy05P/vKVT2Ui7WtC7NiRzGtJyAKKn32g5Gvcjn7KAClLPWlipCtxIus934dFg9o9jXiBL0nP+t9Q==" + }, "es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", @@ -27673,6 +27774,11 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, + "fast-text-encoding": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", + "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==" + }, "fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", @@ -33064,6 +33170,11 @@ "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" }, + "promise-polyfill": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.2.0.tgz", + "integrity": "sha512-k/TC0mIcPVF6yHhUvwAp7cvL6I2fFV7TzF1DuGPI8mBh4QQazf36xCKEHKTZKRysEoTQoQdKyP25J8MPJp7j5g==" + }, "prompts": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz", @@ -36017,6 +36128,11 @@ "which-boxed-primitive": "^1.0.2" } }, + "unfetch": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", + "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==" + }, "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", diff --git a/package.json b/package.json index 401b9b6f..d8c21573 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@auth0/auth0-react": "^1.8.0", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^12.1.2", "@testing-library/user-event": "^13.2.1", diff --git a/src/App.test.tsx b/src/App.test.tsx index 3ba8a663..70f6fd50 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -23,6 +23,6 @@ test("Search for Oslofjordtunnelen and get status", async () => { await waitFor(() => screen.getByRole(/road/i)); expect(screen.getByRole("status")).toHaveTextContent( - /Oslofjordtunnelen ser ut/ + /Oslofjordtunnelen (ser ut|er kanskje)/ ); }); diff --git a/src/App.tsx b/src/App.tsx index 43c3e6ff..cf4177ca 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -34,11 +34,14 @@ const App = () => { }, []); useEffect(() => { - const [, roadFromPath] = window.location.pathname.split("/"); + const [, roadFromPath, action] = window.location.pathname.split("/"); Promise.resolve(localStorage.getItem("favorites") || "[]") .then((r) => JSON.parse(r)) .then((storedFavorites) => { + if (action === "user-report") { + return storedFavorites; + } if (roadFromPath && roads.find((r) => r.urlFriendly === roadFromPath)) { if (storedFavorites.indexOf(roadFromPath) === -1) { storedFavorites.push(roadFromPath); diff --git a/src/Header.tsx b/src/Header.tsx index a417a206..c9940261 100644 --- a/src/Header.tsx +++ b/src/Header.tsx @@ -41,7 +41,8 @@ const Header = (props: HeaderProps) => { return ( <> - + Velg én eller flere tunneler + useContext(LocationContext); + +const LocationContextProvider = (props: any) => { + const [location, setLocation] = useState(defaultLocation); + navigator.geolocation.getCurrentPosition((position) => { + const lat = position.coords.latitude; + const lon = position.coords.longitude; + + setLocation({ + lat, + lon, + }); + }); + + return ( + + {props.children} + + ); +}; + +export default LocationContextProvider; diff --git a/src/Road.tsx b/src/Road.tsx index ee116587..0c13e39c 100644 --- a/src/Road.tsx +++ b/src/Road.tsx @@ -1,6 +1,4 @@ -import React, { FC, useEffect, useState, useMemo } from "react"; -import ReactGA from "react-ga"; -import { IRoad, IRoadStatus, ISource } from "./types"; +import React, { useEffect, useState, useMemo } from "react"; import { Card, Feed, @@ -10,12 +8,16 @@ import { Placeholder, } from "semantic-ui-react"; +import ReactGA from "react-ga"; +import { IRoad, ISource } from "./types"; +import Status from "./Status"; + type RoadProps = { road: IRoad; }; -const Road: FC = (props: RoadProps) => { - const [road, setRoad] = useState(); +const Road = (props: RoadProps) => { + const [road, setRoad] = useState(); const [shouldUpdate, setShouldUpdate] = useState(false); const [loading, setLoading] = useState(true); const { roadName } = props.road; @@ -153,6 +155,9 @@ const Road: FC = (props: RoadProps) => { {statusMessage} + + + diff --git a/src/Status.tsx b/src/Status.tsx index 589c3fad..b7d14a78 100644 --- a/src/Status.tsx +++ b/src/Status.tsx @@ -1,42 +1,98 @@ -import React, { FC, useState } from "react"; -import { IRoad } from "./types"; +import React, { useState, useEffect } from "react"; import { Modal, Button } from "semantic-ui-react"; +import { useAuth0 } from "@auth0/auth0-react"; +// import { useLocation } from "./LocationContext"; +// import { getDistance } from "./utils"; + +import { IRoad } from "./types"; + +type StatusModalProps = { + road: IRoad; + open: boolean; + setOpen: (f: boolean) => void; +}; type StatusProps = { road: IRoad; }; -const Status: FC = (props: StatusProps) => { - const [open, setOpen] = useState(false); - const { roadName } = props.road; +const StatusModal = (props: StatusModalProps) => { + const { user, isAuthenticated, loginWithRedirect } = useAuth0(); + // const { lat, lon } = useLocation(); + + const { + open, + setOpen, + road: { urlFriendly, roadName, status }, + } = props; + + if (!isAuthenticated) { + loginWithRedirect({ + appState: { returnTo: `/${urlFriendly}/user-report` }, + }); + } + + // @todo: Get GPS coordinates for road from API + // const distance = getDistance(0, 0, lat, lon); return ( - setOpen(false)} - onOpen={() => setOpen(true)} - open={open} - trigger={} - > - Er {roadName} åpen eller stengt? - - -

- Basert på din lokasjon ser vi at du er nærmere enn 20km fra - tunnelen. Så du vet kanskje hva som er riktig? -

-

Er {roadName} egentlig åpen?

-
-
- - - - -
+ { isAuthenticated } && ( + setOpen(false)} + onOpen={() => setOpen(true)} + open={open} + > + Er {roadName} åpen eller stengt? + + +

Hei {user && user.name}!

+

+ Du vet kanskje hva som er riktig? Er {roadName} egentlig{" "} + {status === "green" ? "stengt" : "åpen"}? +

+
+
+ + + + +
+ ) ); }; +const Status = (props: StatusProps) => { + const [open, setOpen] = useState(false); + + useEffect(() => { + if (window.location.pathname === `/${props.road.urlFriendly}/user-report`) { + setOpen(true); + } + }, [props.road.urlFriendly]); + + useEffect(() => { + if (open) { + window.history.replaceState( + null, + "Stengt tunnel", + `/${props.road.urlFriendly}/user-report` + ); + } else { + if (window.location.pathname !== "/") { + window.history.replaceState(null, "Stengt tunnel", "/"); + } + } + }, [open, props.road.urlFriendly]); + + return ( + <> + {open && } + + + ); +}; export default Status; diff --git a/src/index.tsx b/src/index.tsx index 5b97be04..7ecd425f 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,12 +1,34 @@ import React from "react"; import ReactDOM from "react-dom"; +import { Auth0Provider } from "@auth0/auth0-react"; + import "semantic-ui-css/semantic.min.css"; import "./index.css"; import App from "./App"; +import LocationProvider from "./LocationContext"; + +const onRedirectCallback = (appState: any) => { + window.history.pushState( + null, + "Stengt tunnel", + appState && appState.returnTo ? appState.returnTo : window.location.pathname + ); +}; + +const providerConfig = { + domain: "stengttunnel.eu.auth0.com", + clientId: "lB8xWOACj3oVrIm80jGrRGcFkFv192Km", + redirectUri: window.location.origin, + onRedirectCallback, +}; ReactDOM.render( - + + + + + , document.getElementById("root") ); diff --git a/src/types.ts b/src/types.ts index 2a6a91d3..3360e4eb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,6 +2,10 @@ export interface IRoad { roadName: string; urlFriendly: string; url: string; + messages: IMessage[]; + status: "green" | "yellow" | "red"; + statusMessage: string; + statusCode: 10 | 20 | 30; } export enum ISource { @@ -22,7 +26,6 @@ export interface IRoadStatus { status: "green" | "yellow" | "red"; statusMessage: string; statusCode: 10 | 20 | 30; - gps?: IGPS; } export interface IGPS { diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 00000000..29eca299 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,14 @@ +export const getDistance = ( + lat1: number, + lon1: number, + lat2: number, + lon2: number +) => { + const p = 0.017453292519943295; + const c = Math.cos; + const a = + 0.5 - + c((lat2 - lat1) * p) / 2 + + (c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p))) / 2; + return 12742 * Math.asin(Math.sqrt(a)); // 2 * R; R = 6371 km +};