Created styling standard

This commit is contained in:
Tim Howitz 2025-05-04 16:04:44 +01:00
parent 92fbd9369c
commit ec25bf0725
26 changed files with 551 additions and 505 deletions

62
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,62 @@
{
"editor.insertSpaces": true,
// Ignores the warning when Git is missing
"git.ignoreMissingGitWarning": true,
// Disable crash reports being sent to Microsoft.
"telemetry.enableCrashReporter": false,
// Disable usage data and errors being sent to Microsoft.
"telemetry.enableTelemetry": false,
"editor.formatOnSave": true,
"cmake.configureOnOpen": false,
"[python]": {
"editor.defaultFormatter": "ms-python.autopep8",
"editor.formatOnSave": true
},
"autopep8.args": ["--max-line-length=200"],
"editor.detectIndentation": false,
"git.confirmSync": false,
"editor.rulers": [
{
"column": 120
}
],
"git.autofetch": true,
"files.autoSave": "onFocusChange",
"cSpell.language": "en-GB",
"C_Cpp.formatting": "disabled",
"cmake.showOptionsMovedNotification": false,
"diffEditor.renderSideBySide": false,
"terminal.integrated.commandsToSkipShell": ["matlab.interrupt"],
"[matlab]": {
"editor.defaultFormatter": "AffenWiesel.matlab-formatter"
},
"workbench.editorAssociations": {
"*.pdf": "latex-workshop-pdf-hook"
},
"notebook.output.textLineLimit": 50,
"prettier.printWidth": 130,
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"prettier.useTabs": true,
"editor.tabSize": 4,
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[cpp]": {
"editor.defaultFormatter": "ms-vscode.cpptools"
},
"C_Cpp.default.cppStandard": "gnu++23",
"C_Cpp.default.cStandard": "gnu23",
"diffEditor.hideUnchangedRegions.enabled": true,
"python.createEnvironment.trigger": "off",
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"importSorter.generalConfiguration.sortOnBeforeSave": true,
"cSpell.words": [
"vars"
]
}

View File

@ -6,11 +6,12 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename); const __dirname = dirname(__filename);
const compat = new FlatCompat({ const compat = new FlatCompat({
baseDirectory: __dirname, baseDirectory: __dirname,
}); });
const eslintConfig = [ export default tseslint.config([...compat.extends("next/core-web-vitals", "next/typescript")], {
...compat.extends("next/core-web-vitals", "next/typescript"), rules: {
]; "no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error",
export default eslintConfig; },
});

View File

@ -1,7 +1,7 @@
import type { NextConfig } from "next"; import type { NextConfig } from "next";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
/* config options here */ /* config options here */
}; };
export default nextConfig; export default nextConfig;

View File

@ -1,6 +1,6 @@
import { NextResponse } from 'next/server'; import { NextResponse } from "next/server";
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from "@prisma/client";
const usingPrisma = false; const usingPrisma = false;
let prisma: PrismaClient; let prisma: PrismaClient;

View File

