Merge branches 'master' and 'master' of ssh://stash.dyson.global.corp:7999/~thowitz/tremor-tracker
This commit is contained in:
commit
98d4018c85
102
package-lock.json
generated
102
package-lock.json
generated
@ -14,6 +14,7 @@
|
|||||||
"bcryptjs": "^3.0.2",
|
"bcryptjs": "^3.0.2",
|
||||||
"body-parser": "^2.2.0",
|
"body-parser": "^2.2.0",
|
||||||
"csv-parser": "^3.2.0",
|
"csv-parser": "^3.2.0",
|
||||||
|
"easy-peasy": "^6.1.0",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"fs": "^0.0.1-security",
|
"fs": "^0.0.1-security",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
@ -55,6 +56,18 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"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": {
|
"node_modules/@emnapi/runtime": {
|
||||||
"version": "1.3.1",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz",
|
||||||
@ -1637,7 +1650,7 @@
|
|||||||
"version": "19.0.10",
|
"version": "19.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz",
|
||||||
"integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==",
|
"integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
@ -1647,7 +1660,7 @@
|
|||||||
"version": "19.0.4",
|
"version": "19.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.4.tgz",
|
||||||
"integrity": "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==",
|
"integrity": "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^19.0.0"
|
"@types/react": "^19.0.0"
|
||||||
@ -2803,7 +2816,7 @@
|
|||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/csv-parser": {
|
"node_modules/csv-parser": {
|
||||||
@ -3048,6 +3061,42 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/ee-first": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
@ -3844,7 +3893,6 @@
|
|||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/fast-glob": {
|
"node_modules/fast-glob": {
|
||||||
@ -4561,6 +4609,16 @@
|
|||||||
"node": ">= 4"
|
"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": {
|
"node_modules/import-fresh": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||||
@ -6605,6 +6663,21 @@
|
|||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/reflect.getprototypeof": {
|
||||||
"version": "1.0.10",
|
"version": "1.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
||||||
@ -6628,6 +6701,12 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"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": {
|
"node_modules/regexp.prototype.flags": {
|
||||||
"version": "1.5.4",
|
"version": "1.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
|
||||||
@ -7770,6 +7849,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0"
|
"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": {
|
"node_modules/tsconfig-paths": {
|
||||||
"version": "3.15.0",
|
"version": "3.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
|
||||||
@ -7953,6 +8038,15 @@
|
|||||||
"punycode": "^2.1.0"
|
"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": {
|
"node_modules/util": {
|
||||||
"version": "0.10.4",
|
"version": "0.10.4",
|
||||||
"resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
|
"resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
"bcryptjs": "^3.0.2",
|
"bcryptjs": "^3.0.2",
|
||||||
"body-parser": "^2.2.0",
|
"body-parser": "^2.2.0",
|
||||||
"csv-parser": "^3.2.0",
|
"csv-parser": "^3.2.0",
|
||||||
|
"easy-peasy": "^6.1.0",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"fs": "^0.0.1-security",
|
"fs": "^0.0.1-security",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
|
|||||||
@ -1,175 +1,145 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React, { useState } from "react";
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
|
||||||
const ContactUs = () => {
|
const ContactUs = () => {
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: "",
|
name: "",
|
||||||
email: "",
|
email: "",
|
||||||
message: "",
|
message: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
console.log("Form submitted with data:", formData);
|
console.log("Form submitted with data:", formData);
|
||||||
alert("Thank you for reaching out! We will get back to you soon.");
|
alert("Thank you for reaching out! We will get back to you soon.");
|
||||||
setFormData({ name: "", email: "", message: "" });
|
setFormData({ name: "", email: "", message: "" });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="h-screen relative text-white py-10 border border-black">
|
||||||
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" />
|
||||||
<Image height={5000} width={5000} alt="Logo" className="border border-neutral-300 absolute z-10" src="/tsunamiWaves.jpg"/>
|
|
||||||
|
|
||||||
{/* Overlay for readability */}
|
|
||||||
<div className="absolute overflow-hidden w-full h-full bg-black bg-opacity-40 flex flex-col items-center z-20">
|
|
||||||
{/* 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.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{/* Content Section */}
|
{/* Overlay for readability */}
|
||||||
<div className="flex flex-col md:flex-row gap-6">
|
<div className="absolute overflow-hidden w-full h-full bg-black bg-opacity-40 flex flex-col items-center z-20">
|
||||||
{/* Contact Form Section */}
|
{/* Container */}
|
||||||
<div className="flex-1 bg-white bg-opacity-90 text-gray-800 rounded-lg shadow-lg p-6">
|
<div className="max-w-4xl mx-auto p-5">
|
||||||
<form onSubmit={handleSubmit}>
|
{/* Header */}
|
||||||
<div className="mb-4">
|
<h1 className="text-4xl font-bold text-center text-white mb-6">Contact Us</h1>
|
||||||
<label
|
<p className="text-lg text-center text-neutral-300 mb-6">
|
||||||
htmlFor="name"
|
Have questions or concerns about earthquake preparedness? Contact us using the form below or through the provided
|
||||||
className="block text-gray-700 font-medium mb-2"
|
contact details.
|
||||||
>
|
</p>
|
||||||
Name
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="name"
|
|
||||||
id="name"
|
|
||||||
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"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mb-4">
|
{/* Content Section */}
|
||||||
<label
|
<div className="flex flex-col md:flex-row gap-6">
|
||||||
htmlFor="email"
|
{/* Contact Form Section */}
|
||||||
className="block text-gray-700 font-medium mb-2"
|
<div className="flex-1 bg-white bg-opacity-90 text-neutral-800 rounded-lg shadow-lg p-6">
|
||||||
>
|
<form onSubmit={handleSubmit}>
|
||||||
Email
|
<div className="mb-4">
|
||||||
</label>
|
<label htmlFor="name" className="block text-neutral-700 font-medium mb-2">
|
||||||
<input
|
Name
|
||||||
type="email"
|
</label>
|
||||||
name="email"
|
<input
|
||||||
id="email"
|
type="text"
|
||||||
value={formData.email}
|
name="name"
|
||||||
onChange={handleChange}
|
id="name"
|
||||||
placeholder="Your Email"
|
value={formData.name}
|
||||||
className="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
onChange={handleChange}
|
||||||
required
|
placeholder="Your Name"
|
||||||
/>
|
className="w-full p-3 border border-neutral-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
</div>
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label
|
<label htmlFor="email" className="block text-neutral-700 font-medium mb-2">
|
||||||
htmlFor="message"
|
Email
|
||||||
className="block text-gray-700 font-medium mb-2"
|
</label>
|
||||||
>
|
<input
|
||||||
Message
|
type="email"
|
||||||
</label>
|
name="email"
|
||||||
<textarea
|
id="email"
|
||||||
name="message"
|
value={formData.email}
|
||||||
id="message"
|
onChange={handleChange}
|
||||||
value={formData.message}
|
placeholder="Your Email"
|
||||||
onChange={handleChange}
|
className="w-full p-3 border border-neutral-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
rows="5"
|
required
|
||||||
placeholder="Your Message"
|
/>
|
||||||
className="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
</div>
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
<div className="mb-4">
|
||||||
type="submit"
|
<label htmlFor="message" className="block text-neutral-700 font-medium mb-2">
|
||||||
className="w-full bg-blue-600 text-white py-3 rounded-lg font-medium hover:bg-blue-700 transition duration-200"
|
Message
|
||||||
>
|
</label>
|
||||||
Send Message
|
<textarea
|
||||||
</button>
|
name="message"
|
||||||
</form>
|
id="message"
|
||||||
</div>
|
value={formData.message}
|
||||||
|
onChange={handleChange}
|
||||||
|
rows="5"
|
||||||
|
placeholder="Your Message"
|
||||||
|
className="w-full p-3 border border-neutral-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Contact Details Section */}
|
<button
|
||||||
<div className="flex-1 bg-white bg-opacity-90 text-gray-800 rounded-lg shadow-lg p-6">
|
type="submit"
|
||||||
<h2 className="text-xl font-bold text-gray-800 mb-4">
|
className="w-full bg-blue-600 text-white py-3 rounded-lg font-medium hover:bg-blue-700 transition duration-200"
|
||||||
Get in Touch
|
>
|
||||||
</h2>
|
Send Message
|
||||||
<div className="mb-4">
|
</button>
|
||||||
<h3 className="text-gray-700 font-medium">Email</h3>
|
</form>
|
||||||
<p className="text-gray-600">getintouch@tremortracker.org.uk</p>
|
</div>
|
||||||
</div>
|
|
||||||
<div className="mb-4">
|
{/* Contact Details Section */}
|
||||||
<h3 className="text-gray-700 font-medium">Phone</h3>
|
<div className="flex-1 bg-white bg-opacity-90 text-neutral-800 rounded-lg shadow-lg p-6">
|
||||||
<p className="text-gray-600">+44 7538 359022</p>
|
<h2 className="text-xl font-bold text-neutral-800 mb-4">Get in Touch</h2>
|
||||||
</div>
|
<div className="mb-4">
|
||||||
<div className="mb-4">
|
<h3 className="text-neutral-700 font-medium">Email</h3>
|
||||||
<h3 className="text-gray-700 font-medium">Address</h3>
|
<p className="text-neutral-600">getintouch@tremortracker.org.uk</p>
|
||||||
<p className="text-gray-600">
|
</div>
|
||||||
1 Swentown Row, Greenwich, London, SE3 0FQ
|
<div className="mb-4">
|
||||||
</p>
|
<h3 className="text-neutral-700 font-medium">Phone</h3>
|
||||||
</div>
|
<p className="text-neutral-600">+44 7538 359022</p>
|
||||||
<h2 className="text-xl font-bold text-gray-800 mb-4 mt-6">
|
</div>
|
||||||
Follow Us
|
<div className="mb-4">
|
||||||
</h2>
|
<h3 className="text-neutral-700 font-medium">Address</h3>
|
||||||
<div className="flex justify-around items-center">
|
<p className="text-neutral-600">1 Swentown Row, Greenwich, London, SE3 0FQ</p>
|
||||||
<a
|
</div>
|
||||||
href="#"
|
<h2 className="text-xl font-bold text-neutral-800 mb-4 mt-6">Follow Us</h2>
|
||||||
className="w-20 h-20 text-blue-600 hover:text-blue-800 transition duration-200"
|
<div className="flex justify-around items-center">
|
||||||
>
|
<a href="#" className="w-20 h-20 text-blue-600 hover:text-blue-800 transition duration-200">
|
||||||
<span className="sr-only">Instagram</span>
|
<span className="sr-only">Instagram</span>
|
||||||
<Image height={200} width={200} alt="Logo" className="z-10" src="/insta.webp"/>
|
<Image height={200} width={200} alt="Logo" className="z-10" src="/insta.webp" />
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a href="#" className="w-20 h-20 text-blue-600 hover:text-blue-800 transition duration-200">
|
||||||
href="#"
|
<span className="sr-only">Facebook</span>
|
||||||
className="w-20 h-20 text-blue-600 hover:text-blue-800 transition duration-200"
|
<Image height={200} width={200} alt="Logo" className="z-10" src="/facebook.webp" />
|
||||||
>
|
</a>
|
||||||
<span className="sr-only">Facebook</span>
|
<a href="#" className="w-20 h-20 p-4 text-blue-600 hover:text-blue-800 transition duration-200">
|
||||||
<Image height={200} width={200} alt="Logo" className="z-10" src="/facebook.webp"/>
|
<span className="sr-only">X</span>
|
||||||
</a>
|
<Image height={200} width={200} alt="Logo" className="z-10 rounded-lg" src="/x_logo.jpg" />
|
||||||
<a
|
</a>
|
||||||
href="#"
|
<a href="#" className="w-20 h-20 flex items-center text-blue-600 hover:text-blue-800 transition duration-200">
|
||||||
className="w-20 h-20 p-4 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" />
|
||||||
<span className="sr-only">X</span>
|
</a>
|
||||||
<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"
|
|
||||||
>
|
|
||||||
<span className="sr-only">LinkedIn</span>
|
|
||||||
<Image height={200} width={200} alt="Logo" className="z-10" src="/linkedIn.png"/>
|
|
||||||
</a>
|
|
||||||
<h3 className="text-l font-bold text-gray-500 mb-2 mt-4">
|
<h3 className="text-l font-bold text-gray-500 mb-2 mt-4">
|
||||||
@tremor.tracker
|
@tremor.tracker
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ContactUs;
|
export default ContactUs;
|
||||||
|
|||||||
@ -1,80 +1,81 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Sidebar from "@/components/Sidebar";
|
import { useMemo, useState } from 'react';
|
||||||
import Map from "@/components/map";
|
|
||||||
import { useState, useMemo } from "react";
|
import Map from '@components/Map';
|
||||||
|
import Sidebar from '@components/Sidebar';
|
||||||
|
|
||||||
export default function Earthquakes() {
|
export default function Earthquakes() {
|
||||||
const [selectedEventId, setSelectedEventId] = useState("");
|
const [selectedEventId, setSelectedEventId] = useState("");
|
||||||
const [hoveredEventId, setHoveredEventId] = useState("");
|
const [hoveredEventId, setHoveredEventId] = useState("");
|
||||||
|
|
||||||
const events = useMemo(
|
const events = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
id: "1234",
|
id: "1234",
|
||||||
title: "Earthquake in Germany",
|
title: "Earthquake in Germany",
|
||||||
text1: "Magnitude 8.5",
|
text1: "Magnitude 8.5",
|
||||||
text2: "30 minutes ago",
|
text2: "30 minutes ago",
|
||||||
magnitude: 8.5,
|
magnitude: 8.5,
|
||||||
longitude: 10.4515, // Near Berlin, Germany
|
longitude: 10.4515, // Near Berlin, Germany
|
||||||
latitude: 52.52,
|
latitude: 52.52,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "2134",
|
id: "2134",
|
||||||
title: "Earthquake in California",
|
title: "Earthquake in California",
|
||||||
text1: "Magnitude 5.3",
|
text1: "Magnitude 5.3",
|
||||||
text2: "2 hours ago",
|
text2: "2 hours ago",
|
||||||
magnitude: 5.3,
|
magnitude: 5.3,
|
||||||
longitude: -122.4194, // Near San Francisco, California, USA
|
longitude: -122.4194, // Near San Francisco, California, USA
|
||||||
latitude: 37.7749,
|
latitude: 37.7749,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "2314",
|
id: "2314",
|
||||||
title: "Tremor in Japan",
|
title: "Tremor in Japan",
|
||||||
text1: "Magnitude 4.7",
|
text1: "Magnitude 4.7",
|
||||||
text2: "5 hours ago",
|
text2: "5 hours ago",
|
||||||
magnitude: 4.7,
|
magnitude: 4.7,
|
||||||
longitude: 139.6917, // Near Tokyo, Japan
|
longitude: 139.6917, // Near Tokyo, Japan
|
||||||
latitude: 35.6762,
|
latitude: 35.6762,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "2341",
|
id: "2341",
|
||||||
title: "Tremor in Spain",
|
title: "Tremor in Spain",
|
||||||
text1: "Magnitude 2.1",
|
text1: "Magnitude 2.1",
|
||||||
text2: "10 hours ago",
|
text2: "10 hours ago",
|
||||||
magnitude: 2.1,
|
magnitude: 2.1,
|
||||||
longitude: -3.7038, // Near Madrid, Spain
|
longitude: -3.7038, // Near Madrid, Spain
|
||||||
latitude: 40.4168,
|
latitude: 40.4168,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex overflow-hidden">
|
<div className="h-full flex overflow-hidden">
|
||||||
<div className="flex-grow">
|
<div className="flex-grow">
|
||||||
<Map
|
<Map
|
||||||
events={events}
|
events={events}
|
||||||
selectedEventId={selectedEventId}
|
selectedEventId={selectedEventId}
|
||||||
setSelectedEventId={setSelectedEventId}
|
setSelectedEventId={setSelectedEventId}
|
||||||
hoveredEventId={hoveredEventId}
|
hoveredEventId={hoveredEventId}
|
||||||
setHoveredEventId={setHoveredEventId}
|
setHoveredEventId={setHoveredEventId}
|
||||||
mapType="Earthquakes"
|
mapType="Earthquakes"
|
||||||
></Map>
|
></Map>
|
||||||
</div>
|
</div>
|
||||||
<Sidebar
|
<Sidebar
|
||||||
logTitle="Log an Earthquake"
|
logTitle="Log an Earthquake"
|
||||||
logSubtitle="Record new earthquakes - time/date, location, magnitude, observatory and scientists"
|
logSubtitle="Record new earthquakes - time/date, location, magnitude, observatory and scientists"
|
||||||
recentsTitle="Recent Earthquakes"
|
recentsTitle="Recent Earthquakes"
|
||||||
events={events}
|
events={events}
|
||||||
selectedEventId={selectedEventId}
|
selectedEventId={selectedEventId}
|
||||||
setSelectedEventId={setSelectedEventId}
|
setSelectedEventId={setSelectedEventId}
|
||||||
hoveredEventId={hoveredEventId}
|
hoveredEventId={hoveredEventId}
|
||||||
setHoveredEventId={setHoveredEventId}
|
setHoveredEventId={setHoveredEventId}
|
||||||
button1Name="Log an Earthquake"
|
button1Name="Log an Earthquake"
|
||||||
button2Name="Search Earthquakes"
|
button2Name="Search Earthquakes"
|
||||||
></Sidebar>
|
></Sidebar>
|
||||||
{/* <SidebarTest></SidebarTest> */}
|
{/* <SidebarTest></SidebarTest> */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,29 +1,43 @@
|
|||||||
|
"use client";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import Navbar from "@/components/Navbar";
|
|
||||||
import { Inter } from "next/font/google";
|
|
||||||
import "./globals.css";
|
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({
|
const inter = Inter({
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
variable: "--font-inter",
|
variable: "--font-inter",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
const store = createStore<StoreModel>({
|
||||||
title: "Tremor Tracker",
|
currency: {
|
||||||
description: "Generated by tim",
|
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({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body className={`${inter.variable} h-[calc(100vh-3.5rem)] flex flex-col min-h-screen antialiased`}>
|
<StoreProvider store={store}>
|
||||||
<Navbar></Navbar>
|
<body className={`${inter.variable} h-[calc(100vh-3.5rem)] flex flex-col min-h-screen antialiased`}>
|
||||||
<div className="flex-1 overflow-y-auto">{children}</div>
|
<Navbar></Navbar>
|
||||||
</body>
|
<div className="flex-1 overflow-y-auto">{children}</div>
|
||||||
</html>
|
</body>
|
||||||
);
|
</StoreProvider>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,68 +1,68 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Sidebar from "@/components/Sidebar";
|
import Sidebar from "@/components/Sidebar";
|
||||||
import Map from "@/components/map";
|
import Map from "@components/Map";
|
||||||
import { useState, useMemo } from "react";
|
import { useState, useMemo } from "react";
|
||||||
|
|
||||||
export default function Observatories() {
|
export default function Observatories() {
|
||||||
const [selectedEventId, setSelectedEventId] = useState("");
|
const [selectedEventId, setSelectedEventId] = useState("");
|
||||||
const [hoveredEventId, setHoveredEventId] = useState("");
|
const [hoveredEventId, setHoveredEventId] = useState("");
|
||||||
|
|
||||||
const events = useMemo(
|
const events = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
id: "1234",
|
id: "1234",
|
||||||
title: "Earthquake - Berlin Observatory",
|
title: "Earthquake - Berlin Observatory",
|
||||||
text1: "Logged by ",
|
text1: "Logged by ",
|
||||||
text2: "30 minutes ago",
|
text2: "30 minutes ago",
|
||||||
longitude: 10.4515, // Near Berlin, Germany
|
longitude: 10.4515, // Near Berlin, Germany
|
||||||
latitude: 52.52,
|
latitude: 52.52,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "2134",
|
id: "2134",
|
||||||
title: "New Observatory - Phuket, Thailand",
|
title: "New Observatory - Phuket, Thailand",
|
||||||
text1: "Dr. Neil Armstrong",
|
text1: "Dr. Neil Armstrong",
|
||||||
text2: "2 weeks ago",
|
text2: "2 weeks ago",
|
||||||
longitude: -122.4194,
|
longitude: -122.4194,
|
||||||
latitude: 37.7749,
|
latitude: 37.7749,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "2314",
|
id: "2314",
|
||||||
title: "Observatory Scientist Change",
|
title: "Observatory Scientist Change",
|
||||||
text1: "Dr. Samantha Green new lead scientist",
|
text1: "Dr. Samantha Green new lead scientist",
|
||||||
text2: "1 month ago",
|
text2: "1 month ago",
|
||||||
longitude: 139.6917,
|
longitude: 139.6917,
|
||||||
latitude: 35.6762,
|
latitude: 35.6762,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex overflow-hidden">
|
<div className="h-full flex overflow-hidden">
|
||||||
<div className="flex-grow">
|
<div className="flex-grow">
|
||||||
<Map
|
<Map
|
||||||
events={events}
|
events={events}
|
||||||
selectedEventId={selectedEventId}
|
selectedEventId={selectedEventId}
|
||||||
setSelectedEventId={setSelectedEventId}
|
setSelectedEventId={setSelectedEventId}
|
||||||
hoveredEventId={hoveredEventId}
|
hoveredEventId={hoveredEventId}
|
||||||
setHoveredEventId={setHoveredEventId}
|
setHoveredEventId={setHoveredEventId}
|
||||||
mapType="observatories"
|
mapType="observatories"
|
||||||
></Map>
|
></Map>
|
||||||
</div>
|
</div>
|
||||||
<Sidebar
|
<Sidebar
|
||||||
logTitle="Observatory Mapping"
|
logTitle="Observatory Mapping"
|
||||||
logSubtitle="Record and search observatories - time/date set-up, location, scientists and recent earthquakes"
|
logSubtitle="Record and search observatories - time/date set-up, location, scientists and recent earthquakes"
|
||||||
recentsTitle="Observatory Events"
|
recentsTitle="Observatory Events"
|
||||||
events={events}
|
events={events}
|
||||||
selectedEventId={selectedEventId}
|
selectedEventId={selectedEventId}
|
||||||
setSelectedEventId={setSelectedEventId}
|
setSelectedEventId={setSelectedEventId}
|
||||||
hoveredEventId={hoveredEventId}
|
hoveredEventId={hoveredEventId}
|
||||||
setHoveredEventId={setHoveredEventId}
|
setHoveredEventId={setHoveredEventId}
|
||||||
button1Name="Log a New Observatory"
|
button1Name="Log a New Observatory"
|
||||||
button2Name="Search Observatories"
|
button2Name="Search Observatories"
|
||||||
></Sidebar>
|
></Sidebar>
|
||||||
{/* <SidebarTest></SidebarTest> */}
|
{/* <SidebarTest></SidebarTest> */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -72,6 +72,46 @@ const OurMission = () => {
|
|||||||
</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">
|
||||||
|
<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;
|
export default OurMission;
|
||||||
|
|||||||
@ -1,185 +1,282 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import Sidebar from "@/components/Sidebar";
|
import { Dispatch, SetStateAction, useCallback, useState } from "react";
|
||||||
import { 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
|
// Artifacts Data
|
||||||
const artifacts = [
|
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: 1,
|
||||||
{ id: 3, name: "Medieval Chalice", description: "Used by royalty in medieval ceremonies.", location: "Cambridge, England", image: "/images/artifact3.jpg", price: 120 },
|
name: "Golden Scarab",
|
||||||
{ id: 4, name: "Roman Coin", description: "An authentic Roman coin from the 2nd century CE.", location: "Rome, Italy", image: "/images/artifact4.jpg", price: 80 },
|
description: "An ancient Egyptian artifact symbolizing rebirth.",
|
||||||
{ id: 5, name: "Samurai Mask", description: "Replica of Japanese Samurai battle masks.", location: "Tokyo, Japan", image: "/images/artifact5.jpg", price: 300 },
|
location: "Cairo, Egypt",
|
||||||
{ id: 6, name: "Ancient Greek Vase", description: "Depicts Greek mythology, found in the Acropolis.", location: "Athens, Greece", image: "/images/artifact6.jpg", price: 250 },
|
image: "/images/artifact1.jpg",
|
||||||
{ id: 7, name: "Incan Pendant", description: "Represents the Sun God Inti.", location: "India", image: "/images/artifact7.jpg", price: 175 },
|
price: 150,
|
||||||
{ 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: 2,
|
||||||
{ id: 11, name: "Ancient Scroll", description: "A mysterious scroll from ancient times.", location: "Madrid, Spain", image: "/images/artifact11.jpg", price: 500 },
|
name: "Aztec Sunstone",
|
||||||
{ id: 12, name: "Ming Dynasty Porcelain", description: "Porcelain from China's Ming Dynasty.", location: "Beijing, China", image: "/images/artifact12.jpg", price: 300 },
|
description: "A replica of the Aztec calendar (inscriptions intact).",
|
||||||
{ id: 13, name: "African Tribal Mask", description: "A unique tribal mask from Africa.", location: "Nigeria", image: "/images/artifact13.jpg", price: 250 },
|
location: "Peru",
|
||||||
{ id: 14, name: "Crystal Skull", description: "A mystical pre-Columbian artifact.", location: "Colombia", image: "/images/artifact14.jpg", price: 1000 },
|
image: "/images/artifact2.jpg",
|
||||||
{ id: 15, name: "Medieval Armor Fragment", description: "A fragment of medieval armor.", location: "Normandy, France", image: "/images/artifact15.jpg", price: 400 },
|
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
|
// 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
|
// Shop Component
|
||||||
export default function Shop() {
|
export default function Shop() {
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
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 artifactsPerPage = 9; // Number of artifacts per page
|
||||||
const indexOfLastArtifact = currentPage * artifactsPerPage;
|
const indexOfLastArtifact = currentPage * artifactsPerPage;
|
||||||
const indexOfFirstArtifact = indexOfLastArtifact - artifactsPerPage;
|
const indexOfFirstArtifact = indexOfLastArtifact - artifactsPerPage;
|
||||||
const currentArtifacts = artifacts.slice(indexOfFirstArtifact, indexOfLastArtifact);
|
const currentArtifacts = artifacts.slice(indexOfFirstArtifact, indexOfLastArtifact);
|
||||||
|
|
||||||
const handleNextPage = () => {
|
const selectedCurrency = useStoreState((state) => state.currency.selectedCurrency);
|
||||||
if (indexOfLastArtifact < artifacts.length) {
|
const conversionRates = useStoreState((state) => state.currency.conversionRates);
|
||||||
setCurrentPage((prev) => prev + 1);
|
const currencyTickers = useStoreState((state) => state.currency.tickers);
|
||||||
}
|
const convertPrice = useCallback((price: number, currency: Currency) => (price * conversionRates[currency]).toFixed(2), []);
|
||||||
};
|
|
||||||
|
|
||||||
const handlePreviousPage = () => {
|
const handleNextPage = () => {
|
||||||
if (currentPage > 1) {
|
if (indexOfLastArtifact < artifacts.length) {
|
||||||
setCurrentPage((prev) => prev - 1);
|
setCurrentPage((prev) => prev + 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectArtifact = (artifact) => {
|
const handlePreviousPage = () => {
|
||||||
setSelectedArtifact(artifact); // Open modal with selected artifact
|
if (currentPage > 1) {
|
||||||
};
|
setCurrentPage((prev) => prev - 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleCloseModal = () => {
|
function Modal({ artifact }: { artifact: Artifact }) {
|
||||||
setSelectedArtifact(null); // Close modal
|
if (!artifact) return null;
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
const handleOverlayClick = (e: { target: any; currentTarget: any }) => {
|
||||||
<div className="flex flex-col min-h-screen bg-gray-100">
|
if (e.target === e.currentTarget) {
|
||||||
{/* Main Content */}
|
setSelectedArtifact(null);
|
||||||
<div className="flex flex-1">
|
}
|
||||||
{/* Artifact Grid */}
|
};
|
||||||
<div className="flex-grow grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 p-6 pr-72">
|
|
||||||
{currentArtifacts.map((artifact) => (
|
|
||||||
<ArtifactCard key={artifact.id} artifact={artifact} onSelect={handleSelectArtifact} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Sidebar */}
|
return (
|
||||||
<aside className="w-72 bg-white shadow-lg p-4 h-screen fixed top-10 right-0 overflow-auto">
|
<div
|
||||||
<Sidebar
|
className="fixed inset-0 bg-neutral-900 bg-opacity-50 flex justify-center items-center z-50"
|
||||||
logTitle="Artifact Collection"
|
onClick={handleOverlayClick}
|
||||||
logSubtitle="Record new artifacts - name, description, image, location and price"
|
>
|
||||||
recentsTitle="Recent Updates"
|
<div className="bg-white rounded-md shadow-lg max-w-lg w-full p-6">
|
||||||
events={[/* example events if needed */]}
|
<h3 className="text-xl font-bold mb-4">{artifact.name}</h3>
|
||||||
selectedEventId=""
|
<img src={artifact.image} alt={artifact.name} className="w-full h-64 object-cover rounded-md mb-4" />
|
||||||
setSelectedEventId={() => {}}
|
<p className="text-neutral-600 mb-2">{artifact.description}</p>
|
||||||
hoveredEventId=""
|
<p className="text-neutral-500 font-bold mb-4">Location: {artifact.location}</p>
|
||||||
setHoveredEventId={() => {}}
|
<p className="text-red-600 font-bold">Price: ${artifact.price}</p>
|
||||||
button1Name="Add New Artifact"
|
<div className="flex justify-end gap-4 mt-6">
|
||||||
button2Name="Search Artifacts"
|
<button
|
||||||
/>
|
onClick={() => alert("Reserved Successfully!")}
|
||||||
</aside>
|
className="px-4 py-2 bg-yellow-500 text-white rounded-md hover:bg-yellow-400"
|
||||||
</div>
|
>
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
{/* Pagination Footer */}
|
// ArtifactCard Component
|
||||||
<footer className="bg-white border-t border-gray-300 py-2 text-center w-full flex justify-center items-centre">
|
function ArtifactCard({ artifact }: { artifact: Artifact }) {
|
||||||
<button
|
return (
|
||||||
onClick={handlePreviousPage}
|
<div
|
||||||
disabled={currentPage === 1}
|
className="flex flex-col bg-white shadow-md rounded-md overflow-hidden cursor-pointer hover:scale-105 transition-transform"
|
||||||
className={`mx-2 px-4 py-1 bg-blue-700 text-white rounded-md font-bold shadow-md ${
|
onClick={() => setSelectedArtifact(artifact)} // Opens modal
|
||||||
currentPage === 1 ? "opacity-50 cursor-not-allowed" : "hover:bg-blue-600"
|
>
|
||||||
}`} >
|
<img src={artifact.image} alt={artifact.name} className="w-full h-56 object-cover" />
|
||||||
← Previous
|
<div className="p-4">
|
||||||
</button>
|
<h3 className="text-lg font-bold">{artifact.name}</h3>
|
||||||
<button
|
<p className="text-neutral-700 mb-2">{artifact.description}</p>
|
||||||
onClick={handleNextPage}
|
<p className="text-neutral-500 mb-2">{artifact.location}</p>
|
||||||
disabled={indexOfLastArtifact >= artifacts.length}
|
<p className="text-blue-600 font-bold text-md mt-4">
|
||||||
className={`mx-2 px-4 py-1 bg-blue-500 text-white rounded-md font-bold shadow-md ${
|
{currencyTickers[selectedCurrency]}
|
||||||
indexOfLastArtifact >= artifacts.length ? "opacity-50 cursor-not-allowed" : "hover:bg-blue-600"
|
{convertPrice(artifact.price, selectedCurrency)}
|
||||||
}`} >
|
</p>
|
||||||
Next →
|
</div>
|
||||||
</button>
|
</div>
|
||||||
</footer>
|
);
|
||||||
|
}
|
||||||
|
|
||||||
{/* Modal */}
|
return (
|
||||||
<Modal artifact={selectedArtifact} onClose={handleCloseModal} />
|
<div className="flex flex-col min-h-screen bg-neutral-100">
|
||||||
</div>
|
{/* Main Content */}
|
||||||
);
|
<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">
|
||||||
|
{currentArtifacts.map((artifact) => (
|
||||||
|
<ArtifactCard key={artifact.id} artifact={artifact} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* Sidebar */}
|
||||||
|
<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 */
|
||||||
|
]
|
||||||
|
}
|
||||||
|
selectedEventId=""
|
||||||
|
setSelectedEventId={() => {}}
|
||||||
|
hoveredEventId=""
|
||||||
|
setHoveredEventId={() => {}}
|
||||||
|
button1Name="Add New Artifact"
|
||||||
|
button2Name="Search Artifacts"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Pagination Footer */}
|
||||||
|
<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"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
← Previous
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleNextPage}
|
||||||
|
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 →
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
|
{/* Modal */}
|
||||||
|
{selectedArtifact && <Modal artifact={selectedArtifact} />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,55 +3,53 @@ import Sidebar from "@/components/Sidebar";
|
|||||||
import { useState, useMemo } from "react";
|
import { useState, useMemo } from "react";
|
||||||
|
|
||||||
export default function Warehouse() {
|
export default function Warehouse() {
|
||||||
const [selectedEventId, setSelectedEventId] = useState("");
|
const [selectedEventId, setSelectedEventId] = useState("");
|
||||||
const [hoveredEventId, setHoveredEventId] = useState("");
|
const [hoveredEventId, setHoveredEventId] = useState("");
|
||||||
const events = useMemo(
|
const events = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
id: "1234",
|
id: "1234",
|
||||||
title: "Artifact found - Germany Earthquake",
|
title: "Artifact found - Germany Earthquake",
|
||||||
text1: "Mortar and pestle",
|
text1: "Mortar and pestle",
|
||||||
text2: "10 minutes ago",
|
text2: "10 minutes ago",
|
||||||
longitude: 10.4515, // Near Berlin, Germany
|
longitude: 10.4515, // Near Berlin, Germany
|
||||||
latitude: 52.52,
|
latitude: 52.52,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "2134",
|
id: "2134",
|
||||||
title: "All artifacts from California earthquake transported",
|
title: "All artifacts from California earthquake transported",
|
||||||
text1: "India to London",
|
text1: "India to London",
|
||||||
text2: "15 hours ago",
|
text2: "15 hours ago",
|
||||||
longitude: -122.4194, // Near San Francisco, California, USA
|
longitude: -122.4194, // Near San Francisco, California, USA
|
||||||
latitude: 37.7749,
|
latitude: 37.7749,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "2341",
|
id: "2341",
|
||||||
title: "7 artifacts destroyed - Spain earthquake",
|
title: "7 artifacts destroyed - Spain earthquake",
|
||||||
text1: "edVases and pots from Madrid",
|
text1: "edVases and pots from Madrid",
|
||||||
text2: "3 weeks ago",
|
text2: "3 weeks ago",
|
||||||
longitude: -3.7038, // Near Madrid, Spain
|
longitude: -3.7038, // Near Madrid, Spain
|
||||||
latitude: 40.4168,
|
latitude: 40.4168,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full">
|
<div className="w-full h-full">
|
||||||
<p>
|
<p>warehouse image pasted in here</p>
|
||||||
warehouse image pasted in here
|
<Sidebar
|
||||||
</p>
|
logTitle="Artifact Retrieval and Tracking"
|
||||||
<Sidebar
|
logSubtitle="Record new artifacts collected - name, description, time/date found, location found, scientist and collection stage"
|
||||||
logTitle="Artifact Retrieval and Tracking"
|
recentsTitle="Artifact News"
|
||||||
logSubtitle="Record new artifacts collected - name, description, time/date found, location found, scientist and collection stage"
|
events={events}
|
||||||
recentsTitle="Artifact News"
|
selectedEventId={selectedEventId}
|
||||||
events={events}
|
setSelectedEventId={setSelectedEventId}
|
||||||
selectedEventId={selectedEventId}
|
hoveredEventId={hoveredEventId}
|
||||||
setSelectedEventId={setSelectedEventId}
|
setHoveredEventId={setHoveredEventId}
|
||||||
hoveredEventId={hoveredEventId}
|
button1Name="Log New Artifacts"
|
||||||
setHoveredEventId={setHoveredEventId}
|
button2Name="Search Artifacts"
|
||||||
button1Name="Log New Artifacts"
|
></Sidebar>
|
||||||
button2Name="Search Artifacts"
|
</div>
|
||||||
></Sidebar>
|
);
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
// tells React if you're using its "server-side rendering" features. this component runs only on the client-side (browser)
|
// tells React if you're using its "server-side rendering" features. this component runs only on the client-side (browser)
|
||||||
"use client";
|
"use client";
|
||||||
// importing React hooks or utilities that enhance functionality inside the component
|
// 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).
|
useState: Used to manage state (data that changes over time).
|
||||||
FormEvent: TypeScript definition for form-related events like submission.
|
FormEvent: TypeScript definition for form-related events like submission.
|
||||||
@ -12,39 +13,40 @@ import { useState, FormEvent, useRef, MouseEvent, useEffect } from "react";
|
|||||||
|
|
||||||
// defining a type for the props (inputs) that AuthModal expects
|
// defining a type for the props (inputs) that AuthModal expects
|
||||||
interface AuthModalProps {
|
interface AuthModalProps {
|
||||||
isOpen: boolean; // bool for if the modal should be visible
|
isOpen: boolean; // bool for if the modal should be visible
|
||||||
onClose: () => void; //A function that will be executed to close the modal
|
onClose: () => void; //A function that will be executed to close the modal
|
||||||
}
|
}
|
||||||
// creates a React functional component
|
// 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) {
|
||||||
const [isLogin, setIsLogin] = useState<boolean>(true);
|
//AuthModalProps ensures TypeScript validates the props to match the type definition above
|
||||||
const modalRef = useRef<HTMLDivElement>(null);
|
const [isLogin, setIsLogin] = useState<boolean>(true);
|
||||||
const [isFailed, setIsFailed] = useState<boolean>(false);
|
const modalRef = useRef<HTMLDivElement>(null);
|
||||||
const [failMessage, setFailMessage] = useState<boolean>(false);
|
const [isFailed, setIsFailed] = useState<boolean>(false);
|
||||||
/*
|
const [failMessage, setFailMessage] = useState<boolean>(false);
|
||||||
|
/*
|
||||||
useState is a React Hook that declares state variables in a functional component. It returns a two-element array
|
useState is a React Hook that declares state variables in a functional component. It returns a two-element array
|
||||||
The state variable (isLogin) : Represents the current state value (e.g., true initially). This is the value you can use in your component.
|
The state variable (isLogin) : Represents the current state value (e.g., true initially). This is the value you can use in your component.
|
||||||
The state updater function (setIsLogin) : A function that allows you to update the state variable. React takes care of re-rendering the component when the state is updated
|
The state updater function (setIsLogin) : A function that allows you to update the state variable. React takes care of re-rendering the component when the state is updated
|
||||||
*/
|
*/
|
||||||
/*
|
/*
|
||||||
modalRef allows direct access to the modal DOM element (the container div for the modal). This is useful for detecting if the user clicks outside of the modal
|
modalRef allows direct access to the modal DOM element (the container div for the modal). This is useful for detecting if the user clicks outside of the modal
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// useEffect runs code after the component renders or when a dependency changes (in this case, isOpen as seen in the end [])
|
// useEffect runs code after the component renders or when a dependency changes (in this case, isOpen as seen in the end [])
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) setIsLogin(true); // runs when isOpen changes, if it is true, the login is shown
|
if (isOpen) setIsLogin(true); // runs when isOpen changes, if it is true, the login is shown
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
if (!isOpen) return null; // if is open is false, the model isnt shown
|
if (!isOpen) return null; // if is open is false, the model isnt shown
|
||||||
|
|
||||||
// this is an arrow function. e: is used to specify that an event object is expected
|
// this is an arrow function. e: is used to specify that an event object is expected
|
||||||
const handleOverlayClick = (e: MouseEvent<HTMLDivElement>) => {
|
const handleOverlayClick = (e: MouseEvent<HTMLDivElement>) => {
|
||||||
if (modalRef.current && !modalRef.current.contains(e.target as Node)) {
|
if (modalRef.current && !modalRef.current.contains(e.target as Node)) {
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
.current gives a reference to the actual DOM element (the inner modal container)
|
.current gives a reference to the actual DOM element (the inner modal container)
|
||||||
e.target refers to the specific element the mouse click event occurred on
|
e.target refers to the specific element the mouse click event occurred on
|
||||||
.contains(e.target) checks if the clicked element (e.target) is inside the modal (modalRef.current)
|
.contains(e.target) checks if the clicked element (e.target) is inside the modal (modalRef.current)
|
||||||
@ -54,9 +56,9 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) { //AuthM
|
|||||||
Logic : if modalRef.current exists and the click target (e.target) is not inside the modal , then the condition is true
|
Logic : if modalRef.current exists and the click target (e.target) is not inside the modal , then the condition is true
|
||||||
This means the user clicked outside the modal
|
This means the user clicked outside the modal
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// LS - The following bit contains the more important code for what I'm focused on
|
// LS - The following bit contains the more important code for what I'm focused on
|
||||||
/*
|
/*
|
||||||
Note : handleSubmit is typically used as a event handler for submitting a form in react.
|
Note : handleSubmit is typically used as a event handler for submitting a form in react.
|
||||||
For example:
|
For example:
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
@ -64,105 +66,113 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) { //AuthM
|
|||||||
<button type="submit">Submit</button>
|
<button type="submit">Submit</button>
|
||||||
</form>
|
</form>
|
||||||
*/
|
*/
|
||||||
/*
|
/*
|
||||||
e is the parameter passsed into the function
|
e is the parameter passsed into the function
|
||||||
async indicates the function runs asyncronously, meaning it performs tasks which take time.
|
async indicates the function runs asyncronously, meaning it performs tasks which take time.
|
||||||
an example of this would be API calls
|
an example of this would be API calls
|
||||||
*/
|
*/
|
||||||
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault(); // stops page from refreshing
|
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 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 email = formData.get("email") as string; // gets email from form response
|
||||||
const password = formData.get("password") as string; // gets password from form response
|
const password = formData.get("password") as string; // gets password from form response
|
||||||
const name = isLogin ? undefined : (formData.get("name") as string);// if the form is in login mode, the name is undefine, otherwise the name is a value obtained from the response
|
const name = isLogin ? undefined : (formData.get("name") as string); // if the form is in login mode, the name is undefine, otherwise the name is a value obtained from the response
|
||||||
|
|
||||||
let endpoint = isLogin ? "/api/login" : "/api/signup"; // sets endpoint for backend code (either sign up or login)
|
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
|
const body = isLogin ? { email, password } : { name: name!, email, password }; // creates a json body for the backend
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log("Sending data to API");
|
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, {
|
||||||
method: "POST", // Post is used since the form submission modifies the server side state
|
// sends a request to the server at the end point
|
||||||
headers: { "Content-Type": "application/json" }, //indicates it expects a json object returned
|
method: "POST", // Post is used since the form submission modifies the server side state
|
||||||
body: JSON.stringify(body), // converts the body to a JSON string to be sent
|
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
|
});
|
||||||
console.log("Success!");
|
if (res.ok) {
|
||||||
onClose(); // closes UI
|
//res.ok checks if the response is between 200-299
|
||||||
} else if (res.status >= 400 && res.status <500){
|
console.log("Success!");
|
||||||
const responseBody = await res.json()
|
onClose(); // closes UI
|
||||||
console.log("4xx error:", responseBody.message)
|
} else if (res.status >= 400 && res.status < 500) {
|
||||||
setFailMessage(responseBody.message)
|
const responseBody = await res.json();
|
||||||
setIsFailed(true)
|
console.log("4xx error:", responseBody.message);
|
||||||
} else{
|
setFailMessage(responseBody.message);
|
||||||
console.error("Error:", await res.text()); // logs error with error message sent to console
|
setIsFailed(true);
|
||||||
}
|
} else {
|
||||||
} catch (error) {// catches any errors (e.g. Not connected to network)
|
console.error("Error:", await res.text()); // logs error with error message sent to console
|
||||||
console.error("Request failed:", error instanceof Error ? error.message : String(error));
|
}
|
||||||
}
|
} catch (error) {
|
||||||
};
|
// catches any errors (e.g. Not connected to network)
|
||||||
|
console.error("Request failed:", error instanceof Error ? error.message : String(error));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center z-50" onClick={handleOverlayClick}>
|
<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">
|
<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={() => {
|
||||||
</button>
|
setIsFailed(false);
|
||||||
<h2 className="text-2xl font-bold text-center mb-4">{isLogin ? "Login" : "Sign Up"}</h2>
|
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>
|
||||||
|
|
||||||
{/* Form */}
|
{/* Form */}
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
{!isLogin && (
|
{!isLogin && (
|
||||||
<div>
|
<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
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="name"
|
name="name"
|
||||||
className="mt-1 w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="mt-1 w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
required={!isLogin}
|
required={!isLogin}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<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
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
name="email"
|
name="email"
|
||||||
className="mt-1 w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="mt-1 w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<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
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
name="password"
|
name="password"
|
||||||
className="mt-1 w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="mt-1 w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{ isFailed && (
|
{isFailed && (
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-red-700">{failMessage}</label>
|
<label className="block text-sm font-medium text-red-700">{failMessage}</label>
|
||||||
</div>
|
</div>
|
||||||
)
|
)}
|
||||||
}
|
</div>
|
||||||
</div>
|
<button type="submit" className="w-full bg-blue-600 text-white p-2 rounded-md hover:bg-blue-700 transition">
|
||||||
<button type="submit" className="w-full bg-blue-600 text-white p-2 rounded-md hover:bg-blue-700 transition">
|
{isLogin ? "Login" : "Sign Up"}
|
||||||
{isLogin ? "Login" : "Sign Up"}
|
</button>
|
||||||
</button>
|
</form>
|
||||||
</form>
|
|
||||||
|
|
||||||
<p className="mt-4 text-center text-sm">
|
<p className="mt-4 text-center text-sm">
|
||||||
{isLogin ? "Need an account?" : "Already have an account?"}{" "}
|
{isLogin ? "Need an account?" : "Already have an account?"}{" "}
|
||||||
<button onClick={() => setIsLogin(!isLogin)} className="text-blue-600 hover:underline">
|
<button onClick={() => setIsLogin(!isLogin)} className="text-blue-600 hover:underline">
|
||||||
{isLogin ? "Sign Up" : "Login"}
|
{isLogin ? "Sign Up" : "Login"}
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,100 +1,101 @@
|
|||||||
import React from "react";
|
|
||||||
import { Dispatch, SetStateAction } from "react";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import React, { Dispatch, SetStateAction } from "react";
|
||||||
import { TbHexagon } from "react-icons/tb";
|
import { TbHexagon } from "react-icons/tb";
|
||||||
|
|
||||||
import Event from "@appTypes/Event";
|
import Event from "@appTypes/Event";
|
||||||
import getMagnitudeColor from "@utils/getMagnitudeColour";
|
import getMagnitudeColor from "@utils/getMagnitudeColour";
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
logTitle: string;
|
logTitle: string;
|
||||||
logSubtitle: string;
|
logSubtitle: string;
|
||||||
recentsTitle: string;
|
recentsTitle: string;
|
||||||
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>>;
|
||||||
button1Name: string;
|
button1Name: string;
|
||||||
button2Name: string;
|
button2Name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function MagnitudeNumber({ magnitude }: { magnitude: number }) {
|
function MagnitudeNumber({ magnitude }: { magnitude: number }) {
|
||||||
// Convert magnitude to string with one decimal place
|
// Convert magnitude to string with one decimal place
|
||||||
const magnitudeStr = magnitude.toFixed(1);
|
const magnitudeStr = magnitude.toFixed(1);
|
||||||
const [whole, decimal] = magnitudeStr.split(".");
|
const [whole, decimal] = magnitudeStr.split(".");
|
||||||
|
|
||||||
// Define color based on magnitude (0-10 scale)
|
// Define color based on magnitude (0-10 scale)
|
||||||
return (
|
return (
|
||||||
<div className={`relative`} style={{ color: getMagnitudeColor(magnitude) }}>
|
<div className={`relative`} style={{ color: getMagnitudeColor(magnitude) }}>
|
||||||
<TbHexagon size={40} className="drop-shadow-sm" />
|
<TbHexagon size={40} className="drop-shadow-sm" />
|
||||||
<div className="absolute inset-0 flex items-center justify-center">
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
<div className="flex items-baseline font-mono font-bold tracking-tight">
|
<div className="flex items-baseline font-mono font-bold tracking-tight">
|
||||||
<span className="text-xl -mr-1">{whole}</span>
|
<span className="text-xl -mr-1">{whole}</span>
|
||||||
<span className="text-xs ml-[1.5px] -mr-[2.5px]">.</span>
|
<span className="text-xs ml-[1.5px] -mr-[2.5px]">.</span>
|
||||||
<span className="text-xs -mr-[1px]">{decimal}</span>
|
<span className="text-xs -mr-[1px]">{decimal}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Sidebar({
|
export default function Sidebar({
|
||||||
logTitle,
|
logTitle,
|
||||||
logSubtitle,
|
logSubtitle,
|
||||||
recentsTitle,
|
recentsTitle,
|
||||||
events,
|
events,
|
||||||
selectedEventId,
|
selectedEventId,
|
||||||
setSelectedEventId,
|
setSelectedEventId,
|
||||||
hoveredEventId,
|
hoveredEventId,
|
||||||
setHoveredEventId,
|
setHoveredEventId,
|
||||||
button1Name,
|
button1Name,
|
||||||
button2Name,
|
button2Name,
|
||||||
}: SidebarProps) {
|
}: SidebarProps) {
|
||||||
return (
|
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="px-6 pb-8 border-b border-neutral-200">
|
<div className="py-6">
|
||||||
<h2 className="text-2xl font-bold text-neutral-800 mb-2">{logTitle}</h2>
|
<div className="px-6 pb-8 border-b border-neutral-200">
|
||||||
<p className="text-sm text-neutral-600 leading-relaxed">{logSubtitle}</p>
|
<h2 className={`text-2xl font-bold text-neutral-800 mb-2`}>{logTitle}</h2>
|
||||||
<Link href="/">
|
<p className="text-sm text-neutral-600 leading-relaxed">{logSubtitle}</p>
|
||||||
<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}
|
|
||||||
</button>
|
|
||||||
</Link>
|
|
||||||
<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">
|
|
||||||
{button2Name}
|
|
||||||
</button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex-1 px-6 pt-6 overflow-y-auto">
|
<Link href="/">
|
||||||
<h2 className="text-xl font-bold text-neutral-800 mb-4">{recentsTitle}</h2>
|
<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">
|
||||||
<div className="space-y-3">
|
{button1Name}
|
||||||
{events.map((event) => (
|
</button>
|
||||||
<button
|
</Link>
|
||||||
key={event.title}
|
<Link href="/">
|
||||||
className={`w-full border ${hoveredEventId === event.id ? "bg-neutral-100" : "bg-white"} ${
|
<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">
|
||||||
selectedEventId === event.id ? "border-neutral-800" : "border-neutral-200"
|
{button2Name}
|
||||||
} rounded-lg p-4 flex items-center gap-4 hover:bg-neutral-100 transition-colors duration-150 shadow-sm text-left`}
|
</button>
|
||||||
onClick={() => {
|
</Link>
|
||||||
setSelectedEventId((prevEventId) => (prevEventId !== event.id ? event.id : ""));
|
</div>
|
||||||
}}
|
|
||||||
onMouseEnter={() => setHoveredEventId(event.id)}
|
<div className="flex-1 px-6 pt-6 overflow-y-auto">
|
||||||
onMouseLeave={() => setHoveredEventId("")}
|
<h2 className="text-xl font-bold text-neutral-800 mb-4">{recentsTitle}</h2>
|
||||||
>
|
<div className="space-y-3">
|
||||||
<div className="flex-1">
|
{events.map((event) => (
|
||||||
<p className="text-sm font-medium text-neutral-800">{event.title}</p>
|
<button
|
||||||
{/* Uncomment if you want to use text1 */}
|
key={event.title}
|
||||||
{/* <p className="text-xs text-neutral-600">{event.text1}</p> */}
|
className={`w-full border ${hoveredEventId === event.id ? "bg-neutral-100" : "bg-white"} ${
|
||||||
<p className="text-xs text-neutral-500 mt-1">{event.text2}</p>
|
selectedEventId === event.id ? "border-neutral-800" : "border-neutral-200"
|
||||||
</div>
|
} rounded-lg p-4 flex items-center gap-4 hover:bg-neutral-100 transition-colors duration-150 shadow-sm text-left`}
|
||||||
{
|
onClick={() => {
|
||||||
(event.magnitude) ? <MagnitudeNumber magnitude={event.magnitude} /> : <></>
|
setSelectedEventId((prevEventId) => (prevEventId !== event.id ? event.id : ""));
|
||||||
}
|
}}
|
||||||
</button>
|
onMouseEnter={() => setHoveredEventId(event.id)}
|
||||||
))}
|
onMouseLeave={() => setHoveredEventId("")}
|
||||||
</div>
|
>
|
||||||
</div>
|
<div className="flex-1">
|
||||||
</div>
|
<p className="text-sm font-medium text-neutral-800">{event.title}</p>
|
||||||
);
|
{/* Uncomment if you want to use text1 */}
|
||||||
|
{/* <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} /> : <></>}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,75 +1,117 @@
|
|||||||
"use client";
|
"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 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 { 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[] }) {
|
function NavbarButton({ name, href, dropdownItems }: { name: string; href: string; dropdownItems?: string[] }) {
|
||||||
const pathname = usePathname();
|
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 (
|
return (
|
||||||
<button className="flex items-center justify-center px-2 py-4 relative group">
|
<button className="flex items-center justify-center px-2 py-4 relative group">
|
||||||
{dropdownItems ? (
|
{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"}`}
|
||||||
<Link href={href} className={`px-4 py-1.5 rounded-md transition-colors ${isActive ? "bg-neutral-200" : "hover:bg-neutral-200"}`}>
|
>
|
||||||
{name}
|
{name}
|
||||||
</Link>
|
</span>
|
||||||
)}
|
) : (
|
||||||
{dropdownItems && (
|
<Link
|
||||||
<div className="absolute hidden group-hover:block top-full left-1/2 -translate-x-1/2 w-40 bg-white border border-neutral-300 rounded-lg overflow-hidden shadow-lg z-40">
|
href={href}
|
||||||
<ul>
|
className={`px-4 py-1.5 rounded-md transition-colors ${isActive ? "bg-neutral-200" : "hover:bg-neutral-200"}`}
|
||||||
{dropdownItems.map((item) => {
|
>
|
||||||
const itemHref = `/${item.toLowerCase().replace(" ", "-")}`;
|
{name}
|
||||||
const isDropdownActive = pathname === itemHref;
|
</Link>
|
||||||
return (
|
)}
|
||||||
<li key={item}>
|
{dropdownItems && (
|
||||||
<Link href={itemHref} className={`block px-4 py-2 hover:bg-neutral-100 ${isDropdownActive ? "bg-neutral-100" : ""}`}>
|
<div className="absolute hidden group-hover:block top-full left-1/2 -translate-x-1/2 w-40 bg-white border border-neutral-300 rounded-lg overflow-hidden shadow-lg z-40">
|
||||||
{item}
|
<ul>
|
||||||
</Link>
|
{dropdownItems.map((item) => {
|
||||||
</li>
|
const itemHref = `/${item.toLowerCase().replace(" ", "-")}`;
|
||||||
);
|
const isDropdownActive = pathname === itemHref;
|
||||||
})}
|
return (
|
||||||
</ul>
|
<li key={item}>
|
||||||
</div>
|
<Link
|
||||||
)}
|
href={itemHref}
|
||||||
</button>
|
className={`block px-4 py-2 hover:bg-neutral-100 ${isDropdownActive ? "bg-neutral-100" : ""}`}
|
||||||
);
|
>
|
||||||
|
{item}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Navbar() {
|
export default function Navbar({}: // currencySelector,
|
||||||
const navOptions = useMemo(() => ["Earthquakes", "Observatories", "Warehouse", "Shop"], []);
|
{
|
||||||
// const navOptions = useMemo(() => ["Earthquakes"], []);
|
// currencySelector?: { selectedCurrency: string; setSelectedCurrency: Dispatch<SetStateAction<"GBP" | "USD" | "EUR">> };
|
||||||
const aboutDropdown = ["Contact Us", "Our Mission", "The Team"];
|
}) {
|
||||||
// { label: "Our Mission", path: "/our-mission" },
|
const pathname = usePathname();
|
||||||
// { label: "The Team", path: "/the-team" },
|
const selectedCurrency = useStoreState((state) => state.currency.selectedCurrency);
|
||||||
// { label: "Contact Us", path: "/contact-us" }]
|
const setSelectedCurrency = useStoreActions((actions) => actions.currency.setSelectedCurrency);
|
||||||
|
const navOptions = useMemo(() => ["Earthquakes", "Observatories", "Warehouse", "Shop"], []);
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
// const navOptions = useMemo(() => ["Earthquakes"], []);
|
||||||
|
const aboutDropdown = ["Contact Us", "Our Mission", "The Team"];
|
||||||
|
// { label: "Our Mission", path: "/our-mission" },
|
||||||
|
// { label: "The Team", path: "/the-team" },
|
||||||
|
// { label: "Contact Us", path: "/contact-us" }]
|
||||||
|
|
||||||
return (
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
<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">
|
const currencies = useStoreState((state) => state.currency.currencies);
|
||||||
<Link href="/" className="rounded-full">
|
const currencyTickers = useStoreState((state) => state.currency.tickers);
|
||||||
<Image height={50} width={50} alt="Logo" className="border border-neutral-300 rounded-full" src="/logo.png" />
|
|
||||||
</Link>
|
return (
|
||||||
</div>
|
<div className="flex sticky top-0 w-full h-14 z-40 font-medium bg-white border border-b-neutral-200">
|
||||||
<div className="flex">
|
<div className="my-1 flex aspect-square ml-3 mr-3">
|
||||||
{navOptions.map((name) => (
|
<Link href="/" className="rounded-full">
|
||||||
<NavbarButton name={name} href={`/${name.toLowerCase()}`} key={name} />
|
<Image height={50} width={50} alt="Logo" className="border border-neutral-300 rounded-full" src="/logo.png" />
|
||||||
))}
|
</Link>
|
||||||
<NavbarButton name="About Us" href="/about" dropdownItems={aboutDropdown} />
|
</div>
|
||||||
</div>
|
<div className="flex">
|
||||||
<div className="flex-grow" />
|
{navOptions.map((name) => (
|
||||||
<button className="my-auto mr-4" onClick={() => setIsModalOpen(true)}>
|
<NavbarButton name={name} href={`/${name.toLowerCase()}`} key={name} />
|
||||||
<FaRegUserCircle size={22} />
|
))}
|
||||||
</button>
|
<NavbarButton name="About Us" href="/about" dropdownItems={aboutDropdown} />
|
||||||
<AuthModal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} />
|
</div>
|
||||||
</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>
|
||||||
|
<AuthModal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +1,42 @@
|
|||||||
import React from 'react';
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
const Sidebar = () => {
|
const Sidebar = () => {
|
||||||
return (
|
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 h-screen w-64 bg-neutral-400 text-white border-l border-neutral-700">
|
||||||
<div className="flex flex-col p-4 border-b border-gray-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>
|
<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
|
Record new earthquakes - time/date, location, magnitude, observatory and scientists
|
||||||
</p>
|
</p>
|
||||||
<button className="mt-4 bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded">
|
<button className="mt-4 bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded">
|
||||||
<Link href="/">
|
<Link href="/">Log Event</Link>
|
||||||
Log Event
|
</button>
|
||||||
</Link>
|
</div>
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Section: Recent Events - Will need to be replaced with a link to the database*/}
|
{/* Section: Recent Events - Will need to be replaced with a link to the database*/}
|
||||||
<div className="flex-1 p-4">
|
<div className="flex-1 p-4">
|
||||||
<h2 className="text-xl font-semibold mb-2">Recent Events</h2>
|
<h2 className="text-xl font-semibold mb-2">Recent Events</h2>
|
||||||
<ul className="space-y-2">
|
<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-sm">Earthquake in California</p>
|
||||||
<p className="text-xs text-gray-300">Magnitude 5.3</p>
|
<p className="text-xs text-neutral-300">Magnitude 5.3</p>
|
||||||
<p className="text-xs text-gray-400">2 hours ago</p>
|
<p className="text-xs text-neutral-400">2 hours ago</p>
|
||||||
</li>
|
</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-sm">Tremor in Japan</p>
|
||||||
<p className="text-xs text-gray-300">Magnitude 4.7</p>
|
<p className="text-xs text-neutral-300">Magnitude 4.7</p>
|
||||||
<p className="text-xs text-gray-400">5 hours ago</p>
|
<p className="text-xs text-neutral-400">5 hours ago</p>
|
||||||
</li>
|
</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-sm">Tremor in Spain</p>
|
||||||
<p className="text-xs text-gray-300">Magnitude 2.1</p>
|
<p className="text-xs text-neutral-300">Magnitude 2.1</p>
|
||||||
<p className="text-xs text-gray-400">10 hours ago</p>
|
<p className="text-xs text-neutral-400">10 hours ago</p>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Sidebar;
|
export default Sidebar;
|
||||||
|
|||||||
@ -1,44 +1,40 @@
|
|||||||
import React from 'react';
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
const Sidebar = () => {
|
const Sidebar = () => {
|
||||||
return (
|
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 h-screen w-64 bg-neutral-400 text-white border-l border-neutral-700">
|
||||||
<div className="flex flex-col p-4 border-b border-gray-700">
|
<div className="flex flex-col p-4 border-b border-neutral-700">
|
||||||
<h2 className="text-xl font-semibold mb-2">Observatories</h2>
|
<h2 className="text-xl font-semibold mb-2">Observatories</h2>
|
||||||
<p className="text-sm text-gray-700">
|
<p className="text-sm text-neutral-700">Observatory events - location, scientists, recent earthquakes</p>
|
||||||
Observatory events - location, scientists, recent earthquakes
|
<button className="mt-4 bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded">
|
||||||
</p>
|
<Link href="/">Observatory News</Link>
|
||||||
<button className="mt-4 bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded">
|
</button>
|
||||||
<Link href="/">
|
</div>
|
||||||
Observatory News
|
|
||||||
</Link>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Section: Recent Events - Will need to be replaced with a link to the database*/}
|
{/* Section: Recent Events - Will need to be replaced with a link to the database*/}
|
||||||
<div className="flex-1 p-4">
|
<div className="flex-1 p-4">
|
||||||
<h2 className="text-xl font-semibold mb-2">Recent Observatory Events</h2>
|
<h2 className="text-xl font-semibold mb-2">Recent Observatory Events</h2>
|
||||||
<ul className="space-y-2">
|
<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-sm">Earthquake in California</p>
|
||||||
<p className="text-xs text-gray-300">Magnitude 5.3</p>
|
<p className="text-xs text-neutral-300">Magnitude 5.3</p>
|
||||||
<p className="text-xs text-gray-400">Cali Observatory</p>
|
<p className="text-xs text-neutral-400">Cali Observatory</p>
|
||||||
</li>
|
</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-sm">Tremor in Japan</p>
|
||||||
<p className="text-xs text-gray-300">Magnitude 4.7</p>
|
<p className="text-xs text-neutral-300">Magnitude 4.7</p>
|
||||||
<p className="text-xs text-gray-400">Kyoto Observatory</p>
|
<p className="text-xs text-neutral-400">Kyoto Observatory</p>
|
||||||
</li>
|
</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-sm">Tremor in Spain</p>
|
||||||
<p className="text-xs text-gray-300">Magnitude 2.1</p>
|
<p className="text-xs text-neutral-300">Magnitude 2.1</p>
|
||||||
<p className="text-xs text-gray-400">Madrid Observatory</p>
|
<p className="text-xs text-neutral-400">Madrid Observatory</p>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Sidebar;
|
export default Sidebar;
|
||||||
|
|||||||
10
src/hooks/store.ts
Normal file
10
src/hooks/store.ts
Normal 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
11
src/types/Artifact.ts
Normal 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
17
src/types/StoreModel.ts
Normal 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 };
|
||||||
@ -20,11 +20,11 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"],
|
"@components/*": ["./src/components/*"],
|
||||||
"@components/*": ["./src/components/*"],
|
"@hooks/*": ["./src/hooks/*"],
|
||||||
"@hooks/*": ["./src/hooks/*"],
|
"@utils/*": ["./src/utils/*"],
|
||||||
"@utils/*": ["./src/utils/*"],
|
"@appTypes/*": ["./src/types/*"],
|
||||||
"@appTypes/*": ["./src/types/*"]
|
"@/*": ["./src/*"],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user