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
-
-
OK
-
-
- );
+ 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
+
+
+ OK
+
+
+
+ );
}
-
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.
-
OK
-
-
- );
+ if (!open) return null;
+ return (
+
+
+
+ ×
+
+
No Access
+
Sorry, You do not have access rights, please log in or contact an Admin.
+
+ OK
+
+
+
+ );
}
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;
}