@ -1,69 +1,69 @@
import path from "path";
import fs from "fs";
import csv from "csv-parser"; import csv from "csv-parser";
import fs from "fs";
import path from "path";
export type User = { export type User = {
name: string; name: string;
email: string; email: string;
password: string; password: string;
accessLevel: string; accessLevel: string;
}; };
/** /**
* @returns {array} - Array of Objects containing user data * @returns {array} - Array of Objects containing user data
*/ */
export async function readUserCsv(): Promise<User[]> { export async function readUserCsv(): Promise<User[]> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Dynamic CSV location generation // Dynamic CSV location generation
let csvPath = path.dirname(__dirname); // /login let csvPath = path.dirname(__dirname); // /login
csvPath = path.dirname(csvPath); // /api csvPath = path.dirname(csvPath); // /api
csvPath = path.dirname(csvPath); // /app csvPath = path.dirname(csvPath); // /app
csvPath = path.dirname(csvPath); // /src csvPath = path.dirname(csvPath); // /src
csvPath = path.dirname(csvPath); // /[project] csvPath = path.dirname(csvPath); // /[project]
csvPath = path.dirname(csvPath); // /termor-tracker csvPath = path.dirname(csvPath); // /termor-tracker
csvPath = path.join(csvPath, "src", "databases", "Users.csv"); csvPath = path.join(csvPath, "src", "databases", "Users.csv");
// Forms array for user data // Forms array for user data
let results: User[] = []; let results: User[] = [];
// Reads data and adds it to results // Reads data and adds it to results
fs.createReadStream(csvPath) fs.createReadStream(csvPath)
.pipe(csv()) .pipe(csv())
.on("data", (data) => results.push(data)) .on("data", (data) => results.push(data))
.on("end", () => { .on("end", () => {
resolve(results); resolve(results);
}) })
.on("error", (error) => { .on("error", (error) => {
reject(error); reject(error);
}); });
}); });
} }
export function findUserByEmail(users: User[], email: string): User | undefined { export function findUserByEmail(users: User[], email: string): User | undefined {
return users.find((user) => user.email === email); return users.find((user) => user.email === email);
} }
export async function passwordStrengthCheck(password: string): Promise<string> { export async function passwordStrengthCheck(password: string): Promise<string> {
if (password.length < 8) { if (password.length < 8) {
return "short"; return "short";
} else if (password.length > 16) { } else if (password.length > 16) {
return "long"; return "long";
} }
const lowercaseRegex = /[a-z]/; const lowercaseRegex = /[a-z]/;
const uppercaseRegex = /[A-Z]/; const uppercaseRegex = /[A-Z]/;
const digitRegex = /\d/; const digitRegex = /\d/;
const specialCharRegex = /[!@#$%^&*]/; const specialCharRegex = /[!@#$%^&*]/;
if (!lowercaseRegex.test(password)) { if (!lowercaseRegex.test(password)) {
return "no lower"; return "no lower";
} else if (!uppercaseRegex.test(password)) { } else if (!uppercaseRegex.test(password)) {
return "no upper"; return "no upper";
} else if (!digitRegex.test(password)) { } else if (!digitRegex.test(password)) {
return "no digit"; return "no digit";
} else if (!specialCharRegex.test(password)) { } else if (!specialCharRegex.test(password)) {
return "no special"; return "no special";
} else { } else {
return "secure"; return "secure";
} }
return "end of function"; return "end of function";
} }
/** /**
@ -72,28 +72,28 @@ export async function passwordStrengthCheck(password: string): Promise<string> {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
export async function writeUserCsv(users: User[]): Promise<void> { export async function writeUserCsv(users: User[]): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Dynamic CSV location generation // Dynamic CSV location generation
let csvPath = path.dirname(__dirname); // /login let csvPath = path.dirname(__dirname); // /login
csvPath = path.dirname(csvPath); // /api csvPath = path.dirname(csvPath); // /api
csvPath = path.dirname(csvPath); // /app csvPath = path.dirname(csvPath); // /app
csvPath = path.dirname(csvPath); // /src csvPath = path.dirname(csvPath); // /src
csvPath = path.dirname(csvPath); // /[project] csvPath = path.dirname(csvPath); // /[project]
csvPath = path.dirname(csvPath); // /termor-tracker csvPath = path.dirname(csvPath); // /termor-tracker
csvPath = path.join(csvPath, "src", "databases", "Users.csv"); csvPath = path.join(csvPath, "src", "databases", "Users.csv");
// Prepare CSV data as a string // Prepare CSV data as a string
const headers = "name,email,password,level"; // CSV headers const headers = "name,email,password,level"; // CSV headers
const rows = users.map((user) => `${user.name},${user.email},${user.password},${user.accessLevel}`); const rows = users.map((user) => `${user.name},${user.email},${user.password},${user.accessLevel}`);
const csvData = `${headers}\n${rows.join("\n")}`; const csvData = `${headers}\n${rows.join("\n")}`;
// Write data to the file // Write data to the file
fs.writeFile(csvPath, csvData, (error) => { fs.writeFile(csvPath, csvData, (error) => {
if (error) { if (error) {
reject(error); // Reject promise on error reject(error); // Reject promise on error
} else { } else {
resolve(); // Resolve the promise if successful resolve(); // Resolve the promise if successful
} }
}); });
}); });
} }

View File

@ -1,9 +1,9 @@
import bcrypt from 'bcrypt'; import bcrypt from "bcrypt";
import { NextResponse } from 'next/server'; import { NextResponse } from "next/server";
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from "@prisma/client";
import { findUserByEmail, readUserCsv, User } from '../functions/csvReadWrite'; import { findUserByEmail, readUserCsv, User } from "../functions/csvReadWrite";
const usingPrisma = false; const usingPrisma = false;
let prisma: PrismaClient; let prisma: PrismaClient;

View File

@ -1,6 +1,6 @@
import { NextResponse } from 'next/server'; import { NextResponse } from "next/server";
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from "@prisma/client";
const usingPrisma = false; const usingPrisma = false;
let prisma: PrismaClient; let prisma: PrismaClient;

View File

@ -1,11 +1,9 @@
import bcrypt from 'bcrypt'; import bcrypt from "bcrypt";
import { NextResponse } from 'next/server'; import { NextResponse } from "next/server";
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from "@prisma/client";
import { import { findUserByEmail, passwordStrengthCheck, readUserCsv, User, writeUserCsv } from "../functions/csvReadWrite";
findUserByEmail, passwordStrengthCheck, readUserCsv, User, writeUserCsv
} from '../functions/csvReadWrite';
const usingPrisma = false; const usingPrisma = false;
let prisma: PrismaClient; let prisma: PrismaClient;

View File

@ -1,6 +1,6 @@
"use client"; "use client";
import Image from 'next/image'; import Image from "next/image";
import React, { useState } from 'react'; import React, { useState } from "react";
const ContactUs = () => { const ContactUs = () => {
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({

View File

@ -1,11 +1,11 @@
"use client"; "use client";
import { useMemo, useState } from 'react'; import { useMemo, useState } from "react";
import useSWR from 'swr'; import useSWR from "swr";
import Map from '@components/Map'; import Map from "@components/Map";
import Sidebar from '@components/Sidebar'; import Sidebar from "@components/Sidebar";
import { fetcher } from '@utils/fetcher'; import { fetcher } from "@utils/fetcher";
export default function Earthquakes() { export default function Earthquakes() {
const [selectedEventId, setSelectedEventId] = useState(""); const [selectedEventId, setSelectedEventId] = useState("");

View File

@ -3,8 +3,8 @@
@tailwind utilities; @tailwind utilities;
:root { :root {
--background: #ffffff; --background: #ffffff;
--foreground: #171717; --foreground: #171717;
} }
/* @media (prefers-color-scheme: dark) { /* @media (prefers-color-scheme: dark) {
@ -15,29 +15,29 @@
} */ } */
body { body {
color: var(--foreground); color: var(--foreground);
background: var(--background); background: var(--background);
} }
/* Increase specificity and use !important where necessary */ /* Increase specificity and use !important where necessary */
.mapboxgl-popup .mapboxgl-popup-content { .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 */ /* Hide the popup tip */
.mapboxgl-popup .mapboxgl-popup-tip { .mapboxgl-popup .mapboxgl-popup-tip {
display: none !important; display: none !important;
} }
/* Child elements */ /* Child elements */
.mapboxgl-popup-content h3 { .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 { .mapboxgl-popup-content p {
@apply text-xs text-neutral-600 !important; @apply text-xs text-neutral-600 !important;
} }
.mapboxgl-popup-content p + p { .mapboxgl-popup-content p + p {
@apply text-neutral-500 !important; @apply text-neutral-500 !important;
} }

View File

@ -1,11 +1,11 @@
"use client"; "use client";
import { useMemo, useState } from 'react'; import { useMemo, useState } from "react";
import useSWR from 'swr'; import useSWR from "swr";
import Sidebar from '@/components/Sidebar'; import Sidebar from "@/components/Sidebar";
import Map from '@components/Map'; import Map from "@components/Map";
import { fetcher } from '@utils/fetcher'; import { fetcher } from "@utils/fetcher";
export default function Observatories() { export default function Observatories() {
const [selectedEventId, setSelectedEventId] = useState(""); const [selectedEventId, setSelectedEventId] = useState("");

View File

@ -1,77 +1,53 @@
"use client"; "use client";
const OurMission = () => { const OurMission = () => {
return ( return (
<div <div
className="h-screen relative bg-fixed bg-cover bg-center text-white py-10" className="h-screen relative bg-fixed bg-cover bg-center text-white py-10"
style={{ backgroundImage: "url('destruction.jpg')" }} style={{ backgroundImage: "url('destruction.jpg')" }}
> >
{/* Overlay to Improve Text Readability */} {/* Overlay to Improve Text Readability */}
<div className="absolute inset-0 bg-black bg-opacity-40"></div> <div className="absolute inset-0 bg-black bg-opacity-40"></div>
{/* Content Area */} {/* Content Area */}
<div className="relative z-10 max-w-4xl mx-auto p-5 bg-white bg-opacity-90 shadow-lg rounded-lg"> <div className="relative z-10 max-w-4xl mx-auto p-5 bg-white bg-opacity-90 shadow-lg rounded-lg">
<h1 className="text-3xl font-bold text-center text-gray-800 mb-6"> <h1 className="text-3xl font-bold text-center text-gray-800 mb-6">Our Mission</h1>
Our Mission <p className="text-lg text-gray-600 leading-relaxed mb-4">
</h1> At <span className="font-semibold text-blue-600">Earthquake Awareness Initiative</span>, our mission is to help people
<p className="text-lg text-gray-600 leading-relaxed mb-4"> worldwide prepare for and recover from earthquakes. Through education, research, and innovative technology, we work
At{" "} tirelessly to empower communities with the knowledge they need to stay safe before, during, and after seismic events.
<span className="font-semibold text-blue-600"> </p>
Earthquake Awareness Initiative <p className="text-lg text-gray-600 leading-relaxed mb-4">
</span> We aim to bridge the gap between scientific research and community awareness by providing resources, tools, and
, our mission is to help people worldwide prepare for and recover real-time updates for earthquake preparedness. Together, we aspire to save lives, mitigate impacts, and foster
from earthquakes. Through education, research, and innovative resilience against nature's powerful forces.
technology, we work tirelessly to empower communities with the </p>
knowledge they need to stay safe before, during, and after seismic <div className="flex flex-col md:flex-row md:justify-evenly items-center mt-6">
events. <div className="flex flex-col items-center p-10">
</p> <img src="education.png" alt="Education Icon" className="h-20 w-20 mb-8" />
<p className="text-lg text-gray-600 leading-relaxed mb-4"> <h3 className="text-xl font-bold text-gray-700 mb-2">Education</h3>
We aim to bridge the gap between scientific research and community <p className="text-sm text-gray-500 text-center">
awareness by providing resources, tools, and real-time updates for Providing accessible resources to educate people about earthquake preparedness.
earthquake preparedness. Together, we aspire to save lives, mitigate </p>
impacts, and foster resilience against nature's powerful forces. </div>
</p> <div className="flex flex-col items-center p-10">
<div className="flex flex-col md:flex-row md:justify-evenly items-center mt-6"> <img src="research.jpg" alt="Research Icon" className="h-20 w-20 mb-4" />
<div className="flex flex-col items-center p-10"> <h3 className="text-xl font-bold text-gray-700 mb-2">Research</h3>
<img <p className="text-sm text-gray-500 text-center">
src="education.png" Supporting scientific studies to enhance understanding of seismic activity.
alt="Education Icon" </p>
className="h-20 w-20 mb-8" </div>
/> <div className="flex flex-col items-center p-10">
<h3 className="text-xl font-bold text-gray-700 mb-2">Education</h3> <img src="tech.jpg" alt="Technology Icon" className="h-20 w-20 mb-8" />
<p className="text-sm text-gray-500 text-center"> <h3 className="text-xl font-bold text-gray-700 mb-2">Technology</h3>
Providing accessible resources to educate people about earthquake <p className="text-sm text-gray-500 text-center">
preparedness. Leveraging innovation to deliver real-time alerts and safety tools.
</p> </p>
</div> </div>
<div className="flex flex-col items-center p-10"> </div>
<img </div>
src="research.jpg" </div>
alt="Research Icon" );
className="h-20 w-20 mb-4"
/>
<h3 className="text-xl font-bold text-gray-700 mb-2">Research</h3>
<p className="text-sm text-gray-500 text-center">
Supporting scientific studies to enhance understanding of seismic
activity.
</p>
</div>
<div className="flex flex-col items-center p-10">
<img
src="tech.jpg"
alt="Technology Icon"
className="h-20 w-20 mb-8"
/>
<h3 className="text-xl font-bold text-gray-700 mb-2">Technology</h3>
<p className="text-sm text-gray-500 text-center">
Leveraging innovation to deliver real-time alerts and safety
tools.
</p>
</div>
</div>
</div>
</div>
);
return ( return (
<div className="min-h-screen bg-neutral-100 flex flex-col items-center justify-center py-10"> <div className="min-h-screen bg-neutral-100 flex flex-col items-center justify-center py-10">
<div className="max-w-4xl mx-auto p-5 bg-white shadow-lg rounded-lg"> <div className="max-w-4xl mx-auto p-5 bg-white shadow-lg rounded-lg">

View File

@ -1,128 +1,126 @@
import Image from "next/image"; import Image from "next/image";
export default function Home() { export default function Home() {
return ( return (
<main className="min-h-screen text-black"> <main className="min-h-screen text-black">
<div className="w-full relative overflow-hidden"> <div className="w-full relative overflow-hidden">
<div className=""> <div className="">
<Image height={2000} width={2000} alt="Background Image" src="/lava_flows.jpg"></Image> <Image height={2000} width={2000} alt="Background Image" src="/lava_flows.jpg"></Image>
</div> </div>
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-black/10 to-black/40"></div> <div className="absolute inset-0 bg-gradient-to-b from-transparent via-black/10 to-black/40"></div>
<div className="absolute inset-0 top-[30%]"> <div className="absolute inset-0 top-[30%]">
<Image className="mx-auto" height={300} width={1000} alt="Title Image" src="/tremortrackertext.png"></Image> <Image className="mx-auto" height={300} width={1000} alt="Title Image" src="/tremortrackertext.png"></Image>
</div> </div>
</div> </div>
<div className="mx-auto mt-20 w-4/6 px-2 border border-black divide-y">
{["Earthquake 1", "Earthquake 2", "Earthquake 3"].map((name) => (
<div className="px-5 py-5" key={name}>
<p className="ml-3">{name}</p>
<p></p>
</div>
))}
</div>
<p className="mt-96">Spacer</p>
<p className="mt-96">Spacer</p>
</main>
);
// return (
// <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
// <main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
// <Image
// className="dark:invert"
// src="/next.svg"
// alt="Next.js logo"
// width={180}
// height={38}
// priority
// />
// <ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
// <li className="mb-2">
// Get started by editing{" "}
// <code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
// src/app/page.tsx
// </code>
// .
// </li>
// <li>Save and see your changes instantly.</li>
// </ol>
<div className="mx-auto mt-20 w-4/6 px-2 border border-black divide-y"> // <div className="flex gap-4 items-center flex-col sm:flex-row">
{["Earthquake 1", "Earthquake 2", "Earthquake 3"].map((name) => ( // <a
<div className="px-5 py-5" key={name}> // className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
<p className="ml-3">{name}</p> // href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
<p></p> // target="_blank"
</div> // rel="noopener noreferrer"
))} // >
</div> // <Image
<p className="mt-96">Spacer</p> // className="dark:invert"
<p className="mt-96">Spacer</p> // src="/vercel.svg"
</main> // alt="Vercel logomark"
); // width={20}
// height={20}
// return ( // />
// <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]"> // Deploy now
// <main className="flex flex-col gap-8 row-start-2 items-center sm:items-start"> // </a>
// <Image // <a
// className="dark:invert" // className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
// src="/next.svg" // href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
// alt="Next.js logo" // target="_blank"
// width={180} // rel="noopener noreferrer"
// height={38} // >
// priority // Read our docs
// /> // </a>
// <ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]"> // </div>
// <li className="mb-2"> // </main>
// Get started by editing{" "} // <footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
// <code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold"> // <a
// src/app/page.tsx // className="flex items-center gap-2 hover:underline hover:underline-offset-4"
// </code> // href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
// . // target="_blank"
// </li> // rel="noopener noreferrer"
// <li>Save and see your changes instantly.</li> // >
// </ol> // <Image
// aria-hidden
// <div className="flex gap-4 items-center flex-col sm:flex-row"> // src="/file.svg"
// <a // alt="File icon"
// className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5" // width={16}
// href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" // height={16}
// target="_blank" // />
// rel="noopener noreferrer" // Learn
// > // </a>
// <Image // <a
// className="dark:invert" // className="flex items-center gap-2 hover:underline hover:underline-offset-4"
// src="/vercel.svg" // href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
// alt="Vercel logomark" // target="_blank"
// width={20} // rel="noopener noreferrer"
// height={20} // >
// /> // <Image
// Deploy now // aria-hidden
// </a> // src="/window.svg"
// <a // alt="Window icon"
// className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44" // width={16}
// href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" // height={16}
// target="_blank" // />
// rel="noopener noreferrer" // Examples
// > // </a>
// Read our docs // <a
// </a> // className="flex items-center gap-2 hover:underline hover:underline-offset-4"
// </div> // href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
// </main> // target="_blank"
// <footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center"> // rel="noopener noreferrer"
// <a // >
// className="flex items-center gap-2 hover:underline hover:underline-offset-4" // <Image
// href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" // aria-hidden
// target="_blank" // src="/globe.svg"
// rel="noopener noreferrer" // alt="Globe icon"
// > // width={16}
// <Image // height={16}
// aria-hidden // />
// src="/file.svg" // Go to nextjs.org →
// alt="File icon" // </a>
// width={16} // </footer>
// height={16} // </div>
// /> // );
// Learn
// </a>
// <a
// className="flex items-center gap-2 hover:underline hover:underline-offset-4"
// href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
// target="_blank"
// rel="noopener noreferrer"
// >
// <Image
// aria-hidden
// src="/window.svg"
// alt="Window icon"
// width={16}
// height={16}
// />
// Examples
// </a>
// <a
// className="flex items-center gap-2 hover:underline hover:underline-offset-4"
// href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
// target="_blank"
// rel="noopener noreferrer"
// >
// <Image
// aria-hidden
// src="/globe.svg"
// alt="Globe icon"
// width={16}
// height={16}
// />
// Go to nextjs.org →
// </a>
// </footer>
// </div>
// );
} }

View File

@ -1,11 +1,11 @@
"use client"; "use client";
import Image from 'next/image'; import Image from "next/image";
import { Dispatch, SetStateAction, useCallback, useState } from 'react'; import { Dispatch, SetStateAction, useCallback, useState } from "react";
import Artifact from '@appTypes/Artifact'; import Artifact from "@appTypes/Artifact";
import { Currency } from '@appTypes/StoreModel'; import { Currency } from "@appTypes/StoreModel";
import Sidebar from '@components/Sidebar'; import Sidebar from "@components/Sidebar";
import { useStoreState } from '@hooks/store'; import { useStoreState } from "@hooks/store";
// Artifacts Data // Artifacts Data
const artifacts: Artifact[] = [ const artifacts: Artifact[] = [

View File

@ -1,5 +1,5 @@
"use client"; // Required for components in Next.js (using client-side rendering) "use client"; // Required for components in Next.js (using client-side rendering)
import Image from 'next/image'; import Image from "next/image";
const teamMembers = [ const teamMembers = [
{ {

View File

@ -1,7 +1,7 @@
export default function User() { export default function User() {
return ( return (
<div className="w-full h-full"> <div className="w-full h-full">
<p>User</p> <p>User</p>
</div> </div>
); );
} }

View File

@ -1,6 +1,7 @@
"use client"; "use client";
import { useMemo, useState } from "react";
import Sidebar from "@/components/Sidebar"; import Sidebar from "@/components/Sidebar";
import { useState, useMemo } from "react";
export default function Warehouse() { export default function Warehouse() {
const [selectedEventId, setSelectedEventId] = useState(""); const [selectedEventId, setSelectedEventId] = useState("");

View File

@ -1,6 +1,6 @@
"use client"; "use client";
import axios from 'axios'; import axios from "axios";
import { FormEvent, MouseEvent, useEffect, useRef, useState } from 'react'; import { FormEvent, MouseEvent, useEffect, useRef, useState } from "react";
interface AuthModalProps { interface AuthModalProps {
isOpen: boolean; // bool for if the modal should be visible isOpen: boolean; // bool for if the modal should be visible

View File

@ -1,191 +1,200 @@
import { useCallback, useState } from "react";
import React, { useRef, useEffect, Dispatch, SetStateAction } from "react";
import mapboxgl, { LngLatBounds } from "mapbox-gl";
import "mapbox-gl/dist/mapbox-gl.css"; import "mapbox-gl/dist/mapbox-gl.css";
import { GiObservatory } from "react-icons/gi";
import Event from "@appTypes/Event"; import mapboxgl, { LngLatBounds } from "mapbox-gl";
import getMagnitudeColor from "@utils/getMagnitudeColour";
import { userAgentFromString } from "next/server"; import { userAgentFromString } from "next/server";
import React, { Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import { GiObservatory } from "react-icons/gi";
import Event from "@appTypes/Event";
import getMagnitudeColor from "@utils/getMagnitudeColour";
interface MapComponentProps { interface MapComponentProps {
events: Event[]; events: Event[];
selectedEventId: Event["id"]; selectedEventId: Event["id"];
setSelectedEventId: Dispatch<SetStateAction<string>>; setSelectedEventId: Dispatch<SetStateAction<string>>;
hoveredEventId: Event["id"]; hoveredEventId: Event["id"];
setHoveredEventId: Dispatch<SetStateAction<string>>; setHoveredEventId: Dispatch<SetStateAction<string>>;
mapType: String; mapType: String;
} }
// Map component with location-style pulsing dots, animations, and tooltips // Map component with location-style pulsing dots, animations, and tooltips
function MapComponent({ events, selectedEventId, setSelectedEventId, hoveredEventId, setHoveredEventId, mapType }: MapComponentProps) { function MapComponent({
const map = useRef<mapboxgl.Map | null>(null); events,
const markers = useRef<{ [key: string]: mapboxgl.Marker }>({}); selectedEventId,
const [mapBounds, setMapBounds] = useState<LngLatBounds>(); setSelectedEventId,
hoveredEventId,
setHoveredEventId,
mapType,
}: MapComponentProps) {
const map = useRef<mapboxgl.Map | null>(null);
const markers = useRef<{ [key: string]: mapboxgl.Marker }>({});
const [mapBounds, setMapBounds] = useState<LngLatBounds>();
const fitToBounds = useCallback((bounds: LngLatBounds) => { const fitToBounds = useCallback((bounds: LngLatBounds) => {
if (map.current && bounds) { if (map.current && bounds) {
map.current!.fitBounds(bounds, { padding: 150, maxZoom: 10 }); map.current!.fitBounds(bounds, { padding: 150, maxZoom: 10 });
} }
}, []); }, []);
const showPopup = useCallback((eventId: string) => { const showPopup = useCallback((eventId: string) => {
const marker = markers.current[eventId]; const marker = markers.current[eventId];
if (marker && map.current) { if (marker && map.current) {
marker.getPopup()?.addTo(map.current); marker.getPopup()?.addTo(map.current);
} }
}, []); }, []);
const clearAllPopups = useCallback(() => { const clearAllPopups = useCallback(() => {
Object.values(markers.current).forEach((marker) => marker.getPopup()?.remove()); Object.values(markers.current).forEach((marker) => marker.getPopup()?.remove());
}, []); }, []);
const flyToEvent = useCallback((event: Event) => { const flyToEvent = useCallback((event: Event) => {
if (map.current) { if (map.current) {
map.current.flyTo({ map.current.flyTo({
center: [event.longitude, event.latitude], center: [event.longitude, event.latitude],
zoom: 4, zoom: 4,
speed: 1.5, speed: 1.5,
curve: 1.42, curve: 1.42,
}); });
} }
}, []); }, []);
useEffect(() => { useEffect(() => {
mapboxgl.accessToken = "pk.eyJ1IjoidGltaG93aXR6IiwiYSI6ImNtOGtjcXA5bDA3Ym4ya3NnOWxjbjlxZG8ifQ.6u_KgXEdLTakz910QRAorQ"; mapboxgl.accessToken = "pk.eyJ1IjoidGltaG93aXR6IiwiYSI6ImNtOGtjcXA5bDA3Ym4ya3NnOWxjbjlxZG8ifQ.6u_KgXEdLTakz910QRAorQ";
try { try {
map.current = new mapboxgl.Map({ map.current = new mapboxgl.Map({
container: "map-container", container: "map-container",
style: "mapbox://styles/mapbox/light-v10", style: "mapbox://styles/mapbox/light-v10",
center: [0, 0], center: [0, 0],
zoom: 1, zoom: 1,
}); });
} catch (error) { } catch (error) {
console.error("Map initialization failed:", error); console.error("Map initialization failed:", error);
return; return;
} }
map.current.on("load", () => { map.current.on("load", () => {
// Fit map to bounds // Fit map to bounds
const bounds = new mapboxgl.LngLatBounds(); const bounds = new mapboxgl.LngLatBounds();
events.forEach((event) => { events.forEach((event) => {
bounds.extend([event.longitude, event.latitude]); bounds.extend([event.longitude, event.latitude]);
}); });
fitToBounds(bounds); fitToBounds(bounds);
setMapBounds(bounds); setMapBounds(bounds);
// Add markers with location pulse // Add markers with location pulse
events.forEach((event) => { events.forEach((event) => {
const quakeElement = document.createElement("div");
const dotElement = document.createElement("div");
const pulseElement = document.createElement("div");
const quakeElement = document.createElement("div"); if (event.magnitude) {
const dotElement = document.createElement("div"); const color = getMagnitudeColor(event.magnitude);
const pulseElement = document.createElement("div");
if (event.magnitude) { // Create marker container
const color = getMagnitudeColor(event.magnitude); quakeElement.style.width = "50px"; // Increased size to accommodate pulse
quakeElement.style.height = "50px";
quakeElement.style.position = "absolute";
quakeElement.style.display = "flex";
quakeElement.style.alignItems = "center";
quakeElement.style.justifyContent = "center";
// Create marker container // Central dot
quakeElement.style.width = "50px"; // Increased size to accommodate pulse dotElement.style.width = "10px";
quakeElement.style.height = "50px"; dotElement.style.height = "10px";
quakeElement.style.position = "absolute"; dotElement.style.backgroundColor = color;
quakeElement.style.display = "flex"; dotElement.style.borderRadius = "50%";
quakeElement.style.alignItems = "center"; dotElement.style.position = "absolute";
quakeElement.style.justifyContent = "center"; dotElement.style.zIndex = "2"; // Ensure dot is above pulse
// Central dot // Pulsing ring
dotElement.style.width = "10px"; pulseElement.className = "location-pulse";
dotElement.style.height = "10px"; pulseElement.style.width = "20px"; // Initial size
dotElement.style.backgroundColor = color; pulseElement.style.height = "20px";
dotElement.style.borderRadius = "50%"; pulseElement.style.backgroundColor = `${color}80`; // Color with 50% opacity (hex alpha)
dotElement.style.position = "absolute"; pulseElement.style.borderRadius = "50%";
dotElement.style.zIndex = "2"; // Ensure dot is above pulse pulseElement.style.position = "absolute";
pulseElement.style.zIndex = "1";
}
// Pulsing ring // Observatory marker
pulseElement.className = "location-pulse"; //<GiObservatory />
pulseElement.style.width = "20px"; // Initial size const observatoryElement = document.createElement("div");
pulseElement.style.height = "20px"; const root = createRoot(observatoryElement); // `createRoot` is now the standard API
pulseElement.style.backgroundColor = `${color}80`; // Color with 50% opacity (hex alpha) root.render(<GiObservatory className="text-blue-600 text-2xl drop-shadow-lg" />);
pulseElement.style.borderRadius = "50%";
pulseElement.style.position = "absolute";
pulseElement.style.zIndex = "1";
}
// Observatory marker quakeElement.appendChild(pulseElement);
//<GiObservatory /> quakeElement.appendChild(dotElement);
const observatoryElement = document.createElement("div");
const root = createRoot(observatoryElement); // `createRoot` is now the standard API
root.render(
<GiObservatory className="text-blue-600 text-2xl drop-shadow-lg" />
);
quakeElement.appendChild(pulseElement); const marker = new mapboxgl.Marker({ element: mapType === "observatories" ? observatoryElement : quakeElement })
quakeElement.appendChild(dotElement); .setLngLat([event.longitude, event.latitude])
.addTo(map.current!);
const marker = new mapboxgl.Marker({ element: mapType === "observatories" ? observatoryElement : quakeElement }).setLngLat([event.longitude, event.latitude]).addTo(map.current!); const popup = new mapboxgl.Popup({ offset: 25, closeButton: false, anchor: "bottom" }).setHTML(`
const popup = new mapboxgl.Popup({ offset: 25, closeButton: false, anchor: "bottom" }).setHTML(`
<div> <div>
<h3>${event.title}</h3> <h3>${event.title}</h3>
${ mapType === "observatories" ? `<p>${event.text1}</p>` : `<p>Magnitude: ${event.magnitude}</p>`} ${mapType === "observatories" ? `<p>${event.text1}</p>` : `<p>Magnitude: ${event.magnitude}</p>`}
<p>${event.text2}</p> <p>${event.text2}</p>
</div> </div>
`); `);
marker.setPopup(popup); marker.setPopup(popup);
markers.current[event.id] = marker; markers.current[event.id] = marker;
// Add hover events // Add hover events
const markerDomElement = marker.getElement(); const markerDomElement = marker.getElement();
markerDomElement.style.cursor = "pointer"; // Optional: indicate interactivity markerDomElement.style.cursor = "pointer"; // Optional: indicate interactivity
markerDomElement.addEventListener("mouseenter", () => setHoveredEventId(event.id)); markerDomElement.addEventListener("mouseenter", () => setHoveredEventId(event.id));
markerDomElement.addEventListener("mouseleave", () => setHoveredEventId("")); markerDomElement.addEventListener("mouseleave", () => setHoveredEventId(""));
markerDomElement.addEventListener("click", () => setSelectedEventId((prevEventId) => (prevEventId === event.id ? "" : event.id))); markerDomElement.addEventListener("click", () =>
setSelectedEventId((prevEventId) => (prevEventId === event.id ? "" : event.id))
);
// Cleanup event listeners on unmount // Cleanup event listeners on unmount
markerDomElement.dataset.listenersAdded = "true"; // Mark for cleanup markerDomElement.dataset.listenersAdded = "true"; // Mark for cleanup
}); });
}); });
map.current.on("error", (e) => { map.current.on("error", (e) => {
console.error("Mapbox error:", e); console.error("Mapbox error:", e);
}); });
return () => { return () => {
map.current?.remove(); map.current?.remove();
}; };
}, [events, setSelectedEventId, setHoveredEventId, fitToBounds]); }, [events, setSelectedEventId, setHoveredEventId, fitToBounds]);
useEffect(() => { useEffect(() => {
const event = events.find((x) => x.id === selectedEventId); const event = events.find((x) => x.id === selectedEventId);
if (event) flyToEvent(event); if (event) flyToEvent(event);
else if (!selectedEventId) { else if (!selectedEventId) {
if (mapBounds) fitToBounds(mapBounds); if (mapBounds) fitToBounds(mapBounds);
} }
}, [events, selectedEventId, mapBounds, fitToBounds, clearAllPopups, flyToEvent]); }, [events, selectedEventId, mapBounds, fitToBounds, clearAllPopups, flyToEvent]);
useEffect(() => { useEffect(() => {
// Clear all popups first // Clear all popups first
clearAllPopups(); clearAllPopups();
// Handle both events if they exist and are different // Handle both events if they exist and are different
if (hoveredEventId && selectedEventId && hoveredEventId !== selectedEventId) { if (hoveredEventId && selectedEventId && hoveredEventId !== selectedEventId) {
showPopup(hoveredEventId); showPopup(hoveredEventId);
showPopup(selectedEventId); showPopup(selectedEventId);
} }
// Handle single event case (either hovered or selected) // Handle single event case (either hovered or selected)
else if (hoveredEventId || selectedEventId) { else if (hoveredEventId || selectedEventId) {
showPopup(hoveredEventId || selectedEventId); showPopup(hoveredEventId || selectedEventId);
} }
}, [hoveredEventId, selectedEventId, clearAllPopups, showPopup]); }, [hoveredEventId, selectedEventId, clearAllPopups, showPopup]);
return ( return (
<div className="w-full h-full"> <div className="w-full h-full">
<div id="map-container" className="w-full h-full" /> <div id="map-container" className="w-full h-full" />
</div> </div>
); );
} }
// CSS for location-style pulsing animation // CSS for location-style pulsing animation
@ -211,10 +220,10 @@ const pulseStyles = `
`; `;
export default function Map(props: MapComponentProps) { export default function Map(props: MapComponentProps) {
return ( return (
<> <>
<style>{pulseStyles}</style> <style>{pulseStyles}</style>
<MapComponent {...props} /> <MapComponent {...props} />
</> </>
); );
} }

View File

@ -1,13 +1,13 @@
"use client"; "use client";
import Image from 'next/image'; import Image from "next/image";
import Link from 'next/link'; import Link from "next/link";
import { usePathname } from 'next/navigation'; import { usePathname } from "next/navigation";
import { Dispatch, SetStateAction, useMemo, useState } from 'react'; import { Dispatch, SetStateAction, useMemo, useState } from "react";
import { FaRegUserCircle } from 'react-icons/fa'; import { FaRegUserCircle } from "react-icons/fa";
import { Currency } from '@appTypes/StoreModel'; import { Currency } from "@appTypes/StoreModel";
import AuthModal from '@components/AuthModal'; import AuthModal from "@components/AuthModal";
import { useStoreActions, useStoreState } from '@hooks/store'; import { useStoreActions, useStoreState } from "@hooks/store";
export default function Navbar({}: // currencySelector, export default function Navbar({}: // currencySelector,
{ {

View File

@ -1,11 +1,11 @@
interface Event { interface Event {
id: string; id: string;
title: string; title: string;
magnitude?: number; magnitude?: number;
longitude: number; longitude: number;
latitude: number; latitude: number;
text1: string; text1: string;
text2: string; text2: string;
} }
export default Event; export default Event;

View File

@ -1,3 +1,3 @@
import axios from 'axios'; import axios from "axios";
export const fetcher = (url: string) => axios.get(url).then((res) => res.data); export const fetcher = (url: string) => axios.get(url).then((res) => res.data);

View File

@ -1,20 +1,21 @@
"use client"; "use client";
import resolveConfig from "tailwindcss/resolveConfig"; import resolveConfig from "tailwindcss/resolveConfig";
import tailwindConfig from "../../tailwind.config"; import tailwindConfig from "../../tailwind.config";
function getMagnitudeColour(magnitude: number) { function getMagnitudeColour(magnitude: number) {
const fullConfig = resolveConfig(tailwindConfig); const fullConfig = resolveConfig(tailwindConfig);
const colors = fullConfig.theme.colors; const colors = fullConfig.theme.colors;
if (magnitude >= 7) { if (magnitude >= 7) {
return colors["red"][600]; return colors["red"][600];
} else if (magnitude >= 5) { } else if (magnitude >= 5) {
return colors["orange"][500]; return colors["orange"][500];
} else if (magnitude >= 3) { } else if (magnitude >= 3) {
return colors["amber"][500]; return colors["amber"][500];
} else { } else {
return colors["blue"][400]; return colors["blue"][400];
} }
} }
export default getMagnitudeColour; export default getMagnitudeColour;