From d585cc908f73383526994abbe7544248a768b0ed Mon Sep 17 00:00:00 2001 From: IZZY Date: Sat, 31 May 2025 21:25:59 +0100 Subject: [PATCH] logging works for earthquake --- package-lock.json | 154 ++++++++++++++++++++++ package.json | 1 + src/app/earthquakes/page.tsx | 58 +++++++-- src/components/BottomFooter.tsx | 16 ++- src/components/Sidebar.tsx | 222 +++++++++++++++----------------- 5 files changed, 323 insertions(+), 128 deletions(-) diff --git a/package-lock.json b/package-lock.json index 54bc2b0..1ed5173 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "lodash": "^4.17.21", "mapbox-gl": "^3.10.0", "next": "^15.1.7", + "next-auth": "^4.24.11", "path": "^0.12.7", "prisma": "^6.4.1", "react": "^19.1.0", @@ -1070,6 +1071,15 @@ "node": ">=12.4.0" } }, + "node_modules/@panva/hkdf": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", + "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -5670,6 +5680,47 @@ } } }, + "node_modules/next-auth": { + "version": "4.24.11", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.11.tgz", + "integrity": "sha512-pCFXzIDQX7xmHFs4KVH4luCjaCbuPRtZ9oBUjUhOk84mZ9WVPf94n87TxYI4rSRf9HmfHEF8Yep3JrYDVOo3Cw==", + "license": "ISC", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@panva/hkdf": "^1.0.2", + "cookie": "^0.7.0", + "jose": "^4.15.5", + "oauth": "^0.9.15", + "openid-client": "^5.4.0", + "preact": "^10.6.3", + "preact-render-to-string": "^5.1.19", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "@auth/core": "0.34.2", + "next": "^12.2.5 || ^13 || ^14 || ^15", + "nodemailer": "^6.6.5", + "react": "^17.0.2 || ^18 || ^19", + "react-dom": "^17.0.2 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "@auth/core": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, + "node_modules/next-auth/node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -5708,6 +5759,12 @@ "node": ">=0.10.0" } }, + "node_modules/oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==", + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -5840,6 +5897,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oidc-token-hash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.1.0.tgz", + "integrity": "sha512-y0W+X7Ppo7oZX6eovsRkuzcSM40Bicg2JEJkDJ4irIt1wsYAP5MLSNv+QAogO8xivMffw/9OvV3um1pxXgt1uA==", + "license": "MIT", + "engines": { + "node": "^10.13.0 || >=12.0.0" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -5861,6 +5927,51 @@ "wrappy": "1" } }, + "node_modules/openid-client": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.7.1.tgz", + "integrity": "sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew==", + "license": "MIT", + "dependencies": { + "jose": "^4.15.9", + "lru-cache": "^6.0.0", + "object-hash": "^2.2.0", + "oidc-token-hash": "^5.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/openid-client/node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/openid-client/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/openid-client/node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -6212,6 +6323,28 @@ "integrity": "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw==", "license": "ISC" }, + "node_modules/preact": { + "version": "10.26.8", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.8.tgz", + "integrity": "sha512-1nMfdFjucm5hKvq0IClqZwK4FJkGXhRrQstOQ3P4vp8HxKrJEMFcY6RdBRVTdfQS/UlnX6gfbPuTvaqx/bDoeQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz", + "integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==", + "license": "MIT", + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -6222,6 +6355,12 @@ "node": ">= 0.8.0" } }, + "node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==", + "license": "MIT" + }, "node_modules/prisma": { "version": "6.8.2", "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.8.2.tgz", @@ -7927,6 +8066,15 @@ "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", "license": "ISC" }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -8163,6 +8311,12 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/yaml": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", diff --git a/package.json b/package.json index 2e39b73..0c79f56 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "lodash": "^4.17.21", "mapbox-gl": "^3.10.0", "next": "^15.1.7", + "next-auth": "^4.24.11", "path": "^0.12.7", "prisma": "^6.4.1", "react": "^19.1.0", diff --git a/src/app/earthquakes/page.tsx b/src/app/earthquakes/page.tsx index 9b26801..e25174d 100644 --- a/src/app/earthquakes/page.tsx +++ b/src/app/earthquakes/page.tsx @@ -9,6 +9,29 @@ import { getRelativeDate } from "@utils/formatters"; import GeologicalEvent from "@appTypes/Event"; import axios from "axios"; import EarthquakeLogModal from "@components/EarthquakeLogModal"; +import { useStoreState } from "@hooks/store"; + +// --- NO ACCESS MODAL --- +function NoAccessModal({ open, onClose }) { + if (!open) return null; + return ( +
+
+ +

