From f88f783de939829947f4dc71b51b5cee4e13b2d3 Mon Sep 17 00:00:00 2001 From: Tim Howitz Date: Sun, 1 Jun 2025 14:18:32 +0100 Subject: [PATCH] Made some small improvements --- src/app/earthquakes/page.tsx | 217 +++++++++--------- src/app/globals.css | 244 ++++++++++----------- src/app/observatories/page.tsx | 176 +++++++-------- src/components/LoggingModal.tsx | 2 +- src/components/Map.tsx | 15 +- src/types/{Event.ts => GeologicalEvent.ts} | 2 + 6 files changed, 313 insertions(+), 343 deletions(-) rename src/types/{Event.ts => GeologicalEvent.ts} (82%) diff --git a/src/app/earthquakes/page.tsx b/src/app/earthquakes/page.tsx index 45e3d8f..f4501e3 100644 --- a/src/app/earthquakes/page.tsx +++ b/src/app/earthquakes/page.tsx @@ -6,134 +6,117 @@ import Sidebar from "@components/Sidebar"; import { createPoster } from "@utils/axiosHelpers"; import { Earthquake } from "@prismaclient"; import { getRelativeDate } from "@utils/formatters"; -import GeologicalEvent from "@appTypes/Event"; +import GeologicalEvent from "@appTypes/GeologicalEvent"; import EarthquakeSearchModal from "@components/EarthquakeSearchModal"; import EarthquakeLogModal from "@components/EarthquakeLogModal"; // If you use a separate log modal import { useStoreState } from "@hooks/store"; // Optional: "No Access Modal" - as in your original 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 -

- -
-
- ); + 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 +

