cleaning shop, team, mission and contact pages

This commit is contained in:
Emily Neighbour 2025-05-10 14:05:56 +01:00
parent 0da8b7072e
commit be60b811a9
15 changed files with 404 additions and 368 deletions

BIN
public/artifacts.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

BIN
public/crackedRoad.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 848 KiB

BIN
public/tectonicPlate.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

View File

@ -1,11 +1,11 @@
import bcrypt from "bcrypt"; import bcrypt from 'bcrypt';
import { env } from "@utils/env"; import { SignJWT } from 'jose';
import { NextResponse } from "next/server"; import { NextResponse } from 'next/server';
import { SignJWT } from "jose";
import { PrismaClient } from "@prisma/client"; import { PrismaClient } from '@prisma/client';
import { env } from '@utils/env';
import { findUserByEmail, readUserCsv, User } from "../functions/csvReadWrite"; import { findUserByEmail, readUserCsv, User } from '../functions/csvReadWrite';
const usingPrisma = false; const usingPrisma = false;
let prisma: PrismaClient; let prisma: PrismaClient;
@ -45,12 +45,12 @@ export async function POST(req: Request) {
include: { include: {
earthquakes: true, earthquakes: true,
observatories: true, observatories: true,
artefacts: true, artifacts: true,
superior: true, superior: true,
subordinates: true, subordinates: true,
}, },
}, },
purchasedArtefacts: true, purchasedArtifacts: true,
}, },
}); });

View File