Access Denied

+

Sorry, you do not have access rights to Log an Earthquake. Please Log in here, or contact an Admin if you believe this is a mistake

+ +
+
+ ); +} // --- SEARCH MODAL COMPONENT --- function EarthquakeSearchModal({ open, onClose, onSelect }) { @@ -86,10 +109,17 @@ export default function Earthquakes() { const [selectedEventId, setSelectedEventId] = useState(""); const [hoveredEventId, setHoveredEventId] = useState(""); const [searchModalOpen, setSearchModalOpen] = useState(false); - const [logModalOpen, setLogModalOpen] = useState(false); // <-- Move here! - // Fetch recent earthquakes as before + const [logModalOpen, setLogModalOpen] = useState(false); + const [noAccessModalOpen, setNoAccessModalOpen] = useState(false); + + const user = useStoreState((state) => state.user); + const role: "GUEST" | "SCIENTIST" | "ADMIN" = user?.role ?? "GUEST"; + const canLogEarthquake = role === "SCIENTIST" || role === "ADMIN"; + + // Fetch recent earthquakes const { data, error, isLoading, mutate } = useSWR("/api/earthquakes", createPoster({ rangeDaysPrev: 10 })); - // Prepare events for maps/sidebar + + // Prepare events const earthquakeEvents = useMemo( () => data && data.earthquakes @@ -110,6 +140,16 @@ export default function Earthquakes() { : [], [data] ); + + // This handler is always called, regardless of button state! + const handleLogClick = () => { + if (canLogEarthquake) { + setLogModalOpen(true); + } else { + setNoAccessModalOpen(true); + } + }; + return (
@@ -133,22 +173,24 @@ export default function Earthquakes() { setHoveredEventId={setHoveredEventId} button1Name="Log an Earthquake" button2Name="Search Earthquakes" - onButton1Click={() => setLogModalOpen(true)} // Correct! + onButton1Click={handleLogClick} // <--- Important! onButton2Click={() => setSearchModalOpen(true)} + button1Disabled={!canLogEarthquake} // <--- For style only! /> setSearchModalOpen(false)} - onSelect={(eq) => { - setSelectedEventId(eq.code); - // setSelectedSearchResult(eq); // optional - }} + onSelect={(eq) => setSelectedEventId(eq.code)} /> setLogModalOpen(false)} onSuccess={() => mutate()} // To refresh /> + setNoAccessModalOpen(false)} + />
); } \ No newline at end of file diff --git a/src/components/BottomFooter.tsx b/src/components/BottomFooter.tsx index 39339fd..fcb9f57 100644 --- a/src/components/BottomFooter.tsx +++ b/src/components/BottomFooter.tsx @@ -2,15 +2,15 @@ import React, { useCallback, useRef, useState } from "react"; import Link from "next/link"; export default function BottomFooter() { - // Lava flood state & timer - const [lavaActive, setLavaActive] = useState(false); + // ig easter egg + const [lavaActive, setLavaActive] = useState(false); const lavaTimeout = useRef(null); - // LinkedIn shake state & timer + // LinkedIn easter egg const [shaking, setShaking] = useState(false); const shakeTimeout = useRef(null); - // Crack+collapse states for the X logo + // x easter egg const [showCracks, setShowCracks] = useState(false); const [collapse, setCollapse] = useState(false); const crackTimeout = useRef(null); @@ -131,6 +131,14 @@ export default function BottomFooter() { {/* Bottom bar */}
+
+ TremorTracker logo +
© TremorTracker 2025 diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index b88ec84..e9c56fd 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,135 +1,125 @@ -import Link from "next/link"; import React, { Dispatch, SetStateAction, useEffect, useRef } from "react"; import { TbHexagon } from "react-icons/tb"; - import GeologicalEvent from "@appTypes/Event"; import getMagnitudeColor from "@utils/getMagnitudeColour"; interface SidebarProps { - logTitle: string; - logSubtitle: string; - recentsTitle: string; - events: GeologicalEvent[]; - selectedEventId: GeologicalEvent["id"]; - setSelectedEventId: Dispatch>; - hoveredEventId: GeologicalEvent["id"]; - setHoveredEventId: Dispatch>; - button1Name: string; - button2Name: string; - onButton2Click?: () => void; + logTitle: string; + logSubtitle: string; + recentsTitle: string; + events: GeologicalEvent[]; + selectedEventId: GeologicalEvent["id"]; + setSelectedEventId: Dispatch>; + hoveredEventId: GeologicalEvent["id"]; + setHoveredEventId: Dispatch>; + button1Name: string; + button2Name: string; + onButton2Click?: () => void; onButton1Click?: () => void; + button1Disabled?: boolean; } function MagnitudeNumber({ magnitude }: { magnitude: number }) { - const magnitudeStr = magnitude.toFixed(1); - const [whole, decimal] = magnitudeStr.split("."); - - return ( -
- -
-
- {whole} - . - {decimal} -
-
-
- ); + const magnitudeStr = magnitude.toFixed(1); + const [whole, decimal] = magnitudeStr.split("."); + return ( +
+ +
+
+ {whole} + . + {decimal} +
+
+
+ ); } -// todo change sidebar event highlighting on selection - export default function Sidebar({ - logTitle, - logSubtitle, - recentsTitle, - events, - selectedEventId, - setSelectedEventId, - hoveredEventId, - setHoveredEventId, - button1Name, - button2Name, - onButton2Click, + logTitle, + logSubtitle, + recentsTitle, + events, + selectedEventId, + setSelectedEventId, + hoveredEventId, + setHoveredEventId, + button1Name, + button2Name, + onButton2Click, onButton1Click, + button1Disabled = false, }: SidebarProps) { - const eventsContainerRef = useRef(null); + const eventsContainerRef = useRef(null); + useEffect(() => { + if (selectedEventId && eventsContainerRef.current) { + const selectedEventElement = eventsContainerRef.current.querySelector(`[data-event-id="${selectedEventId}"]`); + if (selectedEventElement) { + selectedEventElement.scrollIntoView({ + block: "center", + behavior: "smooth", + }); + } + } + }, [selectedEventId]); - useEffect(() => { - if (selectedEventId && eventsContainerRef.current) { - const selectedEventElement = eventsContainerRef.current.querySelector(`[data-event-id="${selectedEventId}"]`); - if (selectedEventElement) { - selectedEventElement.scrollIntoView({ - block: "center", - behavior: "smooth", - }); - } - } - }, [selectedEventId]); - - return ( -
-
-
-

{logTitle}

-

{logSubtitle}

- - {onButton1Click ? ( - - ) : ( - + return ( +
+
+
+

{logTitle}

+

{logSubtitle}

+ + +
+
+

{recentsTitle}

+
+
+
+ {events.map((event) => ( - - )} - - -
-
-

{recentsTitle}

-
-
-
- {events.map((event) => ( - - ))} -
-
-
-
- ); -} + ))} +
+
+
+
+ ); +} \ No newline at end of file