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 compat = new FlatCompat({
baseDirectory: __dirname,
baseDirectory: __dirname,
});
const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
];
export default eslintConfig;
export default tseslint.config([...compat.extends("next/core-web-vitals", "next/typescript")], {
rules: {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error",
},
});

View File

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

2
package-lock.json generated
View File

@ -8524,4 +8524,4 @@
}
}
}
}
}

View File

@ -47,4 +47,4 @@
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}
}

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;
let prisma: PrismaClient;

View File

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

View File

@ -1,9 +1,9 @@
import bcrypt from 'bcrypt';
import { NextResponse } from 'next/server';
import bcrypt from "bcrypt";
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;
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;
let prisma: PrismaClient;

View File

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

View File

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

View File

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

View File

@ -3,8 +3,8 @@
@tailwind utilities;
:root {
--background: #ffffff;
--foreground: #171717;
--background: #ffffff;
--foreground: #171717;
}
/* @media (prefers-color-scheme: dark) {
@ -15,29 +15,29 @@
} */
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;
@apply text-neutral-500 !important;
}

View File

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

View File

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

View File

@ -1,128 +1,126 @@
import Image from "next/image";
export default function Home() {
return (
<main className="min-h-screen text-black">
<div className="w-full relative overflow-hidden">
<div className="">
<Image height={2000} width={2000} alt="Background Image" src="/lava_flows.jpg"></Image>
</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%]">
<Image className="mx-auto" height={300} width={1000} alt="Title Image" src="/tremortrackertext.png"></Image>
</div>
</div>
return (
<main className="min-h-screen text-black">
<div className="w-full relative overflow-hidden">
<div className="">
<Image height={2000} width={2000} alt="Background Image" src="/lava_flows.jpg"></Image>
</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%]">
<Image className="mx-auto" height={300} width={1000} alt="Title Image" src="/tremortrackertext.png"></Image>
</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>
);
<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>
// 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="flex gap-4 items-center flex-col sm:flex-row">
// <a
// 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"
// href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
// target="_blank"
// rel="noopener noreferrer"
// >
// <Image
// className="dark:invert"
// src="/vercel.svg"
// alt="Vercel logomark"
// width={20}
// height={20}
// />
// Deploy now
// </a>
// <a
// 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"
// href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
// target="_blank"
// rel="noopener noreferrer"
// >
// Read our docs
// </a>
// </div>
// </main>
// <footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
// <a
// className="flex items-center gap-2 hover:underline hover:underline-offset-4"
// href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
// target="_blank"
// rel="noopener noreferrer"
// >
// <Image
// aria-hidden
// src="/file.svg"
// alt="File icon"
// width={16}
// height={16}
// />
// 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>
// );
// <div className="flex gap-4 items-center flex-col sm:flex-row">
// <a
// 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"
// href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
// target="_blank"
// rel="noopener noreferrer"
// >
// <Image
// className="dark:invert"
// src="/vercel.svg"
// alt="Vercel logomark"
// width={20}
// height={20}
// />
// Deploy now
// </a>
// <a
// 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"
// href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
// target="_blank"
// rel="noopener noreferrer"
// >
// Read our docs
// </a>
// </div>
// </main>
// <footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
// <a
// className="flex items-center gap-2 hover:underline hover:underline-offset-4"
// href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
// target="_blank"
// rel="noopener noreferrer"
// >
// <Image
// aria-hidden
// src="/file.svg"
// alt="File icon"
// width={16}
// height={16}
// />
// 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";
import Image from 'next/image';
import { Dispatch, SetStateAction, useCallback, useState } from 'react';
import Image from "next/image";
import { Dispatch, SetStateAction, useCallback, useState } from "react";
import Artifact from '@appTypes/Artifact';
import { Currency } from '@appTypes/StoreModel';
import Sidebar from '@components/Sidebar';
import { useStoreState } from '@hooks/store';
import Artifact from "@appTypes/Artifact";
import { Currency } from "@appTypes/StoreModel";
import Sidebar from "@components/Sidebar";
import { useStoreState } from "@hooks/store";
// Artifacts Data
const artifacts: Artifact[] = [

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,11 @@
interface Event {
id: string;
title: string;
magnitude?: number;
longitude: number;
latitude: number;
text1: string;
text2: string;
id: string;
title: string;
magnitude?: number;
longitude: number;
latitude: number;
text1: string;
text2: string;
}
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);

View File

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