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

This commit is contained in:
Tim Howitz 2025-05-28 15:45:14 +01:00
commit c1d686b012
8 changed files with 175 additions and 211 deletions

View File

@ -93,7 +93,7 @@ model Artefact {
earthquake Earthquake @relation(fields: [earthquakeId], references: [id]) earthquake Earthquake @relation(fields: [earthquakeId], references: [id])
creatorId Int? creatorId Int?
creator User? @relation("UserArtefactCreator", fields: [creatorId], references: [id], onDelete: NoAction, onUpdate: NoAction) creator User? @relation("UserArtefactCreator", fields: [creatorId], references: [id], onDelete: NoAction, onUpdate: NoAction)
isRequired Boolean @default(true) isRequired Boolean @default(true) @map("isRequired")
dateAddedToShop DateTime? dateAddedToShop DateTime?
shopPrice Float? shopPrice Float?
isSold Boolean @default(false) isSold Boolean @default(false)

BIN
public/BlueBackground.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
public/EarthHighRes.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 KiB

BIN
public/tectonicPlates.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

View File

@ -0,0 +1,18 @@
import { NextResponse } from "next/server";
import { prisma } from "@utils/prisma";
export async function GET(request: Request) {
try {
const artefact = await prisma.artefact.findMany();
return NextResponse.json({
message: "Got artefacts successfully",
artefact
}, { status: 200 });
} catch (error) {
console.error("Error in artefacts endpoint:", error);
return NextResponse.json({ message: "Internal Server Error" }, { status: 500 });
}
}

View File

@ -1,191 +1,46 @@
"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, useEffect } from "react";
import BottomFooter from "@components/BottomFooter";
import { ExtendedArtefact } from "@appTypes/ApiTypes"; import { ExtendedArtefact } from "@appTypes/ApiTypes";
import { Currency } from "@appTypes/StoreModel"; import { Currency } from "@appTypes/StoreModel";
import { useStoreState } from "@hooks/store"; import { useStoreState } from "@hooks/store";
const artefacts: ExtendedArtefact[] = [
{
id: 1,
name: "Golden Scarab",
description: "An ancient Egyptian artefact symbolizing rebirth.",
location: "Cairo, Egypt",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact1.jpg",
price: 150,
},
{
id: 2,
name: "Aztec Sunstone",
description: "A replica of the Aztec calendar (inscriptions intact).",
location: "Peru",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact2.jpg",
price: 200,
},
{
id: 3,
name: "Medieval Chalice",
description: "Used by royalty in medieval ceremonies.",
location: "Cambridge, England",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact3.jpg",
price: 120,
},
{
id: 4,
name: "Roman Coin",
description: "An authentic Roman coin from the 2nd century CE.",
location: "Rome, Italy",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact4.jpg",
price: 80,
},
{
id: 5,
name: "Samurai Mask",
description: "Replica of Japanese Samurai battle masks.",
location: "Tokyo, Japan",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact5.jpg",
price: 300,
},
{
id: 6,
name: "Ancient Greek Vase",
description: "Depicts Greek mythology, found in the Acropolis.",
location: "Athens, Greece",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact6.jpg",
price: 250,
},
{
id: 7,
name: "Incan Pendant",
description: "Represents the Sun God Inti.",
location: "India",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact7.jpg",
price: 175,
},
{
id: 8,
name: "Persian Carpet Fragment",
description: "Ancient Persian artistry.",
location: "Petra, Jordan",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact8.jpg",
price: 400,
},
{
id: 9,
name: "Stone Buddha",
description: "Authentic stone Buddha carving.",
location: "India",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact9.jpg",
price: 220,
},
{
id: 10,
name: "Victorian Brooch",
description: "A beautiful Victorian-era brooch with a ruby centre.",
location: "Oxford, England",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact10.jpg",
price: 150,
},
{
id: 11,
name: "Ancient Scroll",
description: "A mysterious scroll from ancient times.",
location: "Madrid, Spain",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact11.jpg",
price: 500,
},
{
id: 12,
name: "Ming Dynasty Porcelain",
description: "Porcelain from China's Ming Dynasty.",
location: "Beijing, China",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact12.jpg",
price: 300,
},
{
id: 13,
name: "African Tribal Mask",
description: "A unique tribal mask from Africa.",
location: "Nigeria",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact13.jpg",
price: 250,
},
{
id: 14,
name: "Crystal Skull",
description: "A mystical pre-Columbian artefact.",
location: "Colombia",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact14.jpg",
price: 1000,
},
{
id: 15,
name: "Medieval Armor Fragment",
description: "A fragment of medieval armor.",
location: "Normandy, France",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact15.jpg",
price: 400,
},
{
id: 16,
name: "Medieval Helmet Fragment",
description: "A fragment of a medieval helmet.",
location: "Normandy, France",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact16.jpg",
price: 500,
},
];
export default function Shop() { export default function Shop() {
const [artefacts, setArtefacts] = useState<ExtendedArtefact[]>([]);
const [loading, setLoading] = useState(true);
// 3. Fetch from your API route and map data to fit your existing fields
useEffect(() => {
async function fetchArtefacts() {
setLoading(true);
try {
const res = await fetch("/api/artefacts");
const data = await res.json();
const transformed = data.artefact.map((a: any) => ({
id: a.id,
name: a.name,
description: a.description,
location: a.warehouseArea, // your database
earthquakeID: a.earthquakeId?.toString() ?? "",
observatory: a.type ?? "", // if you want to display type
dateReleased: a.createdAt ? new Date(a.createdAt).toLocaleDateString() : "",
image: "/artefactImages/" + (a.imageName || "NoImageFound.PNG"),
price: a.shopPrice ?? 100, // fallback price if not in DB
}));
setArtefacts(transformed);
} catch (e) {
// Optionally handle error
console.error("Failed to fetch artefacts", e);
} finally {
setLoading(false);
}
}
fetchArtefacts();
}, []);
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [selectedArtefact, setSelectedArtefact] = useState<Artefact | null>(null); const [selectedArtefact, setSelectedArtefact] = useState<Artefact | null>(null);
const [showPaymentModal, setShowPaymentModal] = useState(false); const [showPaymentModal, setShowPaymentModal] = useState(false);
@ -433,20 +288,20 @@ export default function Shop() {
} }
return ( return (
<div <div
className="min-h-screen relative flex flex-col" className="min-h-screen bg-blue-50 relative flex flex-col"
style={{ style={{
backgroundImage: "url('/artefacts.jpg')", backgroundImage: "url('/EarthHighRes.jpg')",
backgroundSize: "cover", backgroundSize: "cover",
backgroundPosition: "center", backgroundPosition: "center",
}} }}
> >
<div className="absolute inset-0 bg-black bg-opacity-50 z-0"></div> <div className="absolute inset-0 bg-black bg-opacity-0 z-0"></div>
<div className="relative z-10 flex flex-col items-center w-full px-2 py-12"> <div className="relative z-10 flex flex-col items-center w-full px-2 py-12">
<h1 className="text-4xl md:text-4xl font-bold text-center text-white mb-2 tracking-tight drop-shadow-lg"> <h1 className="text-4xl md:text-4xl font-bold text-center text-blue-300 mb-2 tracking-tight drop-shadow-lg">
Artefact Shop Artefact Shop
</h1> </h1>
<p className="text-lg md:text-xl text-center text-white mb-10 drop-shadow-md max-w-2xl"> <p className="text-lg md:text-xl text-center text-white mb-10 drop-shadow-md max-w-2xl">
Discover extraordinary historical artefacts and collectibles from major seismic events from around the world - now Discover extraordinary artefacts and collectibles from major seismic events from around the world - Previously studied by our scientists, now
available for purchase. available for purchase.
</p> </p>
<div className="w-full max-w-7xl grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-10 p-2"> <div className="w-full max-w-7xl grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-10 p-2">
@ -489,6 +344,10 @@ export default function Shop() {
{showThankYouModal && orderNumber && ( {showThankYouModal && orderNumber && (
<ThankYouModal orderNumber={orderNumber} onClose={() => setShowThankYouModal(false)} /> <ThankYouModal orderNumber={orderNumber} onClose={() => setShowThankYouModal(false)} />
)} )}
{!selectedArtefact && !showPaymentModal && !showThankYouModal && (
<div className="relative z-50">
<BottomFooter />
</div>
)}
</div> </div>
); );
}

View File

@ -33,7 +33,7 @@ export default function Page() {
return ( return (
<div <div
className="min-h-screen relative flex flex-col items-center justify-center px-4 py-30" className="min-h-screen relative flex flex-col items-center justify-center px-4 py-30"
style={{ backgroundImage: "url('tectonicPlate.jpg')", backgroundSize: "cover", backgroundPosition: "center" }} style={{ backgroundImage: "url('tectonicPlates.png')", backgroundSize: "cover", backgroundPosition: "center" }}
> >
{/* Overlay */} {/* Overlay */}
<div className="absolute inset-0 bg-black bg-opacity-50 pointer-events-none"></div> <div className="absolute inset-0 bg-black bg-opacity-50 pointer-events-none"></div>

View File

@ -0,0 +1,87 @@
import Link from "next/link";
import React, { Dispatch, SetStateAction, useState } from "react";
import { TbHexagon } from "react-icons/tb";
import Event from "@appTypes/Event";
import getMagnitudeColor from "@utils/getMagnitudeColour";
function setButton1(button1Name) {
const [modalOpen, setModalOpen] = useState(false);
return (
modalOpen && (
<div className="fixed z-50 inset-0 bg-black bg-opacity-50 flex items-center justify-center">
<div className="bg-white rounded-lg shadow-lg max-w-6xl w-full p-10">
<div className="flex items-center justify-between mb-3">
<h2 className="text-2xl font-extrabold">Log New Earthquake</h2>
<button
onClick={() => setModalOpen(false)}
className="text-2xl font-bold text-gray-500 hover:text-gray-900"
aria-label="Close modal"
>
&times;
</button>
</div>
{/* Blank Table */}
<table className="w-full border mb-4">
<thead>
<tr>
<th className="border px-2 py-1">Date</th>
<th className="border px-2 py-1">Code</th>
<th className="border px-2 py-1">Magnitude</th>
<th className="border px-2 py-1">Observatory</th>
<th className="border px-2 py-1">Type</th>
<th className="border px-2 py-1">Latitude</th>
<th className="border px-2 py-1">Longitude</th>
<th className="border px-2 py-1">Location</th>
<th className="border px-2 py-1">Observatory</th>
<th className="border px-2 py-1">Depth</th>
</tr>
</thead>
<tbody>
<tr>
<td className="border px-2 py-1">
<input type="date" className="border p-1 w-full" disabled />
</td>
<td className="border px-2 py-1">
<input type="number" className="border p-1 w-full" disabled />
</td>
<td className="border px-2 py-1">
<input type="text" className="border p-1 w-full" disabled />
</td>
<td className="border px-2 py-1">
<input type="text" className="border p-1 w-full" disabled />
</td>
<td className="border px-2 py-1">
<input type="text" className="border p-1 w-full" disabled />
</td>
<td className="border px-2 py-1">
<input type="date" className="border p-1 w-full" disabled />
</td>
<td className="border px-2 py-1">
<input type="number" className="border p-1 w-full" disabled />
</td>
<td className="border px-2 py-1">
<input type="text" className="border p-1 w-full" disabled />
</td>
<td className="border px-2 py-1">
<input type="text" className="border p-1 w-full" disabled />
</td>
<td className="border px-2 py-1">
<input type="text" className="border p-1 w-full" disabled />
</td>
</tr>
</tbody>
</table>
<div className="flex justify-end gap-4">
<button onClick={() => setModalOpen(true)} className="bg-blue-600 text-white px-10 py-2 rounded">
Add
</button>
<button onClick={() => setModalOpen(false)} className="bg-blue-600 text-white px-4 py-2 rounded">
Close
</button>
</div>
</div>
</div>
)
);
}