Merge branches 'master' and 'master' of ssh://stash.dyson.global.corp:7999/~thowitz/tremor-tracker

This commit is contained in:
Emily Neighbour 2025-04-28 21:50:01 +01:00
commit 98d4018c85
18 changed files with 1176 additions and 876 deletions

102
package-lock.json generated
View File

@ -14,6 +14,7 @@
"bcryptjs": "^3.0.2",
"body-parser": "^2.2.0",
"csv-parser": "^3.2.0",
"easy-peasy": "^6.1.0",
"express": "^5.1.0",
"fs": "^0.0.1-security",
"jwt-decode": "^4.0.0",
@ -55,6 +56,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@babel/runtime": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
"integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz",
@ -1637,7 +1650,7 @@
"version": "19.0.10",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz",
"integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==",
"dev": true,
"devOptional": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.0.2"
@ -1647,7 +1660,7 @@
"version": "19.0.4",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.4.tgz",
"integrity": "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==",
"dev": true,
"devOptional": true,
"license": "MIT",
"peerDependencies": {
"@types/react": "^19.0.0"
@ -2803,7 +2816,7 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true,
"devOptional": true,
"license": "MIT"
},
"node_modules/csv-parser": {
@ -3048,6 +3061,42 @@
"dev": true,
"license": "MIT"
},
"node_modules/easy-peasy": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/easy-peasy/-/easy-peasy-6.1.0.tgz",
"integrity": "sha512-zW7mUATJRohNWWSrihmeFhS85jjsIHa1ptTDn6bSXa2DXh8Zeawfpmm1LtKa+Y6jNNz5aQlMjWZ8uuuVk5bdVQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.22.6",
"fast-deep-equal": "^3.1.3",
"immer": "^9.0.21",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"ts-toolbelt": "^9.6.0",
"use-sync-external-store": "^1.4.0"
},
"peerDependencies": {
"@types/react": "^18.0 || ^19.0",
"@types/react-dom": "^18.0 || ^19.0",
"react": "^18.0 || ^19.0",
"react-dom": "^18.0 || ^19.0",
"react-native": ">=0.59"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
},
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
}
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@ -3844,7 +3893,6 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true,
"license": "MIT"
},
"node_modules/fast-glob": {
@ -4561,6 +4609,16 @@
"node": ">= 4"
}
},
"node_modules/immer": {
"version": "9.0.21",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
"integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
}
},
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
@ -6605,6 +6663,21 @@
"node": ">=0.10.0"
}
},
"node_modules/redux": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"license": "MIT"
},
"node_modules/redux-thunk": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
"license": "MIT",
"peerDependencies": {
"redux": "^5.0.0"
}
},
"node_modules/reflect.getprototypeof": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@ -6628,6 +6701,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
"license": "MIT"
},
"node_modules/regexp.prototype.flags": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
@ -7770,6 +7849,12 @@
"dev": true,
"license": "Apache-2.0"
},
"node_modules/ts-toolbelt": {
"version": "9.6.0",
"resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz",
"integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==",
"license": "Apache-2.0"
},
"node_modules/tsconfig-paths": {
"version": "3.15.0",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
@ -7953,6 +8038,15 @@
"punycode": "^2.1.0"
}
},
"node_modules/use-sync-external-store": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
"integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/util": {
"version": "0.10.4",
"resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",

View File

@ -17,6 +17,7 @@
"bcryptjs": "^3.0.2",
"body-parser": "^2.2.0",
"csv-parser": "^3.2.0",
"easy-peasy": "^6.1.0",
"express": "^5.1.0",
"fs": "^0.0.1-security",
"jwt-decode": "^4.0.0",

View File

@ -1,6 +1,6 @@
"use client";
import React, { useState } from "react";
import Image from "next/image";
import React, { useState } from "react";
const ContactUs = () => {
const [formData, setFormData] = useState({
@ -21,8 +21,7 @@ const ContactUs = () => {
};
return (
<div
className="h-screen relative text-white py-10 border border-black">
<div className="h-screen relative text-white py-10 border border-black">
<Image height={5000} width={5000} alt="Logo" className="border border-neutral-300 absolute z-10" src="/tsunamiWaves.jpg" />
{/* Overlay for readability */}
@ -30,24 +29,19 @@ const ContactUs = () => {
{/* Container */}
<div className="max-w-4xl mx-auto p-5">
{/* Header */}
<h1 className="text-4xl font-bold text-center text-white mb-6">
Contact Us
</h1>
<p className="text-lg text-center text-gray-300 mb-6">
Have questions or concerns about earthquake preparedness? Contact us
using the form below or through the provided contact details.
<h1 className="text-4xl font-bold text-center text-white mb-6">Contact Us</h1>
<p className="text-lg text-center text-neutral-300 mb-6">
Have questions or concerns about earthquake preparedness? Contact us using the form below or through the provided
contact details.
</p>
{/* Content Section */}
<div className="flex flex-col md:flex-row gap-6">
{/* Contact Form Section */}
<div className="flex-1 bg-white bg-opacity-90 text-gray-800 rounded-lg shadow-lg p-6">
<div className="flex-1 bg-white bg-opacity-90 text-neutral-800 rounded-lg shadow-lg p-6">
<form onSubmit={handleSubmit}>
<div className="mb-4">
<label
htmlFor="name"
className="block text-gray-700 font-medium mb-2"
>
<label htmlFor="name" className="block text-neutral-700 font-medium mb-2">
Name
</label>
<input
@ -57,16 +51,13 @@ const ContactUs = () => {
value={formData.name}
onChange={handleChange}
placeholder="Your Name"
className="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
className="w-full p-3 border border-neutral-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
</div>
<div className="mb-4">
<label
htmlFor="email"
className="block text-gray-700 font-medium mb-2"
>
<label htmlFor="email" className="block text-neutral-700 font-medium mb-2">
Email
</label>
<input
@ -76,16 +67,13 @@ const ContactUs = () => {
value={formData.email}
onChange={handleChange}
placeholder="Your Email"
className="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
className="w-full p-3 border border-neutral-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
</div>
<div className="mb-4">
<label
htmlFor="message"
className="block text-gray-700 font-medium mb-2"
>
<label htmlFor="message" className="block text-neutral-700 font-medium mb-2">
Message
</label>
<textarea
@ -95,7 +83,7 @@ const ContactUs = () => {
onChange={handleChange}
rows="5"
placeholder="Your Message"
className="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
className="w-full p-3 border border-neutral-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
</div>
@ -110,53 +98,35 @@ const ContactUs = () => {
</div>
{/* Contact Details Section */}
<div className="flex-1 bg-white bg-opacity-90 text-gray-800 rounded-lg shadow-lg p-6">
<h2 className="text-xl font-bold text-gray-800 mb-4">
Get in Touch
</h2>
<div className="flex-1 bg-white bg-opacity-90 text-neutral-800 rounded-lg shadow-lg p-6">
<h2 className="text-xl font-bold text-neutral-800 mb-4">Get in Touch</h2>
<div className="mb-4">
<h3 className="text-gray-700 font-medium">Email</h3>
<p className="text-gray-600">getintouch@tremortracker.org.uk</p>
<h3 className="text-neutral-700 font-medium">Email</h3>
<p className="text-neutral-600">getintouch@tremortracker.org.uk</p>
</div>
<div className="mb-4">
<h3 className="text-gray-700 font-medium">Phone</h3>
<p className="text-gray-600">+44 7538 359022</p>
<h3 className="text-neutral-700 font-medium">Phone</h3>
<p className="text-neutral-600">+44 7538 359022</p>
</div>
<div className="mb-4">
<h3 className="text-gray-700 font-medium">Address</h3>
<p className="text-gray-600">
1 Swentown Row, Greenwich, London, SE3 0FQ
</p>
<h3 className="text-neutral-700 font-medium">Address</h3>
<p className="text-neutral-600">1 Swentown Row, Greenwich, London, SE3 0FQ</p>
</div>
<h2 className="text-xl font-bold text-gray-800 mb-4 mt-6">
Follow Us
</h2>
<h2 className="text-xl font-bold text-neutral-800 mb-4 mt-6">Follow Us</h2>
<div className="flex justify-around items-center">
<a
href="#"
className="w-20 h-20 text-blue-600 hover:text-blue-800 transition duration-200"
>
<a href="#" className="w-20 h-20 text-blue-600 hover:text-blue-800 transition duration-200">
<span className="sr-only">Instagram</span>
<Image height={200} width={200} alt="Logo" className="z-10" src="/insta.webp" />
</a>
<a
href="#"
className="w-20 h-20 text-blue-600 hover:text-blue-800 transition duration-200"
>
<a href="#" className="w-20 h-20 text-blue-600 hover:text-blue-800 transition duration-200">
<span className="sr-only">Facebook</span>
<Image height={200} width={200} alt="Logo" className="z-10" src="/facebook.webp" />
</a>
<a
href="#"
className="w-20 h-20 p-4 text-blue-600 hover:text-blue-800 transition duration-200"
>
<a href="#" className="w-20 h-20 p-4 text-blue-600 hover:text-blue-800 transition duration-200">
<span className="sr-only">X</span>
<Image height={200} width={200} alt="Logo" className="z-10 rounded-lg" src="/x_logo.jpg" />
</a>
<a
href="#"
className="w-20 h-20 flex items-center text-blue-600 hover:text-blue-800 transition duration-200"
>
<a href="#" className="w-20 h-20 flex items-center text-blue-600 hover:text-blue-800 transition duration-200">
<span className="sr-only">LinkedIn</span>
<Image height={200} width={200} alt="Logo" className="z-10" src="/linkedIn.png" />
</a>

View File

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

View File

@ -1,17 +1,29 @@
"use client";
import type { Metadata } from "next";
import Navbar from "@/components/Navbar";
import { Inter } from "next/font/google";
import "./globals.css";
import { action, createStore, StoreProvider } from "easy-peasy";
import { Inter } from "next/font/google";
import { StoreModel } from "@appTypes/StoreModel";
import Navbar from "@components/Navbar";
const inter = Inter({
subsets: ["latin"],
variable: "--font-inter",
});
export const metadata: Metadata = {
title: "Tremor Tracker",
description: "Generated by tim",
};
const store = createStore<StoreModel>({
currency: {
selectedCurrency: "GBP",
setSelectedCurrency: action((state, payload) => {
state.selectedCurrency = payload;
}),
currencies: ["GBP", "USD", "EUR"],
conversionRates: { GBP: 1, USD: 1.33, EUR: 1.17 },
tickers: { GBP: "£", USD: "$", EUR: "€" },
},
});
export default function RootLayout({
children,
@ -20,10 +32,12 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<StoreProvider store={store}>
<body className={`${inter.variable} h-[calc(100vh-3.5rem)] flex flex-col min-h-screen antialiased`}>
<Navbar></Navbar>
<div className="flex-1 overflow-y-auto">{children}</div>
</body>
</StoreProvider>
</html>
);
}

View File

@ -1,7 +1,7 @@
"use client";
import Sidebar from "@/components/Sidebar";
import Map from "@/components/map";
import Map from "@components/Map";
import { useState, useMemo } from "react";
export default function Observatories() {

View File

@ -72,6 +72,46 @@ const OurMission = () => {
</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">
<h1 className="text-3xl font-bold text-center text-neutral-800 mb-6">Our Mission</h1>
<p className="text-lg text-neutral-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-neutral-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-4">
<img src="/images/education-icon.png" alt="Education Icon" className="h-16 w-16 mb-4" />
<h3 className="text-xl font-bold text-neutral-700 mb-2">Education</h3>
<p className="text-sm text-neutral-500 text-center">
Providing accessible resources to educate people about earthquake preparedness.
</p>
</div>
<div className="flex flex-col items-center p-4">
<img src="/images/research-icon.png" alt="Research Icon" className="h-16 w-16 mb-4" />
<h3 className="text-xl font-bold text-neutral-700 mb-2">Research</h3>
<p className="text-sm text-neutral-500 text-center">
Supporting scientific studies to enhance understanding of seismic activity.
</p>
</div>
<div className="flex flex-col items-center p-4">
<img src="/images/technology-icon.png" alt="Technology Icon" className="h-16 w-16 mb-4" />
<h3 className="text-xl font-bold text-neutral-700 mb-2">Technology</h3>
<p className="text-sm text-neutral-500 text-center">
Leveraging innovation to deliver real-time alerts and safety tools.
</p>
</div>
</div>
</div>
</div>
);
};
export default OurMission;

View File

@ -1,115 +1,150 @@
"use client";
import Sidebar from "@/components/Sidebar";
import { useState } from "react";
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";
// Artifacts Data
const artifacts = [
{ id: 1, name: "Golden Scarab", description: "An ancient Egyptian artifact symbolizing rebirth.", location: "Cairo, Egypt", image: "/images/artifact1.jpg", price: 150 },
{ id: 2, name: "Aztec Sunstone", description: "A replica of the Aztec calendar (inscriptions intact).", location: "Peru", image: "/images/artifact2.jpg", price: 200 },
{ id: 3, name: "Medieval Chalice", description: "Used by royalty in medieval ceremonies.", location: "Cambridge, England", image: "/images/artifact3.jpg", price: 120 },
{ id: 4, name: "Roman Coin", description: "An authentic Roman coin from the 2nd century CE.", location: "Rome, Italy", image: "/images/artifact4.jpg", price: 80 },
{ id: 5, name: "Samurai Mask", description: "Replica of Japanese Samurai battle masks.", location: "Tokyo, Japan", image: "/images/artifact5.jpg", price: 300 },
{ id: 6, name: "Ancient Greek Vase", description: "Depicts Greek mythology, found in the Acropolis.", location: "Athens, Greece", image: "/images/artifact6.jpg", price: 250 },
{ id: 7, name: "Incan Pendant", description: "Represents the Sun God Inti.", location: "India", image: "/images/artifact7.jpg", price: 175 },
{ id: 8, name: "Persian Carpet Fragment", description: "Ancient Persian artistry.", location: "Petra, Jordan", image: "/images/artifact8.jpg", price: 400 },
{ id: 9, name: "Stone Buddha", description: "Authentic stone Buddha carving.", location: "India", image: "/images/artifact9.jpg", price: 220 },
{ id: 10, name: "Victorian Brooch", description: "A beautiful Victorian-era brooch with a ruby centre.", location: "Oxford, England", image: "/images/artifact10.jpg", price: 150 },
{ id: 11, name: "Ancient Scroll", description: "A mysterious scroll from ancient times.", location: "Madrid, Spain", image: "/images/artifact11.jpg", price: 500 },
{ id: 12, name: "Ming Dynasty Porcelain", description: "Porcelain from China's Ming Dynasty.", location: "Beijing, China", image: "/images/artifact12.jpg", price: 300 },
{ id: 13, name: "African Tribal Mask", description: "A unique tribal mask from Africa.", location: "Nigeria", image: "/images/artifact13.jpg", price: 250 },
{ id: 14, name: "Crystal Skull", description: "A mystical pre-Columbian artifact.", location: "Colombia", image: "/images/artifact14.jpg", price: 1000 },
{ id: 15, name: "Medieval Armor Fragment", description: "A fragment of medieval armor.", location: "Normandy, France", image: "/images/artifact15.jpg", price: 400 },
const artifacts: Artifact[] = [
{
id: 1,
name: "Golden Scarab",
description: "An ancient Egyptian artifact symbolizing rebirth.",
location: "Cairo, Egypt",
image: "/images/artifact1.jpg",
price: 150,
},
{
id: 2,
name: "Aztec Sunstone",
description: "A replica of the Aztec calendar (inscriptions intact).",
location: "Peru",
image: "/images/artifact2.jpg",
price: 200,
},
{
id: 3,
name: "Medieval Chalice",
description: "Used by royalty in medieval ceremonies.",
location: "Cambridge, England",
image: "/images/artifact3.jpg",
price: 120,
},
{
id: 4,
name: "Roman Coin",
description: "An authentic Roman coin from the 2nd century CE.",
location: "Rome, Italy",
image: "/images/artifact4.jpg",
price: 80,
},
{
id: 5,
name: "Samurai Mask",
description: "Replica of Japanese Samurai battle masks.",
location: "Tokyo, Japan",
image: "/images/artifact5.jpg",
price: 300,
},
{
id: 6,
name: "Ancient Greek Vase",
description: "Depicts Greek mythology, found in the Acropolis.",
location: "Athens, Greece",
image: "/images/artifact6.jpg",
price: 250,
},
{
id: 7,
name: "Incan Pendant",
description: "Represents the Sun God Inti.",
location: "India",
image: "/images/artifact7.jpg",
price: 175,
},
{
id: 8,
name: "Persian Carpet Fragment",
description: "Ancient Persian artistry.",
location: "Petra, Jordan",
image: "/images/artifact8.jpg",
price: 400,
},
{
id: 9,
name: "Stone Buddha",
description: "Authentic stone Buddha carving.",
location: "India",
image: "/images/artifact9.jpg",
price: 220,
},
{
id: 10,
name: "Victorian Brooch",
description: "A beautiful Victorian-era brooch with a ruby centre.",
location: "Oxford, England",
image: "/images/artifact10.jpg",
price: 150,
},
{
id: 11,
name: "Ancient Scroll",
description: "A mysterious scroll from ancient times.",
location: "Madrid, Spain",
image: "/images/artifact11.jpg",
price: 500,
},
{
id: 12,
name: "Ming Dynasty Porcelain",
description: "Porcelain from China's Ming Dynasty.",
location: "Beijing, China",
image: "/images/artifact12.jpg",
price: 300,
},
{
id: 13,
name: "African Tribal Mask",
description: "A unique tribal mask from Africa.",
location: "Nigeria",
image: "/images/artifact13.jpg",
price: 250,
},
{
id: 14,
name: "Crystal Skull",
description: "A mystical pre-Columbian artifact.",
location: "Colombia",
image: "/images/artifact14.jpg",
price: 1000,
},
{
id: 15,
name: "Medieval Armor Fragment",
description: "A fragment of medieval armor.",
location: "Normandy, France",
image: "/images/artifact15.jpg",
price: 400,
},
];
// Modal Component
const Modal = ({ artifact, onClose }) => {
if (!artifact) return null;
const handleOverlayClick = (e) => {
if (e.target === e.currentTarget) {
onClose();
}
};
return (
<div
className="fixed inset-0 bg-gray-900 bg-opacity-50 flex justify-center items-center z-50"
onClick={handleOverlayClick}>
<div className="bg-white rounded-md shadow-lg max-w-lg w-full p-6">
<h3 className="text-xl font-bold mb-4">{artifact.name}</h3>
<img src={artifact.image} alt={artifact.name} className="w-full h-64 object-cover rounded-md mb-4" />
<p className="text-gray-600 mb-2">{artifact.description}</p>
<p className="text-gray-500 font-bold mb-4">Location: {artifact.location}</p>
<p className="text-red-600 font-bold">Price: ${artifact.price}</p>
<div className="flex justify-end gap-4 mt-6">
<button
onClick={() => alert("Reserved Successfully!")}
className="px-4 py-2 bg-yellow-500 text-white rounded-md hover:bg-yellow-400">
Reserve
</button>
<button
onClick={() => alert("Purchased Successfully!")}
className="px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-500">
Buy
</button>
<button
onClick={onClose}
className="px-4 py-2 bg-gray-300 rounded-md hover:bg-gray-400">
Close
</button>
</div>
</div>
</div>
);
};
// ArtifactCard Component
const ArtifactCard = ({ artifact, onSelect }) => {
const [selectedCurrency, setSelectedCurrency] = useState("USD");
const conversionRates = { USD: 1, EUR: 0.94, GBP: 0.81 };
const convertPrice = (price, currency) => (price * conversionRates[currency]).toFixed(2);
const handleCurrencyChange = (e) => {
setSelectedCurrency(e.target.value); // Update selected currency
e.stopPropagation(); // Prevent the modal from opening on dropdown click
};
return (
<div
className="flex flex-col bg-white shadow-md rounded-md overflow-hidden cursor-pointer hover:scale-105 transition-transform"
onClick={() => onSelect(artifact)} // Opens modal
>
<img src={artifact.image} alt={artifact.name} className="w-full h-56 object-cover" />
<div className="p-4">
<h3 className="text-lg font-bold">{artifact.name}</h3>
<p className="text-gray-700 mb-2">{artifact.description}</p>
<p className="text-gray-500 mb-2">{artifact.location}</p>
<p className="text-red-600 font-bold text-md mt-4">
{selectedCurrency}: {convertPrice(artifact.price, selectedCurrency)}
</p>
<select
value={selectedCurrency}
onChange={handleCurrencyChange} // Handles currency change
className="border border-gray-300 rounded-lg px-3 py-1 text-sm items-left"
onClick={(e) => e.stopPropagation()} // Prevents triggering the modal
>
<option value="USD">USD ($)</option>
<option value="EUR">EUR ()</option>
<option value="GBP">GBP (£)</option>
</select>
</div>
</div>
);
};
// Shop Component
export default function Shop() {
const [currentPage, setCurrentPage] = useState(1);
const [selectedArtifact, setSelectedArtifact] = useState(null); // Track selected artifact for modal
const [selectedArtifact, setSelectedArtifact] = useState<Artifact | null>(null); // Track selected artifact for modal
const artifactsPerPage = 9; // Number of artifacts per page
const indexOfLastArtifact = currentPage * artifactsPerPage;
const indexOfFirstArtifact = indexOfLastArtifact - artifactsPerPage;
const currentArtifacts = artifacts.slice(indexOfFirstArtifact, indexOfLastArtifact);
const selectedCurrency = useStoreState((state) => state.currency.selectedCurrency);
const conversionRates = useStoreState((state) => state.currency.conversionRates);
const currencyTickers = useStoreState((state) => state.currency.tickers);
const convertPrice = useCallback((price: number, currency: Currency) => (price * conversionRates[currency]).toFixed(2), []);
const handleNextPage = () => {
if (indexOfLastArtifact < artifacts.length) {
setCurrentPage((prev) => prev + 1);
@ -122,32 +157,94 @@ export default function Shop() {
}
};
const handleSelectArtifact = (artifact) => {
setSelectedArtifact(artifact); // Open modal with selected artifact
};
function Modal({ artifact }: { artifact: Artifact }) {
if (!artifact) return null;
const handleCloseModal = () => {
setSelectedArtifact(null); // Close modal
const handleOverlayClick = (e: { target: any; currentTarget: any }) => {
if (e.target === e.currentTarget) {
setSelectedArtifact(null);
}
};
return (
<div className="flex flex-col min-h-screen bg-gray-100">
<div
className="fixed inset-0 bg-neutral-900 bg-opacity-50 flex justify-center items-center z-50"
onClick={handleOverlayClick}
>
<div className="bg-white rounded-md shadow-lg max-w-lg w-full p-6">
<h3 className="text-xl font-bold mb-4">{artifact.name}</h3>
<img src={artifact.image} alt={artifact.name} className="w-full h-64 object-cover rounded-md mb-4" />
<p className="text-neutral-600 mb-2">{artifact.description}</p>
<p className="text-neutral-500 font-bold mb-4">Location: {artifact.location}</p>
<p className="text-red-600 font-bold">Price: ${artifact.price}</p>
<div className="flex justify-end gap-4 mt-6">
<button
onClick={() => alert("Reserved Successfully!")}
className="px-4 py-2 bg-yellow-500 text-white rounded-md hover:bg-yellow-400"
>
Reserve
</button>
<button
onClick={() => alert("Purchased Successfully!")}
className="px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-500"
>
Buy
</button>
<button
onClick={() => setSelectedArtifact(null)}
className="px-4 py-2 bg-neutral-300 rounded-md hover:bg-neutral-400"
>
Close
</button>
</div>
</div>
</div>
);
}
// ArtifactCard Component
function ArtifactCard({ artifact }: { artifact: Artifact }) {
return (
<div
className="flex flex-col bg-white shadow-md rounded-md overflow-hidden cursor-pointer hover:scale-105 transition-transform"
onClick={() => setSelectedArtifact(artifact)} // Opens modal
>
<img src={artifact.image} alt={artifact.name} className="w-full h-56 object-cover" />
<div className="p-4">
<h3 className="text-lg font-bold">{artifact.name}</h3>
<p className="text-neutral-700 mb-2">{artifact.description}</p>
<p className="text-neutral-500 mb-2">{artifact.location}</p>
<p className="text-blue-600 font-bold text-md mt-4">
{currencyTickers[selectedCurrency]}
{convertPrice(artifact.price, selectedCurrency)}
</p>
</div>
</div>
);
}
return (
<div className="flex flex-col min-h-screen bg-neutral-100">
{/* Main Content */}
<div className="flex flex-1">
<div className="flex flex-1 overflow-y-auto mr-[19rem]">
{/* Artifact Grid */}
<div className="flex-grow grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 p-6 pr-72">
<div className="flex-grow grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 p-6">
{currentArtifacts.map((artifact) => (
<ArtifactCard key={artifact.id} artifact={artifact} onSelect={handleSelectArtifact} />
<ArtifactCard key={artifact.id} artifact={artifact} />
))}
</div>
</div>
{/* Sidebar */}
<aside className="w-72 bg-white shadow-lg p-4 h-screen fixed top-10 right-0 overflow-auto">
<div className="bg-white shadow-lg h-screen fixed right-0 overflow-y-auto">
<Sidebar
logTitle="Artifact Collection"
logSubtitle="Record new artifacts - name, description, image, location and price"
recentsTitle="Recent Updates"
events={[/* example events if needed */]}
events={
[
/* example events if needed */
]
}
selectedEventId=""
setSelectedEventId={() => {}}
hoveredEventId=""
@ -155,17 +252,17 @@ export default function Shop() {
button1Name="Add New Artifact"
button2Name="Search Artifacts"
/>
</aside>
</div>
{/* Pagination Footer */}
<footer className="bg-white border-t border-gray-300 py-2 text-center w-full flex justify-center items-centre">
<footer className="bg-white border-t border-neutral-300 py-2 text-center w-full flex justify-center items-center mr-80">
<button
onClick={handlePreviousPage}
disabled={currentPage === 1}
className={`mx-2 px-4 py-1 bg-blue-700 text-white rounded-md font-bold shadow-md ${
currentPage === 1 ? "opacity-50 cursor-not-allowed" : "hover:bg-blue-600"
}`} >
}`}
>
&larr; Previous
</button>
<button
@ -173,13 +270,13 @@ export default function Shop() {
disabled={indexOfLastArtifact >= artifacts.length}
className={`mx-2 px-4 py-1 bg-blue-500 text-white rounded-md font-bold shadow-md ${
indexOfLastArtifact >= artifacts.length ? "opacity-50 cursor-not-allowed" : "hover:bg-blue-600"
}`} >
}`}
>
Next &rarr;
</button>
</footer>
{/* Modal */}
<Modal artifact={selectedArtifact} onClose={handleCloseModal} />
{selectedArtifact && <Modal artifact={selectedArtifact} />}
</div>
);
}

View File

@ -37,9 +37,7 @@ export default function Warehouse() {
return (
<div className="w-full h-full">
<p>
warehouse image pasted in here
</p>
<p>warehouse image pasted in here</p>
<Sidebar
logTitle="Artifact Retrieval and Tracking"
logSubtitle="Record new artifacts collected - name, description, time/date found, location found, scientist and collection stage"

View File

@ -1,7 +1,8 @@
// tells React if you're using its "server-side rendering" features. this component runs only on the client-side (browser)
"use client";
// importing React hooks or utilities that enhance functionality inside the component
import { useState, FormEvent, useRef, MouseEvent, useEffect } from "react";
import { FormEvent, MouseEvent, useEffect, useRef, useState } from "react";
/*
useState: Used to manage state (data that changes over time).
FormEvent: TypeScript definition for form-related events like submission.
@ -16,7 +17,8 @@ interface AuthModalProps {
onClose: () => void; //A function that will be executed to close the modal
}
// creates a React functional component
export default function AuthModal({ isOpen, onClose }: AuthModalProps) { //AuthModalProps ensures TypeScript validates the props to match the type definition above
export default function AuthModal({ isOpen, onClose }: AuthModalProps) {
//AuthModalProps ensures TypeScript validates the props to match the type definition above
const [isLogin, setIsLogin] = useState<boolean>(true);
const modalRef = useRef<HTMLDivElement>(null);
const [isFailed, setIsFailed] = useState<boolean>(false);
@ -71,7 +73,7 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) { //AuthM
*/
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault(); // stops page from refreshing
setIsFailed(false)
setIsFailed(false);
const formData = new FormData(e.currentTarget); // new variable of class FormData is created. This is part of the standard Web API included in modern web browsers
const email = formData.get("email") as string; // gets email from form response
const password = formData.get("password") as string; // gets password from form response
@ -80,26 +82,28 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) { //AuthM
let endpoint = isLogin ? "/api/login" : "/api/signup"; // sets endpoint for backend code (either sign up or login)
const body = isLogin ? { email, password } : { name: name!, email, password }; // creates a json body for the backend
try {
console.log("Sending data to API");
const res = await fetch(endpoint, { // sends a request to the server at the end point
const res = await fetch(endpoint, {
// sends a request to the server at the end point
method: "POST", // Post is used since the form submission modifies the server side state
headers: { "Content-Type": "application/json" }, //indicates it expects a json object returned
body: JSON.stringify(body), // converts the body to a JSON string to be sent
});
if (res.ok) { //res.ok checks if the response is between 200-299
if (res.ok) {
//res.ok checks if the response is between 200-299
console.log("Success!");
onClose(); // closes UI
} else if (res.status >= 400 && res.status < 500) {
const responseBody = await res.json()
console.log("4xx error:", responseBody.message)
setFailMessage(responseBody.message)
setIsFailed(true)
const responseBody = await res.json();
console.log("4xx error:", responseBody.message);
setFailMessage(responseBody.message);
setIsFailed(true);
} else {
console.error("Error:", await res.text()); // logs error with error message sent to console
}
} catch (error) {// catches any errors (e.g. Not connected to network)
} catch (error) {
// catches any errors (e.g. Not connected to network)
console.error("Request failed:", error instanceof Error ? error.message : String(error));
}
};
@ -107,7 +111,14 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) { //AuthM
return (
<div className="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center z-50" onClick={handleOverlayClick}>
<div ref={modalRef} className="bg-white rounded-lg shadow-lg p-6 w-full max-w-md relative">
<button onClick={() => {setIsFailed(false);onClose();}} className="absolute text-xl top-0 right-2 text-gray-500 hover:text-gray-700" aria-label="Close modal">
<button
onClick={() => {
setIsFailed(false);
onClose();
}}
className="absolute text-xl top-0 right-2 text-neutral-500 hover:text-neutral-700"
aria-label="Close modal"
>
×
</button>
<h2 className="text-2xl font-bold text-center mb-4">{isLogin ? "Login" : "Sign Up"}</h2>
@ -116,7 +127,7 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) { //AuthM
<form onSubmit={handleSubmit} className="space-y-4">
{!isLogin && (
<div>
<label className="block text-sm font-medium text-gray-700">Full Name</label>
<label className="block text-sm font-medium text-neutral-700">Full Name</label>
<input
type="text"
name="name"
@ -126,7 +137,7 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) { //AuthM
</div>
)}
<div>
<label className="block text-sm font-medium text-gray-700">Email</label>
<label className="block text-sm font-medium text-neutral-700">Email</label>
<input
type="email"
name="email"
@ -135,7 +146,7 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) { //AuthM
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">Password</label>
<label className="block text-sm font-medium text-neutral-700">Password</label>
<input
type="password"
name="password"
@ -148,8 +159,7 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) { //AuthM
<div>
<label className="block text-sm font-medium text-red-700">{failMessage}</label>
</div>
)
}
)}
</div>
<button type="submit" className="w-full bg-blue-600 text-white p-2 rounded-md hover:bg-blue-700 transition">
{isLogin ? "Login" : "Sign Up"}

View File

@ -1,7 +1,7 @@
import React from "react";
import { Dispatch, SetStateAction } from "react";
import Link from "next/link";
import React, { Dispatch, SetStateAction } from "react";
import { TbHexagon } from "react-icons/tb";
import Event from "@appTypes/Event";
import getMagnitudeColor from "@utils/getMagnitudeColour";
@ -51,10 +51,12 @@ export default function Sidebar({
button2Name,
}: SidebarProps) {
return (
<div className="flex flex-col py-6 h-full w-full max-w-xs bg-gradient-to-b from-neutral-100 to-neutral-50 shadow-lg">
<div className={`flex flex-col h-full w-80 relative bg-gradient-to-b from-neutral-100 to-neutral-50 shadow-lg`}>
<div className="py-6">
<div className="px-6 pb-8 border-b border-neutral-200">
<h2 className="text-2xl font-bold text-neutral-800 mb-2">{logTitle}</h2>
<h2 className={`text-2xl font-bold text-neutral-800 mb-2`}>{logTitle}</h2>
<p className="text-sm text-neutral-600 leading-relaxed">{logSubtitle}</p>
<Link href="/">
<button className="mt-4 w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-lg transition-colors duration-200 font-medium">
{button1Name}
@ -88,13 +90,12 @@ export default function Sidebar({
{/* <p className="text-xs text-neutral-600">{event.text1}</p> */}
<p className="text-xs text-neutral-500 mt-1">{event.text2}</p>
</div>
{
(event.magnitude) ? <MagnitudeNumber magnitude={event.magnitude} /> : <></>
}
{event.magnitude ? <MagnitudeNumber magnitude={event.magnitude} /> : <></>}
</button>
))}
</div>
</div>
</div>
</div>
);
}

View File

@ -1,23 +1,33 @@
"use client";
import { useState } from "react";
import AuthModal from "@components/AuthModal";
import { usePathname } from "next/navigation";
import Link from "next/link";
import Image from "next/image";
import { useMemo } from "react";
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";
function NavbarButton({ name, href, dropdownItems }: { name: string; href: string; dropdownItems?: string[] }) {
const pathname = usePathname();
const isActive = dropdownItems ? dropdownItems.some((item) => pathname === `/${item.toLowerCase().replace(" ", "-")}`) : pathname === href;
const isActive = dropdownItems
? dropdownItems.some((item) => pathname === `/${item.toLowerCase().replace(" ", "-")}`)
: pathname === href;
return (
<button className="flex items-center justify-center px-2 py-4 relative group">
{dropdownItems ? (
<span className={`px-4 py-1.5 rounded-md transition-colors ${isActive ? "bg-neutral-200" : "group-hover:bg-neutral-200"}`}>{name}</span>
<span
className={`px-4 py-1.5 rounded-md transition-colors ${isActive ? "bg-neutral-200" : "group-hover:bg-neutral-200"}`}
>
{name}
</span>
) : (
<Link href={href} className={`px-4 py-1.5 rounded-md transition-colors ${isActive ? "bg-neutral-200" : "hover:bg-neutral-200"}`}>
<Link
href={href}
className={`px-4 py-1.5 rounded-md transition-colors ${isActive ? "bg-neutral-200" : "hover:bg-neutral-200"}`}
>
{name}
</Link>
)}
@ -29,7 +39,10 @@ function NavbarButton({ name, href, dropdownItems }: { name: string; href: strin
const isDropdownActive = pathname === itemHref;
return (
<li key={item}>
<Link href={itemHref} className={`block px-4 py-2 hover:bg-neutral-100 ${isDropdownActive ? "bg-neutral-100" : ""}`}>
<Link
href={itemHref}
className={`block px-4 py-2 hover:bg-neutral-100 ${isDropdownActive ? "bg-neutral-100" : ""}`}
>
{item}
</Link>
</li>
@ -42,7 +55,13 @@ function NavbarButton({ name, href, dropdownItems }: { name: string; href: strin
);
}
export default function Navbar() {
export default function Navbar({}: // currencySelector,
{
// currencySelector?: { selectedCurrency: string; setSelectedCurrency: Dispatch<SetStateAction<"GBP" | "USD" | "EUR">> };
}) {
const pathname = usePathname();
const selectedCurrency = useStoreState((state) => state.currency.selectedCurrency);
const setSelectedCurrency = useStoreActions((actions) => actions.currency.setSelectedCurrency);
const navOptions = useMemo(() => ["Earthquakes", "Observatories", "Warehouse", "Shop"], []);
// const navOptions = useMemo(() => ["Earthquakes"], []);
const aboutDropdown = ["Contact Us", "Our Mission", "The Team"];
@ -52,6 +71,9 @@ export default function Navbar() {
const [isModalOpen, setIsModalOpen] = useState(false);
const currencies = useStoreState((state) => state.currency.currencies);
const currencyTickers = useStoreState((state) => state.currency.tickers);
return (
<div className="flex sticky top-0 w-full h-14 z-40 font-medium bg-white border border-b-neutral-200">
<div className="my-1 flex aspect-square ml-3 mr-3">
@ -66,6 +88,26 @@ export default function Navbar() {
<NavbarButton name="About Us" href="/about" dropdownItems={aboutDropdown} />
</div>
<div className="flex-grow" />
{pathname.includes("shop") && (
<button className="flex items-center justify-center mr-3 py-4 relative group">
<span className={`px-4 py-1.5 rounded-md transition-colors`}>{selectedCurrency}</span>
<div className="absolute hidden group-hover:block top-full left-1/2 -translate-x-1/2 w-24 bg-white border border-neutral-300 rounded-lg overflow-hidden shadow-lg z-40">
<ul>
{currencies.map((item) => {
let ticker = currencyTickers[item];
return (
<li key={item} onClick={() => setSelectedCurrency(item)}>
<div className={`block px-2 py-2 hover:bg-neutral-100 text-md`}>{`${item} (${ticker})`}</div>
</li>
);
})}
</ul>
</div>
</button>
)}
<button className="my-auto mr-4" onClick={() => setIsModalOpen(true)}>
<FaRegUserCircle size={22} />
</button>

View File

@ -1,18 +1,16 @@
import React from 'react';
import Link from "next/link";
import React from "react";
const Sidebar = () => {
return (
<div className="flex flex-col h-screen w-64 bg-gray-400 text-white border-l border-gray-700">
<div className="flex flex-col p-4 border-b border-gray-700">
<div className="flex flex-col h-screen w-64 bg-neutral-400 text-white border-l border-neutral-700">
<div className="flex flex-col p-4 border-b border-neutral-700">
<h2 className="text-xl font-semibold mb-2">Log an Earthquake</h2>
<p className="text-sm text-gray-700">
<p className="text-sm text-neutral-700">
Record new earthquakes - time/date, location, magnitude, observatory and scientists
</p>
<button className="mt-4 bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded">
<Link href="/">
Log Event
</Link>
<Link href="/">Log Event</Link>
</button>
</div>
@ -20,20 +18,20 @@ const Sidebar = () => {
<div className="flex-1 p-4">
<h2 className="text-xl font-semibold mb-2">Recent Events</h2>
<ul className="space-y-2">
<li className="bg-gray-700 p-3 rounded hover:bg-gray-600">
<li className="bg-neutral-700 p-3 rounded hover:bg-neutral-600">
<p className="text-sm">Earthquake in California</p>
<p className="text-xs text-gray-300">Magnitude 5.3</p>
<p className="text-xs text-gray-400">2 hours ago</p>
<p className="text-xs text-neutral-300">Magnitude 5.3</p>
<p className="text-xs text-neutral-400">2 hours ago</p>
</li>
<li className="bg-gray-700 p-3 rounded hover:bg-gray-600">
<li className="bg-neutral-700 p-3 rounded hover:bg-neutral-600">
<p className="text-sm">Tremor in Japan</p>
<p className="text-xs text-gray-300">Magnitude 4.7</p>
<p className="text-xs text-gray-400">5 hours ago</p>
<p className="text-xs text-neutral-300">Magnitude 4.7</p>
<p className="text-xs text-neutral-400">5 hours ago</p>
</li>
<li className="bg-gray-700 p-3 rounded hover:bg-gray-600">
<li className="bg-neutral-700 p-3 rounded hover:bg-neutral-600">
<p className="text-sm">Tremor in Spain</p>
<p className="text-xs text-gray-300">Magnitude 2.1</p>
<p className="text-xs text-gray-400">10 hours ago</p>
<p className="text-xs text-neutral-300">Magnitude 2.1</p>
<p className="text-xs text-neutral-400">10 hours ago</p>
</li>
</ul>
</div>

View File

@ -1,18 +1,14 @@
import React from 'react';
import Link from "next/link";
import React from "react";
const Sidebar = () => {
return (
<div className="flex flex-col h-screen w-64 bg-gray-400 text-white border-l border-gray-700">
<div className="flex flex-col p-4 border-b border-gray-700">
<div className="flex flex-col h-screen w-64 bg-neutral-400 text-white border-l border-neutral-700">
<div className="flex flex-col p-4 border-b border-neutral-700">
<h2 className="text-xl font-semibold mb-2">Observatories</h2>
<p className="text-sm text-gray-700">
Observatory events - location, scientists, recent earthquakes
</p>
<p className="text-sm text-neutral-700">Observatory events - location, scientists, recent earthquakes</p>
<button className="mt-4 bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded">
<Link href="/">
Observatory News
</Link>
<Link href="/">Observatory News</Link>
</button>
</div>
@ -20,20 +16,20 @@ const Sidebar = () => {
<div className="flex-1 p-4">
<h2 className="text-xl font-semibold mb-2">Recent Observatory Events</h2>
<ul className="space-y-2">
<li className="bg-gray-700 p-3 rounded hover:bg-gray-600">
<li className="bg-neutral-700 p-3 rounded hover:bg-neutral-600">
<p className="text-sm">Earthquake in California</p>
<p className="text-xs text-gray-300">Magnitude 5.3</p>
<p className="text-xs text-gray-400">Cali Observatory</p>
<p className="text-xs text-neutral-300">Magnitude 5.3</p>
<p className="text-xs text-neutral-400">Cali Observatory</p>
</li>
<li className="bg-gray-700 p-3 rounded hover:bg-gray-600">
<li className="bg-neutral-700 p-3 rounded hover:bg-neutral-600">
<p className="text-sm">Tremor in Japan</p>
<p className="text-xs text-gray-300">Magnitude 4.7</p>
<p className="text-xs text-gray-400">Kyoto Observatory</p>
<p className="text-xs text-neutral-300">Magnitude 4.7</p>
<p className="text-xs text-neutral-400">Kyoto Observatory</p>
</li>
<li className="bg-gray-700 p-3 rounded hover:bg-gray-600">
<li className="bg-neutral-700 p-3 rounded hover:bg-neutral-600">
<p className="text-sm">Tremor in Spain</p>
<p className="text-xs text-gray-300">Magnitude 2.1</p>
<p className="text-xs text-gray-400">Madrid Observatory</p>
<p className="text-xs text-neutral-300">Magnitude 2.1</p>
<p className="text-xs text-neutral-400">Madrid Observatory</p>
</li>
</ul>
</div>

10
src/hooks/store.ts Normal file
View File

@ -0,0 +1,10 @@
"use client";
import { createTypedHooks } from "easy-peasy";
import { StoreModel } from "@appTypes/StoreModel";
export const typedHooks = createTypedHooks<StoreModel>();
export const useStoreActions = typedHooks.useStoreActions;
export const useStoreDispatch = typedHooks.useStoreDispatch;
export const useStoreState = typedHooks.useStoreState;

11
src/types/Artifact.ts Normal file
View File

@ -0,0 +1,11 @@
interface Artifact {
// todo change to string
id: number;
name: string;
description: string;
location: string;
image: string;
price: number;
}
export default Artifact;

17
src/types/StoreModel.ts Normal file
View File

@ -0,0 +1,17 @@
import { Action } from "easy-peasy";
type Currency = "GBP" | "USD" | "EUR";
interface CurrencyModel {
selectedCurrency: Currency;
setSelectedCurrency: Action<CurrencyModel, Currency>;
currencies: Currency[];
conversionRates: Record<Currency, number>;
tickers: Record<Currency, string>;
}
interface StoreModel {
currency: CurrencyModel;
}
export type { StoreModel, Currency };

View File

@ -20,11 +20,11 @@
}
],
"paths": {
"@/*": ["./src/*"],
"@components/*": ["./src/components/*"],
"@hooks/*": ["./src/hooks/*"],
"@utils/*": ["./src/utils/*"],
"@appTypes/*": ["./src/types/*"]
"@appTypes/*": ["./src/types/*"],
"@/*": ["./src/*"],
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],