Merge branch 'master' of ssh://stash.dyson.global.corp:7999/~thowitz/tremor-tracker
This commit is contained in:
commit
c1d686b012
@ -10,18 +10,18 @@ generator client {
|
|||||||
|
|
||||||
// User model
|
// User model
|
||||||
model User {
|
model User {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
name String
|
name String
|
||||||
email String @unique
|
email String @unique
|
||||||
passwordHash String
|
passwordHash String
|
||||||
role String @default("GUEST") @db.VarChar(10) // Allowed: ADMIN, SCIENTIST, GUEST
|
role String @default("GUEST") @db.VarChar(10) // Allowed: ADMIN, SCIENTIST, GUEST
|
||||||
scientist Scientist? @relation
|
scientist Scientist? @relation
|
||||||
purchasedOrders Order[] @relation("UserOrders")
|
purchasedOrders Order[] @relation("UserOrders")
|
||||||
requests Request[] @relation("UserRequests")
|
requests Request[] @relation("UserRequests")
|
||||||
earthquakes Earthquake[] @relation("UserEarthquakeCreator")
|
earthquakes Earthquake[] @relation("UserEarthquakeCreator")
|
||||||
observatories Observatory[] @relation("UserObservatoryCreator")
|
observatories Observatory[] @relation("UserObservatoryCreator")
|
||||||
artefacts Artefact[] @relation("UserArtefactCreator")
|
artefacts Artefact[] @relation("UserArtefactCreator")
|
||||||
}
|
}
|
||||||
|
|
||||||
model Request {
|
model Request {
|
||||||
@ -35,15 +35,15 @@ model Request {
|
|||||||
|
|
||||||
// Scientist model
|
// Scientist model
|
||||||
model Scientist {
|
model Scientist {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
name String
|
name String
|
||||||
level String @db.VarChar(10) // JUNIOR, SENIOR
|
level String @db.VarChar(10) // JUNIOR, SENIOR
|
||||||
user User @relation(fields: [userId], references: [id])
|
user User @relation(fields: [userId], references: [id])
|
||||||
userId Int @unique
|
userId Int @unique
|
||||||
superior Scientist? @relation("SuperiorRelation", fields: [superiorId], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
superior Scientist? @relation("SuperiorRelation", fields: [superiorId], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||||
superiorId Int?
|
superiorId Int?
|
||||||
subordinates Scientist[] @relation("SuperiorRelation")
|
subordinates Scientist[] @relation("SuperiorRelation")
|
||||||
}
|
}
|
||||||
|
|
||||||
model Earthquake {
|
model Earthquake {
|
||||||
@ -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
BIN
public/BlueBackground.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
BIN
public/EarthHighRes.jpg
Normal file
BIN
public/EarthHighRes.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 862 KiB |
BIN
public/tectonicPlates.png
Normal file
BIN
public/tectonicPlates.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 85 KiB |
18
src/app/api/artefacts/route.ts
Normal file
18
src/app/api/artefacts/route.ts
Normal 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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)} />
|
||||||
)}
|
)}
|
||||||
</div>
|
{!selectedArtefact && !showPaymentModal && !showThankYouModal && (
|
||||||
);
|
<div className="relative z-50">
|
||||||
}
|
<BottomFooter />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
@ -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>
|
||||||
|
|||||||
87
src/components/LoggingModal.tsx
Normal file
87
src/components/LoggingModal.tsx
Normal 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"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</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>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user