+ +
+
+ ); } - export default function Earthquakes() { - const [selectedEventId, setSelectedEventId] = useState(""); - const [hoveredEventId, setHoveredEventId] = useState(""); - const [searchModalOpen, setSearchModalOpen] = useState(false); - const [logModalOpen, setLogModalOpen] = useState(false); - const [noAccessModalOpen, setNoAccessModalOpen] = useState(false); + const [selectedEventId, setSelectedEventId] = useState(""); + const [hoveredEventId, setHoveredEventId] = useState(""); + const [searchModalOpen, setSearchModalOpen] = useState(false); + const [logModalOpen, setLogModalOpen] = useState(false); + const [noAccessModalOpen, setNoAccessModalOpen] = useState(false); - // Your user/role logic - const user = useStoreState((state) => state.user); - const role: "GUEST" | "SCIENTIST" | "ADMIN" = user?.role ?? "GUEST"; - const canLogEarthquake = role === "SCIENTIST" || role === "ADMIN"; + // Your user/role logic + const user = useStoreState((state) => state.user); + const role: "GUEST" | "SCIENTIST" | "ADMIN" = user?.role ?? "GUEST"; + const canLogEarthquake = role === "SCIENTIST" || role === "ADMIN"; - // Fetch earthquakes (10 days recent) - const { data, error, isLoading, mutate } = useSWR( - "/api/earthquakes", - createPoster({ rangeDaysPrev: 10 }) - ); + // Fetch earthquakes (10 days recent) + const { data, error, isLoading, mutate } = useSWR("/api/earthquakes", createPoster({ rangeDaysPrev: 10 })); - // Shape for Map/Sidebar - const earthquakeEvents = useMemo( - () => - data && data.earthquakes - ? data.earthquakes - .map( - (x: Earthquake): GeologicalEvent => ({ - id: x.code, - title: `Earthquake in ${x.location || (x.code && x.code.split("-")[2])}`, - magnitude: x.magnitude, - longitude: x.longitude, - latitude: x.latitude, - text1: "", - text2: getRelativeDate(x.date), - date: x.date, - }) - ) - .sort( - (a: GeologicalEvent, b: GeologicalEvent) => - new Date(b.date).getTime() - new Date(a.date).getTime() - ) - : [], - [data] - ); + // Shape for Map/Sidebar + const earthquakeEvents = useMemo( + () => + data && data.earthquakes + ? data.earthquakes + .map( + (x: Earthquake): GeologicalEvent => ({ + id: x.code, + title: `Earthquake in ${x.location || (x.code && x.code.split("-")[2])}`, + magnitude: x.magnitude, + longitude: x.longitude, + latitude: x.latitude, + text1: "", + text2: getRelativeDate(x.date), + date: x.date, + code: x.code, + }) + ) + .sort((a: GeologicalEvent, b: GeologicalEvent) => new Date(b.date).getTime() - new Date(a.date).getTime()) + : [], + [data] + ); - // Handler for log - const handleLogClick = () => { - if (canLogEarthquake) { - setLogModalOpen(true); - } else { - setNoAccessModalOpen(true); - } - }; + // Handler for log + const handleLogClick = () => { + if (canLogEarthquake) { + setLogModalOpen(true); + } else { + setNoAccessModalOpen(true); + } + }; - return ( -
-
- -
- setSearchModalOpen(true)} - button1Disabled={!canLogEarthquake} - /> - {/* ---- SEARCH MODAL ---- */} - setSearchModalOpen(false)} - onSelect={(eq) => setSelectedEventId(eq.code)} - /> - {/* ---- LOGGING MODAL ---- */} - setLogModalOpen(false)} - onSuccess={() => mutate()} - /> - {/* ---- NO ACCESS ---- */} - setNoAccessModalOpen(false)} - /> -
- ); -} \ No newline at end of file + return ( +
+
+ +
+ setSearchModalOpen(true)} + button1Disabled={!canLogEarthquake} + /> + {/* ---- SEARCH MODAL ---- */} + setSearchModalOpen(false)} + onSelect={(eq) => setSelectedEventId(eq.code)} + /> + {/* ---- LOGGING MODAL ---- */} + setLogModalOpen(false)} onSuccess={() => mutate()} /> + {/* ---- NO ACCESS ---- */} + setNoAccessModalOpen(false)} /> +
+ ); +} diff --git a/src/app/globals.css b/src/app/globals.css index 5fe3514..1367716 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -3,8 +3,8 @@ @tailwind utilities; :root { - --background: #ffffff; - --foreground: #171717; + --background: #ffffff; + --foreground: #171717; } /* @media (prefers-color-scheme: dark) { @@ -15,216 +15,216 @@ } */ body { - color: var(--foreground); - background: var(--background); + color: var(--foreground); + background: var(--background); } /* Increase specificity and use !important where necessary */ .mapboxgl-popup .mapboxgl-popup-content { - @apply rounded-xl p-4 px-5 drop-shadow-lg border border-neutral-300 max-w-xs !important; + @apply rounded-xl p-4 px-5 drop-shadow-lg border border-neutral-300 max-w-xs !important; } /* Hide the popup tip */ .mapboxgl-popup .mapboxgl-popup-tip { - display: none !important; + display: none !important; } /* Child elements */ .mapboxgl-popup-content h3 { - @apply text-sm font-medium text-neutral-800 !important; + @apply text-sm font-medium text-neutral-800 !important; } .mapboxgl-popup-content p { - @apply text-xs text-neutral-600 !important; + @apply text-xs text-neutral-600 !important; } -.mapboxgl-popup-content p+p { - @apply text-neutral-500 !important; +.mapboxgl-popup-content p + p { + @apply text-neutral-500 !important; } .icon-link { - /* default styles if needed */ + /* default styles if needed */ } .icon-link:hover, .icon-link:focus { - background-color: #16424b; + background-color: #16424b; } .icon-link:hover h3, .icon-link:focus h3, .icon-link:hover p, .icon-link:focus p { - color: #fff !important; + color: #fff !important; } .icon-link:hover h3, .icon-link:hover p, .icon-link:focus h3, .icon-link:focus p { - color: #111; - /* or black */ + color: #111; + /* or black */ } /* ---- LAVA FLOOD OVERLAY ---- */ .lava-flood-overlay { - pointer-events: none; - position: fixed; - top: -100vh; - left: 0; - right: 0; - width: 100vw; - height: 100vh; - z-index: 9999; - display: flex; - justify-content: center; - align-items: flex-start; - transition: top 0.9s cubic-bezier(.6, 0, .2, 1); + pointer-events: none; + position: fixed; + top: -100vh; + left: 0; + right: 0; + width: 100vw; + height: 100vh; + z-index: 9999; + display: flex; + justify-content: center; + align-items: flex-start; + transition: top 0.9s cubic-bezier(0.6, 0, 0.2, 1); } .lava-flood-overlay.lava-active { - top: 0; - transition: top 0.33s cubic-bezier(.6, 0, .2, 1); + top: 0; + transition: top 0.33s cubic-bezier(0.6, 0, 0.2, 1); } .lava-flood-overlay img, .lava-gradient { - width: 100vw; - height: 100vh; - object-fit: cover; - pointer-events: none; - user-select: none; - filter: brightness(1.15) saturate(1.8) drop-shadow(0 0 80px #ff5500); + width: 100vw; + height: 100vh; + object-fit: cover; + pointer-events: none; + user-select: none; + filter: brightness(1.15) saturate(1.8) drop-shadow(0 0 80px #ff5500); } /* ---- INSANE SCREEN SHAKE (LinkedIn) ---- */ @keyframes supershake { - 0% { - transform: translate(0, 0) rotate(0); - } + 0% { + transform: translate(0, 0) rotate(0); + } - 5% { - transform: translate(-20px, 5px) rotate(-2deg); - } + 5% { + transform: translate(-20px, 5px) rotate(-2deg); + } - 10% { - transform: translate(18px, -8px) rotate(2deg); - } + 10% { + transform: translate(18px, -8px) rotate(2deg); + } - 15% { - transform: translate(-22px, 8px) rotate(-4deg); - } + 15% { + transform: translate(-22px, 8px) rotate(-4deg); + } - 20% { - transform: translate(22px, -2px) rotate(4deg); - } + 20% { + transform: translate(22px, -2px) rotate(4deg); + } - 25% { - transform: translate(-18px, 12px) rotate(-2deg); - } + 25% { + transform: translate(-18px, 12px) rotate(-2deg); + } - 30% { - transform: translate(18px, -10px) rotate(2deg); - } + 30% { + transform: translate(18px, -10px) rotate(2deg); + } - 35% { - transform: translate(-22px, 14px) rotate(-4deg); - } + 35% { + transform: translate(-22px, 14px) rotate(-4deg); + } - 40% { - transform: translate(22px, -12px) rotate(4deg); - } + 40% { + transform: translate(22px, -12px) rotate(4deg); + } - 45% { - transform: translate(-18px, 8px) rotate(-2deg); - } + 45% { + transform: translate(-18px, 8px) rotate(-2deg); + } - 50% { - transform: translate(18px, -14px) rotate(4deg); - } + 50% { + transform: translate(18px, -14px) rotate(4deg); + } - 55% { - transform: translate(-22px, 12px) rotate(-4deg); - } + 55% { + transform: translate(-22px, 12px) rotate(-4deg); + } - 60% { - transform: translate(22px, -8px) rotate(2deg); - } + 60% { + transform: translate(22px, -8px) rotate(2deg); + } - 65% { - transform: translate(-18px, 10px) rotate(-2deg); - } + 65% { + transform: translate(-18px, 10px) rotate(-2deg); + } - 70% { - transform: translate(18px, -12px) rotate(2deg); - } + 70% { + transform: translate(18px, -12px) rotate(2deg); + } - 75% { - transform: translate(-22px, 14px) rotate(-4deg); - } + 75% { + transform: translate(-22px, 14px) rotate(-4deg); + } - 80% { - transform: translate(22px, -10px) rotate(4deg); - } + 80% { + transform: translate(22px, -10px) rotate(4deg); + } - 85% { - transform: translate(-18px, 8px) rotate(-2deg); - } + 85% { + transform: translate(-18px, 8px) rotate(-2deg); + } - 90% { - transform: translate(18px, -14px) rotate(2deg); - } + 90% { + transform: translate(18px, -14px) rotate(2deg); + } - 95% { - transform: translate(-20px, 5px) rotate(-2deg); - } + 95% { + transform: translate(-20px, 5px) rotate(-2deg); + } - 100% { - transform: translate(0, 0) rotate(0); - } + 100% { + transform: translate(0, 0) rotate(0); + } } .shake-screen { - animation: supershake 1s cubic-bezier(.36, .07, .19, .97) both; + animation: supershake 1s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; } /* ---- CRACK + COLLAPSE OVERLAY (X icon) ---- */ .crack-overlay { - pointer-events: none; - position: fixed; - inset: 0; - width: 100vw; - height: 100vh; - z-index: 9999; - transition: transform 1.1s cubic-bezier(.65, .05, .45, 1), opacity 0.5s; - will-change: transform, opacity; + pointer-events: none; + position: fixed; + inset: 0; + width: 100vw; + height: 100vh; + z-index: 9999; + transition: transform 1.1s cubic-bezier(0.65, 0.05, 0.45, 1), opacity 0.5s; + will-change: transform, opacity; } .crack { - position: absolute; - pointer-events: none; + position: absolute; + pointer-events: none; } .crack1 { - width: 35vw; - left: 10vw; - top: 22vh; - opacity: 0.8; + width: 35vw; + left: 10vw; + top: 22vh; + opacity: 0.8; } .crack2 { - width: 32vw; - right: 12vw; - top: 42vh; - opacity: 0.7; - transform: rotate(-8deg); + width: 32vw; + right: 12vw; + top: 42vh; + opacity: 0.7; + transform: rotate(-8deg); } /* Add more .crackN classes if using more cracks */ /* Collapse falling effect */ .crack-collapse { - transform: perspective(900px) rotateX(75deg) translateY(80vh) scale(0.9); - opacity: 0; - transition: transform 1.1s cubic-bezier(.65, .05, .45, 1), opacity 0.6s; -} \ No newline at end of file + transform: perspective(900px) rotateX(75deg) translateY(80vh) scale(0.9); + opacity: 0; + transition: transform 1.1s cubic-bezier(0.65, 0.05, 0.45, 1), opacity 0.6s; +} diff --git a/src/app/observatories/page.tsx b/src/app/observatories/page.tsx index 6a1ac17..382d797 100644 --- a/src/app/observatories/page.tsx +++ b/src/app/observatories/page.tsx @@ -7,110 +7,94 @@ import LogObservatoryModal from "@/components/LogObservatoryModal"; // Adjust if import { fetcher } from "@utils/axiosHelpers"; import { Observatory } from "@prismaclient"; import { getRelativeDate } from "@utils/formatters"; -import GeologicalEvent from "@appTypes/Event"; +import GeologicalEvent from "@appTypes/GeologicalEvent"; import { useStoreState } from "@hooks/store"; function NoAccessModal({ open, onClose }) { - if (!open) return null; - return ( -
-
- -

No Access

-

Sorry, You do not have access rights, please log in or contact an Admin.

- -
-
- ); + if (!open) return null; + return ( +
+
+ +

No Access

+

Sorry, You do not have access rights, please log in or contact an Admin.

+ +
+
+ ); } export default function Observatories() { - const [selectedEventId, setSelectedEventId] = useState(""); - const [hoveredEventId, setHoveredEventId] = useState(""); - 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 canLogObservatory = role === "SCIENTIST" || role === "ADMIN"; + const [selectedEventId, setSelectedEventId] = useState(""); + const [hoveredEventId, setHoveredEventId] = useState(""); + const [logModalOpen, setLogModalOpen] = useState(false); + const [noAccessModalOpen, setNoAccessModalOpen] = useState(false); - const { data, error, isLoading, mutate } = useSWR( - "/api/observatories", - fetcher - ); + const user = useStoreState((state) => state.user); + const role: "GUEST" | "SCIENTIST" | "ADMIN" = user?.role ?? "GUEST"; + const canLogObservatory = role === "SCIENTIST" || role === "ADMIN"; - const observatoryEvents = useMemo( - () => - data && data.observatories - ? data.observatories - .map((x: Observatory): GeologicalEvent & { isFunctional: boolean } => ({ - id: x.id.toString(), - title: ` ${x.name}`, - longitude: x.longitude, - latitude: x.latitude, - isFunctional: x.isFunctional, // <-- include this! - text1: "", - text2: getRelativeDate(x.dateEstablished), - date: x.dateEstablished, - })) - .sort( - (a: GeologicalEvent, b: GeologicalEvent) => - new Date(b.date).getTime() - new Date(a.date).getTime() - ) - : [], - [data] - ); + const { data, error, isLoading, mutate } = useSWR("/api/observatories", fetcher); - const handleLogClick = () => { - if (canLogObservatory) { - setLogModalOpen(true); - } else { - setNoAccessModalOpen(true); - } - }; + const observatoryEvents = useMemo( + () => + data && data.observatories + ? data.observatories + .map((x: Observatory): GeologicalEvent & { isFunctional: boolean } => ({ + id: x.id.toString(), + title: ` ${x.name}`, + longitude: x.longitude, + latitude: x.latitude, + isFunctional: x.isFunctional, // <-- include this! + text1: "", + text2: getRelativeDate(x.dateEstablished), + date: x.dateEstablished, + })) + .sort((a: GeologicalEvent, b: GeologicalEvent) => new Date(b.date).getTime() - new Date(a.date).getTime()) + : [], + [data] + ); - return ( -
-
- -
- - setLogModalOpen(false)} - onSuccess={() => mutate()} - /> - setNoAccessModalOpen(false)} - /> -
- ); + const handleLogClick = () => { + if (canLogObservatory) { + setLogModalOpen(true); + } else { + setNoAccessModalOpen(true); + } + }; + + return ( +
+
+ +
+ + setLogModalOpen(false)} onSuccess={() => mutate()} /> + setNoAccessModalOpen(false)} /> +
+ ); } diff --git a/src/components/LoggingModal.tsx b/src/components/LoggingModal.tsx index 26d0817..2577230 100644 --- a/src/components/LoggingModal.tsx +++ b/src/components/LoggingModal.tsx @@ -2,7 +2,7 @@ import Link from "next/link"; import React, { Dispatch, SetStateAction, useState } from "react"; import { TbHexagon } from "react-icons/tb"; -import Event from "@appTypes/Event"; +import Event from "@appTypes/GeologicalEvent"; import getMagnitudeColor from "@utils/getMagnitudeColour"; function setButton1(button1Name) { diff --git a/src/components/Map.tsx b/src/components/Map.tsx index 9472d62..84b93dd 100644 --- a/src/components/Map.tsx +++ b/src/components/Map.tsx @@ -5,7 +5,7 @@ import React, { Dispatch, SetStateAction, useCallback, useEffect, useRef, useSta import { createRoot } from "react-dom/client"; import { GiObservatory } from "react-icons/gi"; -import GeologicalEvent from "@appTypes/Event"; +import GeologicalEvent from "@appTypes/GeologicalEvent"; import getMagnitudeColor from "@utils/getMagnitudeColour"; interface MapComponentProps { @@ -111,12 +111,12 @@ function MapComponent({ } const observatoryElement = document.createElement("div"); - const root = createRoot(observatoryElement); - root.render( - - ); + const root = createRoot(observatoryElement); + root.render( + + ); quakeElement.appendChild(pulseElement); quakeElement.appendChild(dotElement); @@ -128,6 +128,7 @@ function MapComponent({ const popup = new mapboxgl.Popup({ offset: 25, closeButton: false, anchor: "bottom" }).setHTML(`

${event.title}

+ ${mapType !== "observatories" ? `

${event.code}` : null} ${mapType === "observatories" ? `

${event.text1}

` : `

Magnitude: ${event.magnitude}

`}

${event.text2}

diff --git a/src/types/Event.ts b/src/types/GeologicalEvent.ts similarity index 82% rename from src/types/Event.ts rename to src/types/GeologicalEvent.ts index 040697a..b96b5aa 100644 --- a/src/types/Event.ts +++ b/src/types/GeologicalEvent.ts @@ -3,8 +3,10 @@ interface GeologicalEvent { date: Date; title: string; magnitude?: number; + code?: string; longitude: number; latitude: number; + isFunctional?: boolean; text1: string; text2: string; }