@ -1,15 +1,15 @@
import { NextResponse } from "next/server"; import { NextResponse } from 'next/server';
import { env } from "@utils/env";
import { PrismaClient } from "@prisma/client"; import { PrismaClient } from '@prisma/client';
import { verifyJwt } from "@utils/verifyJwt"; import { env } from '@utils/env';
import { verifyJwt } from '@utils/verifyJwt';
const usingPrisma = false; const usingPrisma = false;
let prisma: PrismaClient; let prisma: PrismaClient;
if (usingPrisma) prisma = new PrismaClient(); if (usingPrisma) prisma = new PrismaClient();
// Artefact type // Artifact type
interface Artefact { interface Artifact {
id: number; id: number;
name: string; name: string;
description: string; description: string;
@ -29,7 +29,7 @@ export async function POST(req: Request) {
if (!token) return NextResponse.json({ message: "Unauthorised" }, { status: 401 }); if (!token) return NextResponse.json({ message: "Unauthorised" }, { status: 401 });
await verifyJwt({ token, secret: env.JWT_SECRET_KEY }); await verifyJwt({ token, secret: env.JWT_SECRET_KEY });
const warehouseArtefacts: Artefact[] = [ const warehouseArtifacts: Artifact[] = [
{ {
id: 1, id: 1,
name: "Solidified Lava Chunk", name: "Solidified Lava Chunk",
@ -87,17 +87,17 @@ export async function POST(req: Request) {
}, },
]; ];
let artefacts; let artifacts;
if (usingPrisma) artefacts = await prisma.artefacts.findMany(); if (usingPrisma) artifacts = await prisma.artifacts.findMany();
if (artefacts) { if (artifacts) {
return NextResponse.json({ message: "Got artefacts successfully", artefacts }, { status: 200 }); return NextResponse.json({ message: "Got artifacts successfully", artifacts }, { status: 200 });
} else { } else {
return NextResponse.json({ message: "Got earthquakes successfully", earthquakes: warehouseArtefacts }, { status: 200 }); return NextResponse.json({ message: "Got earthquakes successfully", earthquakes: warehouseArtifacts }, { status: 200 });
// return NextResponse.json({ message: "Failed to get earthquakes" }, { status: 401 }); // return NextResponse.json({ message: "Failed to get earthquakes" }, { status: 401 });
} }
} catch (error) { } catch (error) {
console.error("Error in artefacts endpoint:", error); console.error("Error in artifacts endpoint:", error);
return NextResponse.json({ message: "Internal Server Error" }, { status: 500 }); return NextResponse.json({ message: "Internal Server Error" }, { status: 500 });
} finally { } finally {
if (usingPrisma) await prisma.$disconnect(); if (usingPrisma) await prisma.$disconnect();

View File

@ -1,6 +1,6 @@
"use client"; "use client";
import Image from "next/image"; import Image from 'next/image';
import React, { useState } from "react"; import React, { useState } from 'react';
const ContactUs = () => { const ContactUs = () => {
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
@ -36,8 +36,8 @@ const ContactUs = () => {
<div className="max-w-4xl mx-auto p-5 mt-20"> <div className="max-w-4xl mx-auto p-5 mt-20">
{/* Header */} {/* Header */}
<h1 className="text-4xl font-bold text-center text-neutral-50 mb-6">Contact Us</h1> <h1 className="text-4xl font-bold text-center text-neutral-50 mb-6">Contact Us</h1>
<p className="text-lg text-center text-neutral-200 mb-6"> <p className="text-lg text-center max-w-2xl text-neutral-200 mb-6">
Have questions or concerns about earthquake preparedness? Contact us using the form below or through the provided Have questions or concerns about earthquakes, observatories or artifacts? Contact us via phone, email, social media or using the form below with the relevant
contact details. contact details.
</p> </p>
@ -106,20 +106,20 @@ const ContactUs = () => {
{/* Contact Details Section */} {/* Contact Details Section */}
<div className="w-[45%] bg-white bg-opacity-90 text-neutral-800 rounded-lg shadow-lg p-6"> <div className="w-[45%] bg-white bg-opacity-90 text-neutral-800 rounded-lg shadow-lg p-6">
<h2 className="text-xl font-bold text-neutral-800 mb-4">Get in Touch</h2> <h2 className="text-2xl font-bold text-neutral-800 mb-4">Get in Touch</h2>
<div className="mb-4"> <div className="mb-4">
<h3 className="text-neutral-700 font-medium">Email</h3> <h3 className="text-neutral-700 font-bold font-large">Email</h3>
<a href="mailto:getintouch@tremortracker.com" style={{ color: "initial" }}> <a href="Mail to:getintouch@tremortracker.com font-medium" style={{ color: "initial" }}>
getintouch@tremortracker.com getintouch@tremortracker.com
</a> </a>
</div> </div>
<div className="mb-4"> <div className="mb-4">
<h3 className="text-neutral-700 font-medium">Phone</h3> <h3 className="text-neutral-700 font-bold font-large">Phone</h3>
<p className="text-neutral-600">+44 7538 359022</p> <p className="text-neutral-600 font-medium">+44 7538 359022</p>
</div> </div>
<div className="mb-4"> <div className="mb-4">
<h3 className="text-neutral-700 font-medium">Address</h3> <h3 className="text-neutral-700 font-bold font-large">Address</h3>
<p className="text-neutral-600">1 Swentown Row, Greenwich, London, SE3 0FQ</p> <p className="text-neutral-600 font-medium">1 Swentown Row, Greenwich, London, SE3 0FQ</p>
</div> </div>
<h2 className="text-xl font-bold text-neutral-800 mb-4 mt-6">Follow Us</h2> <h2 className="text-xl font-bold text-neutral-800 mb-4 mt-6">Follow Us</h2>
<div className="flex justify-around items-center"> <div className="flex justify-around items-center">

View File

@ -1,12 +1,12 @@
"use client"; "use client";
import type { Metadata } from "next"; import type { Metadata } from "next";
import "./globals.css"; import './globals.css';
import { action, createStore, StoreProvider } from "easy-peasy"; import { action, createStore, StoreProvider } from 'easy-peasy';
import { Inter } from "next/font/google"; import { Inter } from 'next/font/google';
import { StoreModel } from "@appTypes/StoreModel"; import { StoreModel } from '@appTypes/StoreModel';
import Navbar from "@components/Navbar"; import Navbar from '@components/Navbar';
const inter = Inter({ const inter = Inter({
subsets: ["latin"], subsets: ["latin"],
@ -32,7 +32,7 @@ const store = createStore<StoreModel>({
// name: "Tim Howitz", // name: "Tim Howitz",
// role: "ADMIN", // role: "ADMIN",
// scientist: undefined, // scientist: undefined,
// purchasedArtefacts: [], // purchasedArtifacts: [],
// }, // },
}); });

View File

@ -1,52 +1,61 @@
"use client"; "use client";
import Image from "next/image"; import Image from 'next/image';
const OurMission = () => { const OurMission = () => {
return ( return (
<div <div
className="min-h-screen relative bg-fixed bg-cover bg-center text-white py-28 px-4" className="min-h-screen relative bg-fixed bg-cover bg-center text-white"
style={{ backgroundImage: "url('destruction.jpg')" }} style={{ backgroundImage: "url('destruction.jpg')" }}
> >
{/* Overlay for Readability */} {/* Overlay for Readability */}
<div className="absolute inset-0 bg-black bg-opacity-50"></div> <div className="absolute inset-0 bg-black bg-opacity-50"></div>
{/* Centered content */}
{/* Content Area */} <div className="relative z-20 flex flex-col items-center justify-center min-h-screen">
<div className="relative z-10 max-w-5xl mx-auto p-8 bg-white bg-opacity-95 shadow-xl rounded-3xl"> {/* Title & Mission Statement */}
<h1 className="text-4xl font-extrabold text-center text-neutral-800 mb-8 tracking-tight">Our Mission</h1> <div className="mb-10 flex flex-col items-center">
<p className="text-xl text-neutral-600 leading-relaxed mb-6 max-w-3xl mx-auto"> <h1 className="text-4xl font-bold text-center tracking-tight mb-2 drop-shadow-lg">
At <span className="font-semibold text-blue-600">Tremor Tracker</span>, we empower communities worldwide to prepare for Our Mission
and recover from earthquakes through education, cutting-edge research, and innovative technology. </h1>
</p> <p className="text-lg text-center max-w-2xl text-white drop-shadow-md">
<p className="text-xl text-neutral-600 leading-relaxed mb-8 max-w-3xl mx-auto"> Earthquake awareness accessible for everyone
We bridge scientific insights with public awareness, delivering resources, tools, and real-time updates to enhance </p>
preparedness, save lives, and build resilience against seismic events. </div>
</p> {/* Content Area */}
<div className="flex flex-col md:flex-row md:justify-evenly gap-8 mt-8"> <div className="max-w-5xl w-full p-8 bg-white bg-opacity-90 shadow-xl rounded-3xl">
<div className="flex flex-col items-center p-6 hover:bg-neutral-50 rounded-xl transition-colors duration-300"> <p className="text-xl text-black leading-relaxed mb-6 max-w-3xl mx-auto">
<Image height={100} width={100} src="/education.png" alt="Education Icon" className="h-20 w-20 mb-4" /> At <span className="font-bold text-black">Tremor Tracker</span>, we empower communities worldwide to understand where and why earthquakes occur to enable better preparation
<h3 className="text-xl font-bold text-neutral-700 mb-2">Education</h3> and recovery. Education, cutting-edge research, and innovative technology combine together.
<p className="text-sm text-neutral-500 text-center max-w-xs"> </p>
Delivering accessible resources to educate communities on earthquake preparedness. <p className="text-xl text-black leading-relaxed mb-8 max-w-3xl mx-auto">
</p> We combine scientific insights with public awareness, delivering resources, tools, and real-time updates to enhance
</div> preparedness for earthquakes in order to save lives, improve recovery efficiency, and build resilience against seismic events.
<div className="flex flex-col items-center p-6 hover:bg-neutral-50 rounded-xl transition-colors duration-300"> </p>
<Image height={100} width={100} src="/research.jpg" alt="Research Icon" className="h-20 w-20 mb-4" /> <div className="flex flex-col md:flex-row md:justify-evenly gap-8 mt-8">
<h3 className="text-xl font-bold text-neutral-700 mb-2">Research</h3> <div className="flex flex-col items-center p-6 hover:bg-white hover:bg-opacity-10 rounded-xl transition-colors duration-300">
<p className="text-sm text-neutral-500 text-center max-w-xs"> <Image height={100} width={100} src="/education.png" alt="Education Icon" className="h-20 w-20 mb-4" />
Advancing scientific studies to deepen understanding of seismic activity. <h3 className="text-xl font-bold text-black mb-2">Education</h3>
</p> <p className="text-sm text-black text-center max-w-xs opacity-90">
</div> Delivering accessible resources to educate communities on earthquakes.
<div className="flex flex-col items-center p-6 hover:bg-neutral-50 rounded-xl transition-colors duration-300"> </p>
<Image height={100} width={100} src="/tech.jpg" alt="Technology Icon" className="h-20 w-20 mb-4" /> </div>
<h3 className="text-xl font-bold text-neutral-700 mb-2">Technology</h3> <div className="flex flex-col items-center p-6 hover:bg-white hover:bg-opacity-10 rounded-xl transition-colors duration-300">
<p className="text-sm text-neutral-500 text-center max-w-xs"> <Image height={100} width={100} src="/research.jpg" alt="Research Icon" className="h-20 w-20 mb-4" />
Harnessing innovation for real-time alerts and safety solutions. <h3 className="text-xl font-bold text-black mb-2">Research</h3>
</p> <p className="text-sm text-black text-center max-w-xs opacity-90">
</div> Advancing scientific studies to deepen understanding of seismic activity.
</div> </p>
</div> </div>
</div> <div className="flex flex-col items-center p-6 hover:bg-white hover:bg-opacity-10 rounded-xl transition-colors duration-300">
); <Image height={100} width={100} src="/tech.jpg" alt="Technology Icon" className="h-20 w-20 mb-4" />
<h3 className="text-xl font-bold text-black mb-2">Technology</h3>
<p className="text-sm text-black text-center max-w-xs opacity-90">
Harnessing innovation for real-time alerts and safety solutions.
</p>
</div>
</div>
</div>
</div>
</div>
);
}; };
export default OurMission; export default OurMission;

View File

@ -1,20 +1,19 @@
"use client"; "use client";
import Image from "next/image"; import Image from 'next/image';
import { Dispatch, SetStateAction, useCallback, useState } from "react"; import { Dispatch, SetStateAction, useCallback, useState } from 'react';
import Artefact from "@appTypes/Artefact"; import Artifact from '@appTypes/Artifact';
import { Currency } from "@appTypes/StoreModel"; import { Currency } from '@appTypes/StoreModel';
import Sidebar from "@components/Sidebar"; import { useStoreState } from '@hooks/store';
import { useStoreState } from "@hooks/store";
// Artefacts Data // Artifacts Data
const artefacts: Artefact[] = [ const artifacts: Artifact[] = [
{ {
id: 1, id: 1,
name: "Golden Scarab", name: "Golden Scarab",
description: "An ancient Egyptian artefact symbolizing rebirth.", description: "An ancient Egyptian artifact symbolizing rebirth.",
location: "Cairo, Egypt", location: "Cairo, Egypt",
image: "/artefact1.jpg", image: "/artifact1.jpg",
price: 150, price: 150,
}, },
{ {
@ -22,7 +21,7 @@ const artefacts: Artefact[] = [
name: "Aztec Sunstone", name: "Aztec Sunstone",
description: "A replica of the Aztec calendar (inscriptions intact).", description: "A replica of the Aztec calendar (inscriptions intact).",
location: "Peru", location: "Peru",
image: "/artefact2.jpg", image: "/artifact2.jpg",
price: 200, price: 200,
}, },
{ {
@ -30,7 +29,7 @@ const artefacts: Artefact[] = [
name: "Medieval Chalice", name: "Medieval Chalice",
description: "Used by royalty in medieval ceremonies.", description: "Used by royalty in medieval ceremonies.",
location: "Cambridge, England", location: "Cambridge, England",
image: "/artefact3.jpg", image: "/artifact3.jpg",
price: 120, price: 120,
}, },
{ {
@ -38,7 +37,7 @@ const artefacts: Artefact[] = [
name: "Roman Coin", name: "Roman Coin",
description: "An authentic Roman coin from the 2nd century CE.", description: "An authentic Roman coin from the 2nd century CE.",
location: "Rome, Italy", location: "Rome, Italy",
image: "/artefact4.jpg", image: "/artifact4.jpg",
price: 80, price: 80,
}, },
{ {
@ -46,7 +45,7 @@ const artefacts: Artefact[] = [
name: "Samurai Mask", name: "Samurai Mask",
description: "Replica of Japanese Samurai battle masks.", description: "Replica of Japanese Samurai battle masks.",
location: "Tokyo, Japan", location: "Tokyo, Japan",
image: "/artefact5.jpg", image: "/artifact5.jpg",
price: 300, price: 300,
}, },
{ {
@ -54,7 +53,7 @@ const artefacts: Artefact[] = [
name: "Ancient Greek Vase", name: "Ancient Greek Vase",
description: "Depicts Greek mythology, found in the Acropolis.", description: "Depicts Greek mythology, found in the Acropolis.",
location: "Athens, Greece", location: "Athens, Greece",
image: "/artefact6.jpg", image: "/artifact6.jpg",
price: 250, price: 250,
}, },
{ {
@ -62,7 +61,7 @@ const artefacts: Artefact[] = [
name: "Incan Pendant", name: "Incan Pendant",
description: "Represents the Sun God Inti.", description: "Represents the Sun God Inti.",
location: "India", location: "India",
image: "/artefact7.jpg", image: "/artifact7.jpg",
price: 175, price: 175,
}, },
{ {
@ -70,7 +69,7 @@ const artefacts: Artefact[] = [
name: "Persian Carpet Fragment", name: "Persian Carpet Fragment",
description: "Ancient Persian artistry.", description: "Ancient Persian artistry.",
location: "Petra, Jordan", location: "Petra, Jordan",
image: "/artefact8.jpg", image: "/artifact8.jpg",
price: 400, price: 400,
}, },
{ {
@ -78,7 +77,7 @@ const artefacts: Artefact[] = [
name: "Stone Buddha", name: "Stone Buddha",
description: "Authentic stone Buddha carving.", description: "Authentic stone Buddha carving.",
location: "India", location: "India",
image: "/artefact9.jpg", image: "/artifact9.jpg",
price: 220, price: 220,
}, },
{ {
@ -86,7 +85,7 @@ const artefacts: Artefact[] = [
name: "Victorian Brooch", name: "Victorian Brooch",
description: "A beautiful Victorian-era brooch with a ruby centre.", description: "A beautiful Victorian-era brooch with a ruby centre.",
location: "Oxford, England", location: "Oxford, England",
image: "/artefact10.jpg", image: "/artifact10.jpg",
price: 150, price: 150,
}, },
{ {
@ -94,7 +93,7 @@ const artefacts: Artefact[] = [
name: "Ancient Scroll", name: "Ancient Scroll",
description: "A mysterious scroll from ancient times.", description: "A mysterious scroll from ancient times.",
location: "Madrid, Spain", location: "Madrid, Spain",
image: "/artefact11.jpg", image: "/artifact11.jpg",
price: 500, price: 500,
}, },
{ {
@ -102,7 +101,7 @@ const artefacts: Artefact[] = [
name: "Ming Dynasty Porcelain", name: "Ming Dynasty Porcelain",
description: "Porcelain from China's Ming Dynasty.", description: "Porcelain from China's Ming Dynasty.",
location: "Beijing, China", location: "Beijing, China",
image: "/artefact12.jpg", image: "/artifact12.jpg",
price: 300, price: 300,
}, },
{ {
@ -110,15 +109,15 @@ const artefacts: Artefact[] = [
name: "African Tribal Mask", name: "African Tribal Mask",
description: "A unique tribal mask from Africa.", description: "A unique tribal mask from Africa.",
location: "Nigeria", location: "Nigeria",
image: "/artefact13.jpg", image: "/artifact13.jpg",
price: 250, price: 250,
}, },
{ {
id: 14, id: 14,
name: "Crystal Skull", name: "Crystal Skull",
description: "A mystical pre-Columbian artefact.", description: "A mystical pre-Columbian artifact.",
location: "Colombia", location: "Colombia",
image: "/artefact14.jpg", image: "/artifact14.jpg",
price: 1000, price: 1000,
}, },
{ {
@ -126,137 +125,159 @@ const artefacts: Artefact[] = [
name: "Medieval Armor Fragment", name: "Medieval Armor Fragment",
description: "A fragment of medieval armor.", description: "A fragment of medieval armor.",
location: "Normandy, France", location: "Normandy, France",
image: "/artefact15.jpg", image: "/artifact15.jpg",
price: 400, price: 400,
}, },
{
id: 16,
name: "Medieval Helmet Fragment",
description: "A fragment of a medieval helmet.",
location: "Normandy, France",
image: "/artifact16.jpg",
price: 500,
},
]; ];
// Modal Component
// Shop Component
export default function Shop() { export default function Shop() {
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [selectedArtefact, setSelectedArtefact] = useState<Artefact | null>(null); // Track selected artefact for modal const [selectedArtifact, setSelectedArtifact] = useState<Artifact | null>(null);
const artefactsPerPage = 9; // Number of artefacts per page const artifactsPerPage = 12;
const indexOfLastArtefact = currentPage * artefactsPerPage; const indexOfLastArtifact = currentPage * artifactsPerPage;
const indexOfFirstArtefact = indexOfLastArtefact - artefactsPerPage; const indexOfFirstArtifact = indexOfLastArtifact - artifactsPerPage;
const currentArtefacts = artefacts.slice(indexOfFirstArtefact, indexOfLastArtefact); const currentArtifacts = artifacts.slice(indexOfFirstArtifact, indexOfLastArtifact);
const selectedCurrency = useStoreState((state) => state.currency.selectedCurrency); const selectedCurrency = useStoreState((state) => state.currency.selectedCurrency);
const conversionRates = useStoreState((state) => state.currency.conversionRates); const conversionRates = useStoreState((state) => state.currency.conversionRates);
const currencyTickers = useStoreState((state) => state.currency.tickers); const currencyTickers = useStoreState((state) => state.currency.tickers);
const convertPrice = useCallback((price: number, currency: Currency) => (price * conversionRates[currency]).toFixed(2), []);
const handleNextPage = () => { const convertPrice = useCallback(
if (indexOfLastArtefact < artefacts.length) { (price: number, currency: Currency) => (price * conversionRates[currency]).toFixed(2),
setCurrentPage((prev) => prev + 1); []
} );
};
const handlePreviousPage = () => { const handleNextPage = () => {
if (currentPage > 1) { if (indexOfLastArtifact < artifacts.length) {
setCurrentPage((prev) => prev - 1); setCurrentPage((prev) => prev + 1);
} }
}; };
const handlePreviousPage = () => {
if (currentPage > 1) {
setCurrentPage((prev) => prev - 1);
}
};
function Modal({ artefact }: { artefact: Artefact }) { function Modal({ artifact }: { artifact: Artifact }) {
if (!artefact) return null; if (!artifact) return null;
const handleOverlayClick = (e: { target: any; currentTarget: any }) => {
if (e.target === e.currentTarget) {
setSelectedArtifact(null);
}
};
return (
<div
className="fixed inset-0 bg-neutral-900 bg-opacity-50 flex justify-center items-center z-50"
onClick={handleOverlayClick}
>
<div className="bg-white rounded-xl shadow-2xl max-w-lg w-full p-6">
<h3 className="text-2xl font-bold mb-4">{artifact.name}</h3>
<Image
height={5000}
width={5000}
src={artifact.image}
alt={artifact.name}
className="w-full h-64 object-cover rounded-md"
/>
<p className="text-xl font-bold">
{currencyTickers[selectedCurrency]}
{convertPrice(artifact.price, selectedCurrency)}
</p>
<p className="text-neutral-600 mt-2">{artifact.description}</p>
<p className="text-neutral-500 font-bold mt-1">Location: {artifact.location}</p>
<div className="flex justify-end gap-4 mt-4 mr-2">
<button
onClick={() => alert("Purchased Successfully!")}
className="px-10 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"
>
Buy
</button>
</div>
</div>
</div>
);
}
const handleOverlayClick = (e: { target: any; currentTarget: any }) => { function ArtifactCard({ artifact }: { artifact: Artifact }) {
if (e.target === e.currentTarget) { return (
setSelectedArtefact(null); <div
} className="flex flex-col bg-white shadow-md rounded-md overflow-hidden cursor-pointer hover:scale-105 transition-transform"
}; onClick={() => setSelectedArtifact(artifact)}
>
<img src={artifact.image} alt={artifact.name} className="w-full h-56 object-cover" />
<div className="p-4">
<h3 className="text-lg font-semibold">{artifact.name}</h3>
<p className="text-neutral-500 mb-2">{artifact.location}</p>
<p className="text-black font-bold text-md mt-2">
{currencyTickers[selectedCurrency]}
{convertPrice(artifact.price, selectedCurrency)}
</p>
</div>
</div>
);
}
return ( return (
<div <div
className="fixed inset-0 bg-neutral-900 bg-opacity-50 flex justify-center items-center z-50" className="min-h-screen relative flex flex-col"
onClick={handleOverlayClick} style={{
> backgroundImage: "url('/artifacts.jpg')",
<div className="bg-white rounded-xl shadow-2xl max-w-lg w-full p-6"> backgroundSize: 'cover',
<h3 className="text-2xl font-bold mb-4">{artefact.name}</h3> backgroundPosition: 'center'
<Image }}
height={5000} >
width={5000} {/* Overlay */}
src={artefact.image} <div className="absolute inset-0 bg-black bg-opacity-50 z-0"></div>
alt={artefact.name} <div className="relative z-10 flex flex-col items-center w-full px-2 py-12">
className="w-full h-64 object-cover rounded-md" {/* Title & Subheading */}
></Image> <h1 className="text-4xl md:text-4xl font-bold text-center text-white mb-2 tracking-tight drop-shadow-lg">
<p className="text-xl font-bold"> Artifact Shop
{currencyTickers[selectedCurrency]} </h1>
{convertPrice(artefact.price, selectedCurrency)} <p className="text-lg md:text-xl text-center text-white mb-10 drop-shadow-md max-w-2xl">
</p> Discover extraordinary historical artifacts and collectibles from major seismic events from around the world - now available for purchase.
<p className="text-neutral-600 mt-2">{artefact.description}</p> </p>
<p className="text-neutral-500 font-bold mt-1">Location: {artefact.location}</p>
<div className="flex justify-end gap-4 mt-4 mr-2"> {/* Artifact Grid */}
<button <div className="w-full max-w-7xl grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-10 p-2"> {/* gap-10 for more spacing */}
onClick={() => alert("Purchased Successfully!")} {currentArtifacts.map((artifact) => (
className="px-10 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700" <ArtifactCard key={artifact.id} artifact={artifact} />
> ))}
Buy </div>
</button>
</div>
</div>
</div>
);
}
// ArtefactCard Component {/* Pagination Footer */}
function ArtefactCard({ artefact }: { artefact: Artefact }) { <footer className="mt-10 bg-white bg-opacity-90 border-neutral-300 py-3 text-center flex justify-center items-center w-100 max-w-7xl rounded-lg">
return ( <button
<div onClick={handlePreviousPage}
className="flex flex-col bg-white shadow-md rounded-md overflow-hidden cursor-pointer hover:scale-105 transition-transform" disabled={currentPage === 1}
onClick={() => setSelectedArtefact(artefact)} // Opens modal className={`mx-2 px-4 py-1 bg-blue-500 text-white rounded-md font-bold shadow-md ${
> currentPage === 1 ? "opacity-50 cursor-not-allowed" : "hover:bg-blue-600"
<img src={artefact.image} alt={artefact.name} className="w-full h-56 object-cover" /> }`}
<div className="p-4"> >
<h3 className="text-lg font-semibold">{artefact.name}</h3> &larr; Previous
<p className="text-neutral-500 mb-2">{artefact.location}</p> </button>
<p className="text-black font-bold text-md mt-2"> <p className="mx-3 text-lg font-bold">{currentPage}</p>
{currencyTickers[selectedCurrency]} <button
{convertPrice(artefact.price, selectedCurrency)} onClick={handleNextPage}
</p> disabled={indexOfLastArtifact >= artifacts.length}
</div> className={`mx-2 px-4 py-1 bg-blue-500 text-white rounded-md font-bold shadow-md ${
</div> indexOfLastArtifact >= artifacts.length ? "opacity-50 cursor-not-allowed" : "hover:bg-blue-600"
); }`}
} >
Next &rarr;
</button>
</footer>
</div>
return ( {/* Modal */}
<div className="flex flex-col min-h-screen bg-neutral-100"> {selectedArtifact && <Modal artifact={selectedArtifact} />}
{/* Main Content */} </div>
<div className="flex flex-1 overflow-y-auto"> );
{/* Artefact Grid */}
<div className="flex-grow grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 p-6">
{currentArtefacts.map((artefact) => (
<ArtefactCard key={artefact.id} artefact={artefact} />
))}
</div>
</div>
{/* Pagination Footer */}
<footer className="mt-10 bg-white border-t border-neutral-300 py-3 text-center flex justify-center items-center">
<button
onClick={handlePreviousPage}
disabled={currentPage === 1}
className={`mx-2 px-4 py-1 bg-blue-500 text-white rounded-md font-bold shadow-md ${
currentPage === 1 ? "opacity-50 cursor-not-allowed" : "hover:bg-blue-600"
}`}
>
&larr; Previous
</button>
<p className="mx-3 text-lg font-bold">{currentPage}</p>
<button
onClick={handleNextPage}
disabled={indexOfLastArtefact >= artefacts.length}
className={`mx-2 px-4 py-1 bg-blue-500 text-white rounded-md font-bold shadow-md ${
indexOfLastArtefact >= artefacts.length ? "opacity-50 cursor-not-allowed" : "hover:bg-blue-600"
}`}
>
Next &rarr;
</button>
</footer>
{/* Modal */}
{selectedArtefact && <Modal artefact={selectedArtefact} />}
</div>
);
} }

View File

@ -1,5 +1,4 @@
"use client"; "use client";
const teamMembers = [ const teamMembers = [
{ {
name: "Tim Howitz", name: "Tim Howitz",
@ -30,41 +29,47 @@ const teamMembers = [
image: "/Lukeshanthescientist.PNG", image: "/Lukeshanthescientist.PNG",
}, },
]; ];
export default function Page() { export default function Page() {
return ( return (
<div className="h-full text-center bg-gradient-to-b from-neutral-50 to-neutral-100 pt-14 px-4"> <div
{/* Header */} className="min-h-screen relative flex flex-col items-center justify-center px-4 py-20"
<h1 className="text-5xl font-extrabold text-neutral-800 mb-6 tracking-tight">Meet Our Team</h1> style={{ backgroundImage: "url('tectonicPlate.jpg')", backgroundSize: "cover", backgroundPosition: "center" }}
<p className="text-xl text-neutral-600 mb-12 max-w-2xl mx-auto"> >
Our world-class scientists drive innovation across the globe. Meet our department heads below. {/* Overlay */}
</p> <div className="absolute inset-0 bg-black bg-opacity-50 pointer-events-none"></div>
{/* Header */}
{/* Team Members Section */} <div className="relative z-10 flex flex-col items-center mb-1">
<div className="flex flex-col items-center gap-6"> <h1 className="text-4xl font-bold text-center text-white mb-4 tracking-tight drop-shadow-lg">
{teamMembers.map((member, index) => ( Meet Our Team
<div </h1>
key={index} <p className="text-lg text-center max-w-2xl text-white mb-12 drop-shadow-md">
className="flex bg-white rounded-3xl border border-neutral-200 w-full max-w-[50%] shadow-md hover:shadow-lg transition-shadow duration-300" Our world-class scientists and engineers drive innovation across the globe. Meet our four department heads:
> </p>
{/* Image */} </div>
<div className="flex items-center ml-6"> {/* Team Members Section */}
<div className="relative w-20 h-20"> <div className="relative z-10 flex flex-col items-center gap-6 w-full max-w-3xl">
<div className="absolute inset-0 rounded-full overflow-hidden ring-4 ring-neutral-100"> {teamMembers.map((member, index) => (
<img src={member.image} alt={member.name} className="h-full w-full object-cover" /> <div
</div> key={index}
</div> className="flex bg-white bg-opacity-90 rounded-3xl border border-neutral-200 w-full shadow-md hover:shadow-lg transition-shadow duration-300"
</div> >
{/* Image */}
{/* Text Content */} <div className="flex items-center ml-6">
<div className="flex flex-col items-start pl-8 py-4 pr-6"> <div className="relative w-20 h-20">
<h2 className="text-2xl font-bold text-neutral-800">{member.name}</h2> <div className="absolute inset-0 rounded-full overflow-hidden ring-4 ring-neutral-100">
<p className="text-md text-neutral-500 font-semibold">{member.title}</p> <img src={member.image} alt={member.name} className="h-full w-full object-cover" />
<p className="text-neutral-600 mt-3 text-left text-sm leading-relaxed">{member.description}</p> </div>
</div> </div>
</div> </div>
))} {/* Text Content */}
</div> <div className="flex flex-col items-start pl-8 py-4 pr-6">
</div> <h2 className="text-2xl font-bold text-neutral-800">{member.name}</h2>
); <p className="text-md text-neutral-500 font-semibold">{member.title}</p>
<p className="text-neutral-600 mt-3 text-left text-sm leading-relaxed">{member.description}</p>
</div>
</div>
))}
</div>
</div>
);
} }

View File

@ -1,12 +1,12 @@
"use client"; "use client";
import { useState, useMemo } from "react"; import { Dispatch, SetStateAction, useMemo, useState } from 'react';
import { FaCalendarPlus, FaWarehouse, FaCartShopping } from "react-icons/fa6"; import { FaTimes } from 'react-icons/fa';
import { IoFilter, IoFilterCircleOutline, IoFilterOutline, IoToday } from "react-icons/io5"; import { FaCalendarPlus, FaCartShopping, FaWarehouse } from 'react-icons/fa6';
import { FaTimes } from "react-icons/fa"; import { IoFilter, IoFilterCircleOutline, IoFilterOutline, IoToday } from 'react-icons/io5';
import { SetStateAction, Dispatch } from "react";
// import type { Artefact } from "@prisma/client";
interface Artefact { // import type { Artifact } from "@prisma/client";
interface Artifact {
id: number; id: number;
name: string; name: string;
description: string; description: string;
@ -18,8 +18,8 @@ interface Artefact {
dateAdded: string; dateAdded: string;
} }
// Warehouse Artefacts Data // Warehouse Artifacts Data
const warehouseArtefacts: Artefact[] = [ const warehouseArtifacts: Artifact[] = [
{ {
id: 1, id: 1,
name: "Solidified Lava Chunk", name: "Solidified Lava Chunk",
@ -140,14 +140,14 @@ function FilterInput({
} }
// Table Component // Table Component
function ArtefactTable({ function ArtifactTable({
artefacts, artifacts,
filters, filters,
setFilters, setFilters,
setEditArtefact, setEditArtifact,
clearSort, clearSort,
}: { }: {
artefacts: Artefact[]; artifacts: Artifact[];
filters: Record<string, string>; filters: Record<string, string>;
setFilters: Dispatch< setFilters: Dispatch<
SetStateAction<{ SetStateAction<{
@ -162,15 +162,15 @@ function ArtefactTable({
dateAdded: string; dateAdded: string;
}> }>
>; >;
setEditArtefact: (artefact: Artefact) => void; setEditArtifact: (artifact: Artifact) => void;
clearSort: () => void; clearSort: () => void;
}) { }) {
const [sortConfig, setSortConfig] = useState<{ const [sortConfig, setSortConfig] = useState<{
key: keyof Artefact; key: keyof Artifact;
direction: "asc" | "desc"; direction: "asc" | "desc";
} | null>(null); } | null>(null);
const handleSort = (key: keyof Artefact) => { const handleSort = (key: keyof Artifact) => {
setSortConfig((prev) => { setSortConfig((prev) => {
if (!prev || prev.key !== key) { if (!prev || prev.key !== key) {
return { key, direction: "asc" }; return { key, direction: "asc" };
@ -186,9 +186,9 @@ function ArtefactTable({
clearSort(); clearSort();
}; };
const sortedArtefacts = useMemo(() => { const sortedArtifacts = useMemo(() => {
if (!sortConfig) return artefacts; if (!sortConfig) return artifacts;
const sorted = [...artefacts].sort((a, b) => { const sorted = [...artifacts].sort((a, b) => {
const aValue = a[sortConfig.key]; const aValue = a[sortConfig.key];
const bValue = b[sortConfig.key]; const bValue = b[sortConfig.key];
if (aValue < bValue) return sortConfig.direction === "asc" ? -1 : 1; if (aValue < bValue) return sortConfig.direction === "asc" ? -1 : 1;
@ -196,9 +196,9 @@ function ArtefactTable({
return 0; return 0;
}); });
return sorted; return sorted;
}, [artefacts, sortConfig]); }, [artifacts, sortConfig]);
const columns: { label: string; key: keyof Artefact; width: string }[] = [ const columns: { label: string; key: keyof Artifact; width: string }[] = [
{ label: "ID", key: "id", width: "5%" }, { label: "ID", key: "id", width: "5%" },
{ label: "Name", key: "name", width: "12%" }, { label: "Name", key: "name", width: "12%" },
{ label: "Earthquake ID", key: "earthquakeId", width: "10%" }, { label: "Earthquake ID", key: "earthquakeId", width: "10%" },
@ -217,7 +217,7 @@ function ArtefactTable({
{columns.map(({ label, key, width }) => ( {columns.map(({ label, key, width }) => (
<th key={key} className="text-sm px-5 font-semibold text-neutral-800 cursor-pointer" style={{ width }}> <th key={key} className="text-sm px-5 font-semibold text-neutral-800 cursor-pointer" style={{ width }}>
<div className="flex h-11 items-center"> <div className="flex h-11 items-center">
<div className="flex h-full items-center" onClick={() => handleSort(key as keyof Artefact)}> <div className="flex h-full items-center" onClick={() => handleSort(key as keyof Artifact)}>
<div className="select-none">{label}</div> <div className="select-none">{label}</div>
</div> </div>
<div className="h-full relative"> <div className="h-full relative">
@ -250,11 +250,11 @@ function ArtefactTable({
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{sortedArtefacts.map((artefact) => ( {sortedArtifacts.map((artifact) => (
<tr <tr
key={artefact.id} key={artifact.id}
className="border-b border-neutral-200 hover:bg-neutral-100 cursor-pointer" className="border-b border-neutral-200 hover:bg-neutral-100 cursor-pointer"
onClick={() => setEditArtefact(artefact)} onClick={() => setEditArtifact(artifact)}
> >
{columns.map(({ key, width }) => ( {columns.map(({ key, width }) => (
<td <td
@ -263,18 +263,18 @@ function ArtefactTable({
style={{ width }} style={{ width }}
> >
{key === "isRequired" {key === "isRequired"
? artefact.isRequired ? artifact.isRequired
? "Yes" ? "Yes"
: "No" : "No"
: key === "isSold" : key === "isSold"
? artefact.isSold ? artifact.isSold
? "Yes" ? "Yes"
: "No" : "No"
: key === "isCollected" : key === "isCollected"
? artefact.isCollected ? artifact.isCollected
? "Yes" ? "Yes"
: "No" : "No"
: artefact[key]} : artifact[key]}
</td> </td>
))} ))}
</tr> </tr>
@ -284,7 +284,7 @@ function ArtefactTable({
); );
} }
// Modal Component for Logging Artefact // Modal Component for Logging Artifact
function LogModal({ onClose }: { onClose: () => void }) { function LogModal({ onClose }: { onClose: () => void }) {
const [name, setName] = useState(""); const [name, setName] = useState("");
const [description, setDescription] = useState(""); const [description, setDescription] = useState("");
@ -312,7 +312,7 @@ function LogModal({ onClose }: { onClose: () => void }) {
alert(`Logged ${name} to storage: ${storageLocation}`); alert(`Logged ${name} to storage: ${storageLocation}`);
onClose(); onClose();
} catch { } catch {
setError("Failed to log artefact. Please try again."); setError("Failed to log artifact. Please try again.");
} finally { } finally {
setIsSubmitting(false); setIsSubmitting(false);
} }
@ -324,7 +324,7 @@ function LogModal({ onClose }: { onClose: () => void }) {
onClick={handleOverlayClick} onClick={handleOverlayClick}
> >
<div className="bg-white rounded-lg shadow-xl max-w-md w-full p-6 border border-neutral-300"> <div className="bg-white rounded-lg shadow-xl max-w-md w-full p-6 border border-neutral-300">
<h3 className="text-lg font-semibold mb-4 text-neutral-800">Log New Artefact</h3> <h3 className="text-lg font-semibold mb-4 text-neutral-800">Log New Artifact</h3>
{error && <p className="text-red-600 text-sm mb-2">{error}</p>} {error && <p className="text-red-600 text-sm mb-2">{error}</p>}
<div className="space-y-2"> <div className="space-y-2">
<input <input
@ -333,7 +333,7 @@ function LogModal({ onClose }: { onClose: () => void }) {
value={name} value={name}
onChange={(e) => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500" className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500"
aria-label="Artefact Name" aria-label="Artifact Name"
disabled={isSubmitting} disabled={isSubmitting}
/> />
<textarea <textarea
@ -341,7 +341,7 @@ function LogModal({ onClose }: { onClose: () => void }) {
value={description} value={description}
onChange={(e) => setDescription(e.target.value)} onChange={(e) => setDescription(e.target.value)}
className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500 h-16" className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500 h-16"
aria-label="Artefact Description" aria-label="Artifact Description"
disabled={isSubmitting} disabled={isSubmitting}
/> />
<input <input
@ -350,7 +350,7 @@ function LogModal({ onClose }: { onClose: () => void }) {
value={location} value={location}
onChange={(e) => setLocation(e.target.value)} onChange={(e) => setLocation(e.target.value)}
className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500" className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500"
aria-label="Artefact Location" aria-label="Artifact Location"
disabled={isSubmitting} disabled={isSubmitting}
/> />
<input <input
@ -377,7 +377,7 @@ function LogModal({ onClose }: { onClose: () => void }) {
checked={isRequired} checked={isRequired}
onChange={(e) => setIsRequired(e.target.checked)} onChange={(e) => setIsRequired(e.target.checked)}
className="h-4 w-4 text-blue-600 border-neutral-300 rounded focus:ring-blue-500" className="h-4 w-4 text-blue-600 border-neutral-300 rounded focus:ring-blue-500"
aria-label="Required Artefact" aria-label="Required Artifact"
disabled={isSubmitting} disabled={isSubmitting}
/> />
<label className="text-sm text-neutral-600">Required (Not for Sale)</label> <label className="text-sm text-neutral-600">Required (Not for Sale)</label>
@ -416,7 +416,7 @@ function LogModal({ onClose }: { onClose: () => void }) {
Logging... Logging...
</> </>
) : ( ) : (
"Log Artefact" "Log Artifact"
)} )}
</button> </button>
</div> </div>
@ -524,16 +524,16 @@ function BulkLogModal({ onClose }: { onClose: () => void }) {
); );
} }
// Modal Component for Editing Artefact // Modal Component for Editing Artifact
function EditModal({ artefact, onClose }: { artefact: Artefact; onClose: () => void }) { function EditModal({ artifact, onClose }: { artifact: Artifact; onClose: () => void }) {
const [name, setName] = useState(artefact.name); const [name, setName] = useState(artifact.name);
const [description, setDescription] = useState(artefact.description); const [description, setDescription] = useState(artifact.description);
const [location, setLocation] = useState(artefact.location); const [location, setLocation] = useState(artifact.location);
const [earthquakeId, setEarthquakeId] = useState(artefact.earthquakeId); const [earthquakeId, setEarthquakeId] = useState(artifact.earthquakeId);
const [isRequired, setIsRequired] = useState(artefact.isRequired); const [isRequired, setIsRequired] = useState(artifact.isRequired);
const [isSold, setIsSold] = useState(artefact.isSold); const [isSold, setIsSold] = useState(artifact.isSold);
const [isCollected, setIsCollected] = useState(artefact.isCollected); const [isCollected, setIsCollected] = useState(artifact.isCollected);
const [dateAdded, setDateAdded] = useState(artefact.dateAdded); const [dateAdded, setDateAdded] = useState(artifact.dateAdded);
const [error, setError] = useState(""); const [error, setError] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
@ -551,10 +551,10 @@ function EditModal({ artefact, onClose }: { artefact: Artefact; onClose: () => v
setIsSubmitting(true); setIsSubmitting(true);
try { try {
await new Promise((resolve) => setTimeout(resolve, 500)); // Simulated API call await new Promise((resolve) => setTimeout(resolve, 500)); // Simulated API call
alert(`Updated artefact ${name}`); alert(`Updated artifact ${name}`);
onClose(); onClose();
} catch { } catch {
setError("Failed to update artefact. Please try again."); setError("Failed to update artifact. Please try again.");
} finally { } finally {
setIsSubmitting(false); setIsSubmitting(false);
} }
@ -566,7 +566,7 @@ function EditModal({ artefact, onClose }: { artefact: Artefact; onClose: () => v
onClick={handleOverlayClick} onClick={handleOverlayClick}
> >
<div className="bg-white rounded-lg shadow-xl max-w-md w-full p-6 border border-neutral-300"> <div className="bg-white rounded-lg shadow-xl max-w-md w-full p-6 border border-neutral-300">
<h3 className="text-lg font-semibold mb-4 text-neutral-800">Edit Artefact</h3> <h3 className="text-lg font-semibold mb-4 text-neutral-800">Edit Artifact</h3>
{error && <p className="text-red-600 text-sm mb-2">{error}</p>} {error && <p className="text-red-600 text-sm mb-2">{error}</p>}
<div className="space-y-2"> <div className="space-y-2">
<input <input
@ -574,14 +574,14 @@ function EditModal({ artefact, onClose }: { artefact: Artefact; onClose: () => v
value={name} value={name}
onChange={(e) => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500" className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500"
aria-label="Artefact Name" aria-label="Artifact Name"
disabled={isSubmitting} disabled={isSubmitting}
/> />
<textarea <textarea
value={description} value={description}
onChange={(e) => setDescription(e.target.value)} onChange={(e) => setDescription(e.target.value)}
className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500 h-16" className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500 h-16"
aria-label="Artefact Description" aria-label="Artifact Description"
disabled={isSubmitting} disabled={isSubmitting}
/> />
<input <input
@ -589,7 +589,7 @@ function EditModal({ artefact, onClose }: { artefact: Artefact; onClose: () => v
value={location} value={location}
onChange={(e) => setLocation(e.target.value)} onChange={(e) => setLocation(e.target.value)}
className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500" className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500"
aria-label="Artefact Location" aria-label="Artifact Location"
disabled={isSubmitting} disabled={isSubmitting}
/> />
<input <input
@ -606,7 +606,7 @@ function EditModal({ artefact, onClose }: { artefact: Artefact; onClose: () => v
checked={isRequired} checked={isRequired}
onChange={(e) => setIsRequired(e.target.checked)} onChange={(e) => setIsRequired(e.target.checked)}
className="h-4 w-4 text-blue-600 border-neutral-300 rounded focus:ring-blue-500" className="h-4 w-4 text-blue-600 border-neutral-300 rounded focus:ring-blue-500"
aria-label="Required Artefact" aria-label="Required Artifact"
disabled={isSubmitting} disabled={isSubmitting}
/> />
<label className="text-sm text-neutral-600">Required (Not for Sale)</label> <label className="text-sm text-neutral-600">Required (Not for Sale)</label>
@ -617,7 +617,7 @@ function EditModal({ artefact, onClose }: { artefact: Artefact; onClose: () => v
checked={isSold} checked={isSold}
onChange={(e) => setIsSold(e.target.checked)} onChange={(e) => setIsSold(e.target.checked)}
className="h-4 w-4 text-blue-600 border-neutral-300 rounded focus:ring-blue-500" className="h-4 w-4 text-blue-600 border-neutral-300 rounded focus:ring-blue-500"
aria-label="Sold Artefact" aria-label="Sold Artifact"
disabled={isSubmitting} disabled={isSubmitting}
/> />
<label className="text-sm text-neutral-600">Sold</label> <label className="text-sm text-neutral-600">Sold</label>
@ -628,7 +628,7 @@ function EditModal({ artefact, onClose }: { artefact: Artefact; onClose: () => v
checked={isCollected} checked={isCollected}
onChange={(e) => setIsCollected(e.target.checked)} onChange={(e) => setIsCollected(e.target.checked)}
className="h-4 w-4 text-blue-600 border-neutral-300 rounded focus:ring-blue-500" className="h-4 w-4 text-blue-600 border-neutral-300 rounded focus:ring-blue-500"
aria-label="Collected Artefact" aria-label="Collected Artifact"
disabled={isSubmitting} disabled={isSubmitting}
/> />
<label className="text-sm text-neutral-600">Collected</label> <label className="text-sm text-neutral-600">Collected</label>
@ -685,18 +685,18 @@ function EditModal({ artefact, onClose }: { artefact: Artefact; onClose: () => v
} }
// Filter Logic // Filter Logic
const applyFilters = (artefacts: Artefact[], filters: Record<string, string>): Artefact[] => { const applyFilters = (artifacts: Artifact[], filters: Record<string, string>): Artifact[] => {
return artefacts.filter((artefact) => { return artifacts.filter((artifact) => {
return ( return (
(filters.id === "" || artefact.id.toString().includes(filters.id)) && (filters.id === "" || artifact.id.toString().includes(filters.id)) &&
(filters.name === "" || artefact.name.toLowerCase().includes(filters.name.toLowerCase())) && (filters.name === "" || artifact.name.toLowerCase().includes(filters.name.toLowerCase())) &&
(filters.earthquakeId === "" || artefact.earthquakeId.toLowerCase().includes(filters.earthquakeId.toLowerCase())) && (filters.earthquakeId === "" || artifact.earthquakeId.toLowerCase().includes(filters.earthquakeId.toLowerCase())) &&
(filters.location === "" || artefact.location.toLowerCase().includes(filters.location.toLowerCase())) && (filters.location === "" || artifact.location.toLowerCase().includes(filters.location.toLowerCase())) &&
(filters.description === "" || artefact.description.toLowerCase().includes(filters.description.toLowerCase())) && (filters.description === "" || artifact.description.toLowerCase().includes(filters.description.toLowerCase())) &&
(filters.isRequired === "" || (filters.isRequired === "true" ? artefact.isRequired : !artefact.isRequired)) && (filters.isRequired === "" || (filters.isRequired === "true" ? artifact.isRequired : !artifact.isRequired)) &&
(filters.isSold === "" || (filters.isSold === "true" ? artefact.isSold : !artefact.isSold)) && (filters.isSold === "" || (filters.isSold === "true" ? artifact.isSold : !artifact.isSold)) &&
(filters.isCollected === "" || (filters.isCollected === "true" ? artefact.isCollected : !artefact.isCollected)) && (filters.isCollected === "" || (filters.isCollected === "true" ? artifact.isCollected : !artifact.isCollected)) &&
(filters.dateAdded === "" || artefact.dateAdded === filters.dateAdded) (filters.dateAdded === "" || artifact.dateAdded === filters.dateAdded)
); );
}); });
}; };
@ -706,7 +706,7 @@ export default function Warehouse() {
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [showLogModal, setShowLogModal] = useState(false); const [showLogModal, setShowLogModal] = useState(false);
const [showBulkLogModal, setShowBulkLogModal] = useState(false); const [showBulkLogModal, setShowBulkLogModal] = useState(false);
const [editArtefact, setEditArtefact] = useState<Artefact | null>(null); const [editArtifact, setEditArtifact] = useState<Artifact | null>(null);
const [filters, setFilters] = useState({ const [filters, setFilters] = useState({
id: "", id: "",
name: "", name: "",
@ -720,29 +720,29 @@ export default function Warehouse() {
}); });
const [isFiltering, setIsFiltering] = useState(false); const [isFiltering, setIsFiltering] = useState(false);
const [sortConfig, setSortConfig] = useState<{ const [sortConfig, setSortConfig] = useState<{
key: keyof Artefact; key: keyof Artifact;
direction: "asc" | "desc"; direction: "asc" | "desc";
} | null>(null); } | null>(null);
const artefactsPerPage = 10; const artifactsPerPage = 10;
const indexOfLastArtefact = currentPage * artefactsPerPage; const indexOfLastArtifact = currentPage * artifactsPerPage;
const indexOfFirstArtefact = indexOfLastArtefact - artefactsPerPage; const indexOfFirstArtifact = indexOfLastArtifact - artifactsPerPage;
// Apply filters with loading state // Apply filters with loading state
const filteredArtefacts = useMemo(() => { const filteredArtifacts = useMemo(() => {
setIsFiltering(true); setIsFiltering(true);
const result = applyFilters(warehouseArtefacts, filters); const result = applyFilters(warehouseArtifacts, filters);
setIsFiltering(false); setIsFiltering(false);
return result; return result;
}, [filters]); }, [filters]);
const currentArtefacts = filteredArtefacts.slice(indexOfFirstArtefact, indexOfLastArtefact); const currentArtifacts = filteredArtifacts.slice(indexOfFirstArtifact, indexOfLastArtifact);
// Overview stats // Overview stats
const totalArtefacts = warehouseArtefacts.length; const totalArtifacts = warehouseArtifacts.length;
const today = "2025-05-04"; const today = "2025-05-04";
const artefactsAddedToday = warehouseArtefacts.filter((a) => a.dateAdded === today).length; const artifactsAddedToday = warehouseArtifacts.filter((a) => a.dateAdded === today).length;
const artefactsSoldToday = warehouseArtefacts.filter((a) => a.isSold && a.dateAdded === today).length; const artifactsSoldToday = warehouseArtifacts.filter((a) => a.isSold && a.dateAdded === today).length;
const clearFilters = () => { const clearFilters = () => {
setFilters({ setFilters({
@ -768,15 +768,15 @@ export default function Warehouse() {
<div className="flex gap-8 ml-5 mt-1"> <div className="flex gap-8 ml-5 mt-1">
<div className="flex items-center text-md text-neutral-600"> <div className="flex items-center text-md text-neutral-600">
<FaWarehouse className="mr-2 h-8 w-8 text-blue-600 opacity-90" /> <FaWarehouse className="mr-2 h-8 w-8 text-blue-600 opacity-90" />
Total Artefacts: <span className="font-semibold ml-1">{totalArtefacts}</span> Total Artifacts: <span className="font-semibold ml-1">{totalArtifacts}</span>
</div> </div>
<div className="flex items-center text-md text-neutral-600"> <div className="flex items-center text-md text-neutral-600">
<IoToday className="mr-2 h-8 w-8 text-blue-600 opacity-90" /> <IoToday className="mr-2 h-8 w-8 text-blue-600 opacity-90" />
Added Today: <span className="font-semibold ml-1">{artefactsAddedToday}</span> Added Today: <span className="font-semibold ml-1">{artifactsAddedToday}</span>
</div> </div>
<div className="flex items-center text-md text-neutral-600"> <div className="flex items-center text-md text-neutral-600">
<FaCartShopping className="mr-2 h-8 w-8 text-blue-600 opacity-90" /> <FaCartShopping className="mr-2 h-8 w-8 text-blue-600 opacity-90" />
Sold Today: <span className="font-semibold ml-1">{artefactsSoldToday}</span> Sold Today: <span className="font-semibold ml-1">{artifactsSoldToday}</span>
</div> </div>
</div> </div>
@ -792,7 +792,7 @@ export default function Warehouse() {
onClick={() => setShowLogModal(true)} onClick={() => setShowLogModal(true)}
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 font-medium" className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 font-medium"
> >
Log Single Artefact Log Single Artifact
</button> </button>
<button <button
onClick={() => setShowBulkLogModal(true)} onClick={() => setShowBulkLogModal(true)}
@ -822,11 +822,11 @@ export default function Warehouse() {
</div> </div>
)} )}
<div className="h-full overflow-y-none"> <div className="h-full overflow-y-none">
<ArtefactTable <ArtifactTable
artefacts={currentArtefacts} artifacts={currentArtifacts}
filters={filters} filters={filters}
setFilters={setFilters} setFilters={setFilters}
setEditArtefact={setEditArtefact} setEditArtifact={setEditArtifact}
clearSort={() => setSortConfig(null)} clearSort={() => setSortConfig(null)}
/> />
</div> </div>
@ -837,7 +837,7 @@ export default function Warehouse() {
{/* Modals */} {/* Modals */}
{showLogModal && <LogModal onClose={() => setShowLogModal(false)} />} {showLogModal && <LogModal onClose={() => setShowLogModal(false)} />}
{showBulkLogModal && <BulkLogModal onClose={() => setShowBulkLogModal(false)} />} {showBulkLogModal && <BulkLogModal onClose={() => setShowBulkLogModal(false)} />}
{editArtefact && <EditModal artefact={editArtefact} onClose={() => setEditArtefact(null)} />} {editArtifact && <EditModal artifact={editArtifact} onClose={() => setEditArtifact(null)} />}
</div> </div>
); );
} }

View File

@ -1 +1 @@
Artefacts Artifacts
1 Artefacts Artifacts

View File

@ -13,7 +13,7 @@ model User {
passwordHash String passwordHash String
role String @default("GUEST") @db.VarChar(10) // ADMIN, SCIENTIST, GUEST role String @default("GUEST") @db.VarChar(10) // ADMIN, SCIENTIST, GUEST
scientist Scientist? @relation scientist Scientist? @relation
purchasedArtefacts Artefact[] @relation("UserPurchasedArtefacts") purchasedArtifacts Artifact[] @relation("UserPurchasedArtifacts")
} }
// Scientist model // Scientist model
@ -29,7 +29,7 @@ model Scientist {
subordinates Scientist[] @relation("SuperiorRelation") subordinates Scientist[] @relation("SuperiorRelation")
earthquakes Earthquake[] @relation("ScientistEarthquakeCreator") earthquakes Earthquake[] @relation("ScientistEarthquakeCreator")
observatories Observatory[] @relation("ScientistObservatoryCreator") observatories Observatory[] @relation("ScientistObservatoryCreator")
artefacts Artefact[] @relation("ScientistArtefactCreator") artifacts Artifact[] @relation("ScientistArtifactCreator")
} }
// Earthquake model // Earthquake model
@ -45,7 +45,7 @@ model Earthquake {
depth Float depth Float
creatorId Int? creatorId Int?
creator Scientist? @relation("ScientistEarthquakeCreator", fields: [creatorId], references: [id], onDelete: NoAction, onUpdate: NoAction) creator Scientist? @relation("ScientistEarthquakeCreator", fields: [creatorId], references: [id], onDelete: NoAction, onUpdate: NoAction)
artefacts Artefact[] artifacts Artifact[]
observatories Observatory[] @relation("EarthquakeObservatory") observatories Observatory[] @relation("EarthquakeObservatory")
} }
@ -66,8 +66,8 @@ model Observatory {
earthquakes Earthquake[] @relation("EarthquakeObservatory") earthquakes Earthquake[] @relation("EarthquakeObservatory")
} }
// Artefact model // Artifact model
model Artefact { model Artifact {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@ -76,10 +76,10 @@ model Artefact {
earthquakeId Int earthquakeId Int
earthquake Earthquake @relation(fields: [earthquakeId], references: [id]) earthquake Earthquake @relation(fields: [earthquakeId], references: [id])
creatorId Int? creatorId Int?
creator Scientist? @relation("ScientistArtefactCreator", fields: [creatorId], references: [id], onDelete: NoAction, onUpdate: NoAction) creator Scientist? @relation("ScientistArtifactCreator", fields: [creatorId], references: [id], onDelete: NoAction, onUpdate: NoAction)
required Boolean @default(true) required Boolean @default(true)
shopPrice Float? // In Euros shopPrice Float? // In Euros
purchasedById Int? purchasedById Int?
purchasedBy User? @relation("UserPurchasedArtefacts", fields: [purchasedById], references: [id], onDelete: NoAction, onUpdate: NoAction) purchasedBy User? @relation("UserPurchasedArtifacts", fields: [purchasedById], references: [id], onDelete: NoAction, onUpdate: NoAction)
pickedUp Boolean @default(false) pickedUp Boolean @default(false)
} }

View File

@ -1,4 +1,4 @@
interface Artefact { interface Artifact {
// todo change to string // todo change to string
id: number; id: number;
name: string; name: string;
@ -8,4 +8,4 @@ interface Artefact {
price: number; price: number;
} }
export default Artefact; export default Artifact;

View File

@ -1,4 +1,5 @@
import { Action } from "easy-peasy"; import { Action } from 'easy-peasy';
// import type { User } from "@prisma/client"; // import type { User } from "@prisma/client";
interface Scientist { interface Scientist {
@ -13,10 +14,10 @@ interface Scientist {
subordinates: Scientist[]; subordinates: Scientist[];
// earthquakes: Earthquake[]; // earthquakes: Earthquake[];
// observatories: Observatory[]; // observatories: Observatory[];
artefacts: Artefact[]; artifacts: Artifact[];
} }
interface Artefact { interface Artifact {
id: number; id: number;
name: string; name: string;
description: string; description: string;
@ -36,7 +37,7 @@ interface User {
passwordHash: string; passwordHash: string;
role: string; role: string;
scientist: Scientist | undefined; scientist: Scientist | undefined;
purchasedArtefacts: Artefact[]; purchasedArtifacts: Artifact[];
} }
type Currency = "GBP" | "USD" | "EUR"; type Currency = "GBP" | "USD" | "EUR";