Compare commits
14 Commits
53c6292e1a
...
dce7e51a53
| Author | SHA1 | Date | |
|---|---|---|---|
| dce7e51a53 | |||
| b0ec146bf0 | |||
|
|
579c1c205a | ||
|
|
52f17d5a00 | ||
|
|
8a45f49a04 | ||
|
|
8d007a7393 | ||
|
|
8fefc67428 | ||
|
|
4890af1e25 | ||
|
|
628b4a9370 | ||
|
|
06403d254f | ||
|
|
1cdace58b0 | ||
|
|
28e6a556f8 | ||
|
|
0b6167ed4b | ||
|
|
b615524c6c |
2
.env
2
.env
@ -1,2 +1,2 @@
|
|||||||
DATABASE_URL=""
|
DATABASE_URL="sqlserver://UK-DIET-SQL-T1:1433;database=Group8_DB;user=UserGroup8;password=aFgbsH1f2evK6xyP;trustServerCertificate=true"
|
||||||
JWT_SECRET_KEY=mysupersecretkey
|
JWT_SECRET_KEY=mysupersecretkey
|
||||||
1844
package-lock.json
generated
1844
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@ -15,9 +15,9 @@
|
|||||||
"@types/jsonwebtoken": "^9.0.9",
|
"@types/jsonwebtoken": "^9.0.9",
|
||||||
"@types/mapbox-gl": "^3.4.1",
|
"@types/mapbox-gl": "^3.4.1",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
"bcrypt": "^5.1.1",
|
|
||||||
"bcryptjs": "^3.0.2",
|
"bcryptjs": "^3.0.2",
|
||||||
"body-parser": "^2.2.0",
|
"body-parser": "^2.2.0",
|
||||||
|
"csv-parse": "^5.6.0",
|
||||||
"csv-parser": "^3.2.0",
|
"csv-parser": "^3.2.0",
|
||||||
"dotenv": "^16.5.0",
|
"dotenv": "^16.5.0",
|
||||||
"easy-peasy": "^6.1.0",
|
"easy-peasy": "^6.1.0",
|
||||||
@ -29,11 +29,11 @@
|
|||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mapbox-gl": "^3.10.0",
|
"mapbox-gl": "^3.10.0",
|
||||||
"next": "15.1.7",
|
"next": "^15.1.7",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"prisma": "^6.4.1",
|
"prisma": "^6.4.1",
|
||||||
"react": "^19.0.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-leaflet": "^5.0.0",
|
"react-leaflet": "^5.0.0",
|
||||||
"react-node": "^1.0.2",
|
"react-node": "^1.0.2",
|
||||||
@ -42,7 +42,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
"@types/bcrypt": "^5.0.2",
|
"@types/bcryptjs": "^5.0.2",
|
||||||
"@types/express": "^5.0.1",
|
"@types/express": "^5.0.1",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
|
|||||||
85
prisma/schema.prisma
Normal file
85
prisma/schema.prisma
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
// Datasource configuration
|
||||||
|
datasource db {
|
||||||
|
provider = "sqlserver"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
// User model
|
||||||
|
model User {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
name String
|
||||||
|
email String @unique
|
||||||
|
passwordHash String
|
||||||
|
role String @default("GUEST") @db.VarChar(10) // ADMIN, SCIENTIST, GUEST
|
||||||
|
scientist Scientist? @relation
|
||||||
|
purchasedArtefacts Artefact[] @relation("UserPurchasedArtefacts")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scientist model
|
||||||
|
model Scientist {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
name String
|
||||||
|
level String @db.VarChar(10) // JUNIOR, SENIOR
|
||||||
|
user User @relation(fields: [userId], references: [id])
|
||||||
|
userId Int @unique
|
||||||
|
superior Scientist? @relation("SuperiorRelation", fields: [superiorId], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||||
|
superiorId Int?
|
||||||
|
subordinates Scientist[] @relation("SuperiorRelation")
|
||||||
|
earthquakes Earthquake[] @relation("ScientistEarthquakeCreator")
|
||||||
|
observatories Observatory[] @relation("ScientistObservatoryCreator")
|
||||||
|
artefacts Artefact[] @relation("ScientistArtefactCreator")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Earthquake model
|
||||||
|
model Earthquake {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
date DateTime
|
||||||
|
location String
|
||||||
|
latitude String
|
||||||
|
longitude String
|
||||||
|
magnitude Float
|
||||||
|
depth Float
|
||||||
|
creatorId Int?
|
||||||
|
creator Scientist? @relation("ScientistEarthquakeCreator", fields: [creatorId], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||||
|
artefacts Artefact[]
|
||||||
|
observatories Observatory[] @relation("EarthquakeObservatory")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observatory model
|
||||||
|
model Observatory {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
name String
|
||||||
|
location String
|
||||||
|
longitude String
|
||||||
|
latitude String
|
||||||
|
dateEstablished Int?
|
||||||
|
functional Boolean
|
||||||
|
seismicSensorOnline Boolean @default(true)
|
||||||
|
creatorId Int?
|
||||||
|
creator Scientist? @relation("ScientistObservatoryCreator", fields: [creatorId], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||||
|
earthquakes Earthquake[] @relation("EarthquakeObservatory")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Artefact model
|
||||||
|
model Artefact {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
type String @db.VarChar(50) // Lava, Tephra, Ash, Soil
|
||||||
|
warehouseArea String // Examples: "ZoneA-Shelf1", "ZoneB-Rack2", "ZoneC-Bin3"
|
||||||
|
earthquakeId Int
|
||||||
|
earthquake Earthquake @relation(fields: [earthquakeId], references: [id])
|
||||||
|
creatorId Int?
|
||||||
|
creator Scientist? @relation("ScientistArtefactCreator", fields: [creatorId], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||||
|
required Boolean @default(true)
|
||||||
|
shopPrice Float? // In Euros
|
||||||
|
purchasedById Int?
|
||||||
|
purchasedBy User? @relation("UserPurchasedArtefacts", fields: [purchasedById], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||||
|
pickedUp Boolean @default(false)
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 280 KiB After Width: | Height: | Size: 280 KiB |
1573
public/earthquakes.csv
Normal file
1573
public/earthquakes.csv
Normal file
File diff suppressed because it is too large
Load Diff
@ -6,54 +6,28 @@ const usingPrisma = false;
|
|||||||
let prisma: PrismaClient;
|
let prisma: PrismaClient;
|
||||||
if (usingPrisma) prisma = new PrismaClient();
|
if (usingPrisma) prisma = new PrismaClient();
|
||||||
|
|
||||||
export async function GET(request: Request) {
|
export async function POST(req: Request) {
|
||||||
try {
|
try {
|
||||||
const events = [
|
const json = await req.json(); // Parse incoming JSON data
|
||||||
{
|
const {rangeDaysPrev} = json.body
|
||||||
id: "1234",
|
|
||||||
title: "Earthquake in Germany",
|
|
||||||
text1: "Magnitude 8.5",
|
|
||||||
text2: "30 minutes ago",
|
|
||||||
magnitude: 8.5,
|
|
||||||
longitude: 10.4515, // Near Berlin, Germany
|
|
||||||
latitude: 52.52,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "2134",
|
|
||||||
title: "Earthquake in California",
|
|
||||||
text1: "Magnitude 5.3",
|
|
||||||
text2: "2 hours ago",
|
|
||||||
magnitude: 5.3,
|
|
||||||
longitude: -122.4194, // Near San Francisco, California, USA
|
|
||||||
latitude: 37.7749,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "2314",
|
|
||||||
title: "Tremor in Japan",
|
|
||||||
text1: "Magnitude 4.7",
|
|
||||||
text2: "5 hours ago",
|
|
||||||
magnitude: 4.7,
|
|
||||||
longitude: 139.6917, // Near Tokyo, Japan
|
|
||||||
latitude: 35.6762,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "2341",
|
|
||||||
title: "Tremor in Spain",
|
|
||||||
text1: "Magnitude 2.1",
|
|
||||||
text2: "10 hours ago",
|
|
||||||
magnitude: 2.1,
|
|
||||||
longitude: -3.7038, // Near Madrid, Spain
|
|
||||||
latitude: 40.4168,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
let earthquakes;
|
const now = new Date()
|
||||||
if (usingPrisma) earthquakes = await prisma.earthquakes.findMany();
|
const rangeBeginning = new Date();
|
||||||
|
rangeBeginning.setDate(rangeBeginning.getDate() - rangeDaysPrev)
|
||||||
|
|
||||||
|
const earthquakes = await prisma.earthquake.findMany(
|
||||||
|
{where: {
|
||||||
|
date: {
|
||||||
|
gte: rangeBeginning,
|
||||||
|
lte: now
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
);
|
||||||
|
|
||||||
if (earthquakes) {
|
if (earthquakes) {
|
||||||
return NextResponse.json({ message: "Got earthquakes successfully", earthquakes }, { status: 200 });
|
return NextResponse.json({ message: "Got earthquakes successfully", earthquakes }, { status: 200 });
|
||||||
} else {
|
} else {
|
||||||
return NextResponse.json({ message: "Got earthquakes successfully", earthquakes: events }, { status: 200 });
|
return NextResponse.json({ message: "Got earthquakes successfully", earthquakes }, { 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) {
|
||||||
|
|||||||
63
src/app/api/import-earthquakes/route.ts
Normal file
63
src/app/api/import-earthquakes/route.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
import fs from "fs/promises";
|
||||||
|
import path from "path";
|
||||||
|
import { parse } from "csv-parse/sync";
|
||||||
|
|
||||||
|
// Define the path to your CSV file.
|
||||||
|
// Place your earthquakes.csv in your project root or `public` directory
|
||||||
|
const csvFilePath = path.resolve(process.cwd(), "public/earthquakes.csv");
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
type CsvRow = {
|
||||||
|
Date: string;
|
||||||
|
Magnitude: string;
|
||||||
|
Latitude: string;
|
||||||
|
Longitude: string;
|
||||||
|
Location: string;
|
||||||
|
Depth: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function POST() {
|
||||||
|
try {
|
||||||
|
// 1. Read the CSV file
|
||||||
|
const fileContent = await fs.readFile(csvFilePath, "utf8");
|
||||||
|
|
||||||
|
// 2. Parse the CSV
|
||||||
|
const records: CsvRow[] = parse(fileContent, {
|
||||||
|
columns: true,
|
||||||
|
skip_empty_lines: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. Transform each CSV row to Earthquake model
|
||||||
|
// Since your prisma model expects: name, date (DateTime), location, magnitude (float), depth (float). We'll fill casualties/creatorId as zero/null for now.
|
||||||
|
const earthquakes = records.map(row => {
|
||||||
|
// You may want to add better parsing & validation depending on your actual data
|
||||||
|
return {
|
||||||
|
date: new Date(row.Date),
|
||||||
|
location: row.Location,
|
||||||
|
magnitude: parseFloat(row.Magnitude),
|
||||||
|
latitude: row.Latitude,
|
||||||
|
longitude: row.Longitude,
|
||||||
|
depth: parseFloat(row.Depth.replace(" km", "")),
|
||||||
|
// todo add creatorId
|
||||||
|
creatorId: null
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. Bulk create earthquakes in database:
|
||||||
|
// Consider chunking if your CSV is large!
|
||||||
|
await prisma.earthquake.createMany({
|
||||||
|
data: earthquakes,
|
||||||
|
skipDuplicates: true, // in case the route is called twice
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json({ success: true, count: earthquakes.length });
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error);
|
||||||
|
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
|
||||||
|
} finally {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,11 +1,11 @@
|
|||||||
import bcrypt from 'bcrypt';
|
import bcryptjs from "bcryptjs";
|
||||||
import { SignJWT } from 'jose';
|
import { SignJWT } from "jose";
|
||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from "@prisma/client";
|
||||||
import { env } from '@utils/env';
|
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;
|
||||||
@ -33,7 +33,7 @@ export async function POST(req: Request) {
|
|||||||
user = findUserByEmail(userData, email);
|
user = findUserByEmail(userData, email);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user && bcrypt.compareSync(password, usingPrisma ? user.hashedPassword : user.password)) {
|
if (user && bcryptjs.compareSync(password, usingPrisma ? user.passwordHash : user.password)) {
|
||||||
// todo remove password from returned user
|
// todo remove password from returned user
|
||||||
|
|
||||||
// get user and relations
|
// get user and relations
|
||||||
@ -45,12 +45,12 @@ export async function POST(req: Request) {
|
|||||||
include: {
|
include: {
|
||||||
earthquakes: true,
|
earthquakes: true,
|
||||||
observatories: true,
|
observatories: true,
|
||||||
artifacts: true,
|
artefacts: true,
|
||||||
superior: true,
|
superior: true,
|
||||||
subordinates: true,
|
subordinates: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
purchasedArtifacts: true,
|
purchasedArtefacts: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import bcrypt from "bcrypt";
|
import bcryptjs from "bcryptjs";
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
import { PrismaClient } from "@prisma/client";
|
import { PrismaClient } from "@prisma/client";
|
||||||
@ -56,7 +56,7 @@ export async function POST(req: Request) {
|
|||||||
return NextResponse.json({ message: "Password check script failure" }, { status: 500 });
|
return NextResponse.json({ message: "Password check script failure" }, { status: 500 });
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const passwordHash = await bcrypt.hash(password, 10);
|
const passwordHash = await bcryptjs.hash(password, 10);
|
||||||
if (usingPrisma) {
|
if (usingPrisma) {
|
||||||
// todo add sending back newUser
|
// todo add sending back newUser
|
||||||
const newUser = await prisma.user.create({
|
const newUser = await prisma.user.create({
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from "@prisma/client";
|
||||||
import { env } from '@utils/env';
|
import { env } from "@utils/env";
|
||||||
import { verifyJwt } from '@utils/verifyJwt';
|
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();
|
||||||
|
|
||||||
// Artifact type
|
// Artefact type
|
||||||
interface Artifact {
|
interface Artefact {
|
||||||
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 warehouseArtifacts: Artifact[] = [
|
const warehouseArtefacts: Artefact[] = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "Solidified Lava Chunk",
|
name: "Solidified Lava Chunk",
|
||||||
@ -87,17 +87,17 @@ export async function POST(req: Request) {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
let artifacts;
|
let artefacts;
|
||||||
if (usingPrisma) artifacts = await prisma.artifacts.findMany();
|
if (usingPrisma) artefacts = await prisma.artefacts.findMany();
|
||||||
|
|
||||||
if (artifacts) {
|
if (artefacts) {
|
||||||
return NextResponse.json({ message: "Got artifacts successfully", artifacts }, { status: 200 });
|
return NextResponse.json({ message: "Got artefacts successfully", artefacts }, { status: 200 });
|
||||||
} else {
|
} else {
|
||||||
return NextResponse.json({ message: "Got earthquakes successfully", earthquakes: warehouseArtifacts }, { status: 200 });
|
return NextResponse.json({ message: "Got earthquakes successfully", earthquakes: warehouseArtefacts }, { 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 artifacts endpoint:", error);
|
console.error("Error in artefacts 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();
|
||||||
|
|||||||
@ -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({
|
||||||
@ -37,8 +37,8 @@ const ContactUs = () => {
|
|||||||
{/* 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 max-w-2xl text-neutral-200 mb-6">
|
<p className="text-lg text-center max-w-2xl text-neutral-200 mb-6">
|
||||||
Have questions or concerns about earthquakes, observatories or artifacts? Contact us via phone, email, social media or using the form below with the relevant
|
Have questions or concerns about earthquakes, observatories or artefacts? Contact us via phone, email, social media or
|
||||||
contact details.
|
using the form below with the relevant contact details.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Content Section */}
|
{/* Content Section */}
|
||||||
|
|||||||
@ -23,17 +23,17 @@ const store = createStore<StoreModel>({
|
|||||||
conversionRates: { GBP: 0.85, USD: 1.14, EUR: 1 },
|
conversionRates: { GBP: 0.85, USD: 1.14, EUR: 1 },
|
||||||
tickers: { GBP: "£", USD: "$", EUR: "€" },
|
tickers: { GBP: "£", USD: "$", EUR: "€" },
|
||||||
},
|
},
|
||||||
user: null,
|
// user: null,
|
||||||
// user: {
|
user: {
|
||||||
// id: 123456,
|
id: 123456,
|
||||||
// createdAt: new Date(8.64e15),
|
createdAt: new Date(8.64e15),
|
||||||
// email: "tim.howitz@dyson.com",
|
email: "emily.neighbour@dyson.com",
|
||||||
// passwordHash: "",
|
passwordHash: "",
|
||||||
// name: "Tim Howitz",
|
name: "Emily Neighbour",
|
||||||
// role: "ADMIN",
|
role: "ADMIN",
|
||||||
// scientist: undefined,
|
scientist: undefined,
|
||||||
// purchasedArtifacts: [],
|
purchasedArtefacts: [],
|
||||||
// },
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
|
|||||||
@ -1,34 +1,32 @@
|
|||||||
"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"
|
className="relative bg-fixed bg-cover bg-center text-white "
|
||||||
style={{ backgroundImage: "url('destruction.jpg')" }}
|
style={{ backgroundImage: "url('destruction.jpg')", height: 845, overflow: "hidden" }}
|
||||||
>
|
>
|
||||||
{/* 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 */}
|
{/* Centered content */}
|
||||||
<div className="relative z-20 flex flex-col items-center justify-center min-h-screen">
|
<div className="relative z-20 flex flex-col items-center justify-center h-full py-auto">
|
||||||
{/* Title & Mission Statement */}
|
{/* Title & Mission Statement */}
|
||||||
<div className="mb-10 flex flex-col items-center">
|
<div className="mb-10 flex flex-col items-center">
|
||||||
<h1 className="text-4xl font-bold text-center tracking-tight mb-2 drop-shadow-lg">
|
<h1 className="text-4xl font-bold text-center tracking-tight mb-2 drop-shadow-lg">Our Mission</h1>
|
||||||
Our Mission
|
<p className="text-lg text-center max-w-2xl text-white drop-shadow-md">Earthquake awareness accessible for everyone</p>
|
||||||
</h1>
|
|
||||||
<p className="text-lg text-center max-w-2xl text-white drop-shadow-md">
|
|
||||||
Earthquake awareness accessible for everyone
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
{/* Content Area */}
|
{/* Content Area */}
|
||||||
<div className="max-w-5xl w-full p-8 bg-white bg-opacity-90 shadow-xl rounded-3xl">
|
<div className="max-w-5xl w-full p-8 bg-white bg-opacity-90 shadow-xl rounded-3xl">
|
||||||
<p className="text-xl text-black leading-relaxed mb-6 max-w-3xl mx-auto">
|
<p className="text-xl text-black leading-relaxed mb-6 max-w-3xl mx-auto">
|
||||||
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
|
At <span className="font-bold text-black">Tremor Tracker</span>, we empower communities worldwide to understand where
|
||||||
and recovery. Education, cutting-edge research, and innovative technology combine together.
|
and why earthquakes occur to enable better preparation and recovery. Education, cutting-edge research, and innovative
|
||||||
|
technology combine together.
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xl text-black leading-relaxed mb-8 max-w-3xl mx-auto">
|
<p className="text-xl text-black leading-relaxed mb-8 max-w-3xl mx-auto">
|
||||||
We combine scientific insights with public awareness, delivering resources, tools, and real-time updates to enhance
|
We combine scientific insights with public awareness, delivering resources, tools, and real-time updates to enhance
|
||||||
preparedness for earthquakes in order to save lives, improve recovery efficiency, and build resilience against seismic events.
|
preparedness for earthquakes in order to save lives, improve recovery efficiency, and build resilience against seismic
|
||||||
|
events.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col md:flex-row md:justify-evenly gap-8 mt-8">
|
<div className="flex flex-col md:flex-row md:justify-evenly gap-8 mt-8">
|
||||||
<div className="flex flex-col items-center p-6 hover:bg-white hover:bg-opacity-10 rounded-xl transition-colors duration-300">
|
<div className="flex flex-col items-center p-6 hover:bg-white hover:bg-opacity-10 rounded-xl transition-colors duration-300">
|
||||||
|
|||||||
@ -39,10 +39,10 @@ export default function Home() {
|
|||||||
href="/shop"
|
href="/shop"
|
||||||
className="flex flex-col items-center p-6 hover:bg-white hover:bg-opacity-10 rounded-xl transition-colors duration-300"
|
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="/artifactIcon.jpg" alt="Technology Icon" className="h-40 w-40 mb-4" />
|
<Image height={100} width={100} src="/artefactIcon.jpg" alt="Technology Icon" className="h-40 w-40 mb-4" />
|
||||||
<h3 className="text-xl font-bold text-black mb-4">Artifacts</h3>
|
<h3 className="text-xl font-bold text-black mb-4">Artefacts</h3>
|
||||||
<p className="text-md text-black text-center max-w-xs opacity-90">
|
<p className="text-md text-black text-center max-w-xs opacity-90">
|
||||||
View or purchase recently discovered artifacts from seismic events
|
View or purchase recently discovered artefacts from seismic events
|
||||||
</p>
|
</p>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,19 +1,22 @@
|
|||||||
"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 Artifact from '@appTypes/Artifact';
|
import Artefact from "@appTypes/Artefact";
|
||||||
import { Currency } from '@appTypes/StoreModel';
|
import { Currency } from "@appTypes/StoreModel";
|
||||||
import { useStoreState } from '@hooks/store';
|
import { useStoreState } from "@hooks/store";
|
||||||
|
|
||||||
// Artifacts Data
|
// Artefacts Data
|
||||||
const artifacts: Artifact[] = [
|
const artefacts: Artefact[] = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "Golden Scarab",
|
name: "Golden Scarab",
|
||||||
description: "An ancient Egyptian artifact symbolizing rebirth.",
|
description: "An ancient Egyptian artefact symbolizing rebirth.",
|
||||||
location: "Cairo, Egypt",
|
location: "Cairo, Egypt",
|
||||||
image: "/artifact1.jpg",
|
earthquakeID: "h",
|
||||||
|
observatory: "jhd",
|
||||||
|
dateReleased: "12/02/2025",
|
||||||
|
image: "/artefact1.jpg",
|
||||||
price: 150,
|
price: 150,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -21,7 +24,10 @@ const artifacts: Artifact[] = [
|
|||||||
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: "/artifact2.jpg",
|
earthquakeID: "h",
|
||||||
|
observatory: "jhd",
|
||||||
|
dateReleased: "12/02/2025",
|
||||||
|
image: "/artefact2.jpg",
|
||||||
price: 200,
|
price: 200,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -29,7 +35,10 @@ const artifacts: Artifact[] = [
|
|||||||
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: "/artifact3.jpg",
|
earthquakeID: "h",
|
||||||
|
observatory: "jhd",
|
||||||
|
dateReleased: "12/02/2025",
|
||||||
|
image: "/artefact3.jpg",
|
||||||
price: 120,
|
price: 120,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -37,7 +46,10 @@ const artifacts: Artifact[] = [
|
|||||||
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: "/artifact4.jpg",
|
earthquakeID: "h",
|
||||||
|
observatory: "jhd",
|
||||||
|
dateReleased: "12/02/2025",
|
||||||
|
image: "/artefact4.jpg",
|
||||||
price: 80,
|
price: 80,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -45,7 +57,10 @@ const artifacts: Artifact[] = [
|
|||||||
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: "/artifact5.jpg",
|
earthquakeID: "h",
|
||||||
|
observatory: "jhd",
|
||||||
|
dateReleased: "12/02/2025",
|
||||||
|
image: "/artefact5.jpg",
|
||||||
price: 300,
|
price: 300,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -53,7 +68,10 @@ const artifacts: Artifact[] = [
|
|||||||
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: "/artifact6.jpg",
|
earthquakeID: "h",
|
||||||
|
observatory: "jhd",
|
||||||
|
dateReleased: "12/02/2025",
|
||||||
|
image: "/artefact6.jpg",
|
||||||
price: 250,
|
price: 250,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -61,7 +79,10 @@ const artifacts: Artifact[] = [
|
|||||||
name: "Incan Pendant",
|
name: "Incan Pendant",
|
||||||
description: "Represents the Sun God Inti.",
|
description: "Represents the Sun God Inti.",
|
||||||
location: "India",
|
location: "India",
|
||||||
image: "/artifact7.jpg",
|
earthquakeID: "h",
|
||||||
|
observatory: "jhd",
|
||||||
|
dateReleased: "12/02/2025",
|
||||||
|
image: "/artefact7.jpg",
|
||||||
price: 175,
|
price: 175,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -69,7 +90,10 @@ const artifacts: Artifact[] = [
|
|||||||
name: "Persian Carpet Fragment",
|
name: "Persian Carpet Fragment",
|
||||||
description: "Ancient Persian artistry.",
|
description: "Ancient Persian artistry.",
|
||||||
location: "Petra, Jordan",
|
location: "Petra, Jordan",
|
||||||
image: "/artifact8.jpg",
|
earthquakeID: "h",
|
||||||
|
observatory: "jhd",
|
||||||
|
dateReleased: "12/02/2025",
|
||||||
|
image: "/artefact8.jpg",
|
||||||
price: 400,
|
price: 400,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -77,7 +101,10 @@ const artifacts: Artifact[] = [
|
|||||||
name: "Stone Buddha",
|
name: "Stone Buddha",
|
||||||
description: "Authentic stone Buddha carving.",
|
description: "Authentic stone Buddha carving.",
|
||||||
location: "India",
|
location: "India",
|
||||||
image: "/artifact9.jpg",
|
earthquakeID: "h",
|
||||||
|
observatory: "jhd",
|
||||||
|
dateReleased: "12/02/2025",
|
||||||
|
image: "/artefact9.jpg",
|
||||||
price: 220,
|
price: 220,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -85,7 +112,10 @@ const artifacts: Artifact[] = [
|
|||||||
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: "/artifact10.jpg",
|
earthquakeID: "h",
|
||||||
|
observatory: "jhd",
|
||||||
|
dateReleased: "12/02/2025",
|
||||||
|
image: "/artefact10.jpg",
|
||||||
price: 150,
|
price: 150,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -93,7 +123,10 @@ const artifacts: Artifact[] = [
|
|||||||
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: "/artifact11.jpg",
|
earthquakeID: "h",
|
||||||
|
observatory: "jhd",
|
||||||
|
dateReleased: "12/02/2025",
|
||||||
|
image: "/artefact11.jpg",
|
||||||
price: 500,
|
price: 500,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -101,7 +134,10 @@ const artifacts: Artifact[] = [
|
|||||||
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: "/artifact12.jpg",
|
earthquakeID: "h",
|
||||||
|
observatory: "jhd",
|
||||||
|
dateReleased: "12/02/2025",
|
||||||
|
image: "/artefact12.jpg",
|
||||||
price: 300,
|
price: 300,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -109,15 +145,21 @@ const artifacts: Artifact[] = [
|
|||||||
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: "/artifact13.jpg",
|
earthquakeID: "h",
|
||||||
|
observatory: "jhd",
|
||||||
|
dateReleased: "12/02/2025",
|
||||||
|
image: "/artefact13.jpg",
|
||||||
price: 250,
|
price: 250,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 14,
|
id: 14,
|
||||||
name: "Crystal Skull",
|
name: "Crystal Skull",
|
||||||
description: "A mystical pre-Columbian artifact.",
|
description: "A mystical pre-Columbian artefact.",
|
||||||
location: "Colombia",
|
location: "Colombia",
|
||||||
image: "/artifact14.jpg",
|
earthquakeID: "h",
|
||||||
|
observatory: "jhd",
|
||||||
|
dateReleased: "12/02/2025",
|
||||||
|
image: "/artefact14.jpg",
|
||||||
price: 1000,
|
price: 1000,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -125,7 +167,10 @@ const artifacts: Artifact[] = [
|
|||||||
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: "/artifact15.jpg",
|
earthquakeID: "h",
|
||||||
|
observatory: "jhd",
|
||||||
|
dateReleased: "12/02/2025",
|
||||||
|
image: "/artefact15.jpg",
|
||||||
price: 400,
|
price: 400,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -133,30 +178,30 @@ const artifacts: Artifact[] = [
|
|||||||
name: "Medieval Helmet Fragment",
|
name: "Medieval Helmet Fragment",
|
||||||
description: "A fragment of a medieval helmet.",
|
description: "A fragment of a medieval helmet.",
|
||||||
location: "Normandy, France",
|
location: "Normandy, France",
|
||||||
image: "/artifact16.jpg",
|
earthquakeID: "h",
|
||||||
|
observatory: "jhd",
|
||||||
|
dateReleased: "12/02/2025",
|
||||||
|
image: "/artefact16.jpg",
|
||||||
price: 500,
|
price: 500,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function Shop() {
|
export default function Shop() {
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [selectedArtifact, setSelectedArtifact] = useState<Artifact | null>(null);
|
const [selectedArtefact, setSelectedArtefact] = useState<Artefact | null>(null);
|
||||||
const artifactsPerPage = 12;
|
const artefactsPerPage = 12;
|
||||||
const indexOfLastArtifact = currentPage * artifactsPerPage;
|
const indexOfLastArtefact = currentPage * artefactsPerPage;
|
||||||
const indexOfFirstArtifact = indexOfLastArtifact - artifactsPerPage;
|
const indexOfFirstArtefact = indexOfLastArtefact - artefactsPerPage;
|
||||||
const currentArtifacts = artifacts.slice(indexOfFirstArtifact, indexOfLastArtifact);
|
const currentArtefacts = artefacts.slice(indexOfFirstArtefact, indexOfLastArtefact);
|
||||||
|
|
||||||
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(
|
const convertPrice = useCallback((price: number, currency: Currency) => (price * conversionRates[currency]).toFixed(2), []);
|
||||||
(price: number, currency: Currency) => (price * conversionRates[currency]).toFixed(2),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleNextPage = () => {
|
const handleNextPage = () => {
|
||||||
if (indexOfLastArtifact < artifacts.length) {
|
if (indexOfLastArtefact < artefacts.length) {
|
||||||
setCurrentPage((prev) => prev + 1);
|
setCurrentPage((prev) => prev + 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -166,11 +211,11 @@ export default function Shop() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function Modal({ artifact }: { artifact: Artifact }) {
|
function Modal({ artefact }: { artefact: Artefact }) {
|
||||||
if (!artifact) return null;
|
if (!artefact) return null;
|
||||||
const handleOverlayClick = (e: { target: any; currentTarget: any }) => {
|
const handleOverlayClick = (e: { target: any; currentTarget: any }) => {
|
||||||
if (e.target === e.currentTarget) {
|
if (e.target === e.currentTarget) {
|
||||||
setSelectedArtifact(null);
|
setSelectedArtefact(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
@ -179,20 +224,23 @@ export default function Shop() {
|
|||||||
onClick={handleOverlayClick}
|
onClick={handleOverlayClick}
|
||||||
>
|
>
|
||||||
<div className="bg-white rounded-xl shadow-2xl max-w-lg w-full p-6">
|
<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>
|
<h3 className="text-2xl font-bold mb-4">{artefact.name}</h3>
|
||||||
<Image
|
<Image
|
||||||
height={5000}
|
height={5000}
|
||||||
width={5000}
|
width={5000}
|
||||||
src={artifact.image}
|
src={artefact.image}
|
||||||
alt={artifact.name}
|
alt={artefact.name}
|
||||||
className="w-full h-64 object-cover rounded-md"
|
className="w-full h-64 object-cover rounded-md"
|
||||||
/>
|
/>
|
||||||
<p className="text-xl font-bold">
|
<p className="text-xl font-bold">
|
||||||
{currencyTickers[selectedCurrency]}
|
{currencyTickers[selectedCurrency]}
|
||||||
{convertPrice(artifact.price, selectedCurrency)}
|
{convertPrice(artefact.price, selectedCurrency)}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-neutral-600 mt-2">{artifact.description}</p>
|
<p className="text-neutral-600 mt-2">{artefact.description}</p>
|
||||||
<p className="text-neutral-500 font-bold mt-1">Location: {artifact.location}</p>
|
<p className="text-neutral-500 font-bold mt-1">Location: {artefact.location}</p>
|
||||||
|
<p className="text-neutral-500 mb-2">{artefact.earthquakeID}</p>
|
||||||
|
<p className="text-neutral-500 mb-2">{artefact.observatory}</p>
|
||||||
|
<p className="text-neutral-500 mb-2">{artefact.dateReleased}</p>
|
||||||
<div className="flex justify-end gap-4 mt-4 mr-2">
|
<div className="flex justify-end gap-4 mt-4 mr-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => alert("Purchased Successfully!")}
|
onClick={() => alert("Purchased Successfully!")}
|
||||||
@ -206,19 +254,20 @@ export default function Shop() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ArtifactCard({ artifact }: { artifact: Artifact }) {
|
function ArtefactCard({ artefact }: { artefact: Artefact }) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex flex-col bg-white shadow-md rounded-md overflow-hidden cursor-pointer hover:scale-105 transition-transform"
|
className="flex flex-col bg-white shadow-md rounded-md overflow-hidden cursor-pointer hover:scale-105 transition-transform"
|
||||||
onClick={() => setSelectedArtifact(artifact)}
|
onClick={() => setSelectedArtefact(artefact)}
|
||||||
>
|
>
|
||||||
<img src={artifact.image} alt={artifact.name} className="w-full h-56 object-cover" />
|
<img src={artefact.image} alt={artefact.name} className="w-full h-56 object-cover" />
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<h3 className="text-lg font-semibold">{artifact.name}</h3>
|
<h3 className="text-lg font-semibold">{artefact.name}</h3>
|
||||||
<p className="text-neutral-500 mb-2">{artifact.location}</p>
|
<p className="text-neutral-500 mb-2">{artefact.location}</p>
|
||||||
|
<p className="text-neutral-500 mb-2">{artefact.earthquakeID}</p>
|
||||||
<p className="text-black font-bold text-md mt-2">
|
<p className="text-black font-bold text-md mt-2">
|
||||||
{currencyTickers[selectedCurrency]}
|
{currencyTickers[selectedCurrency]}
|
||||||
{convertPrice(artifact.price, selectedCurrency)}
|
{convertPrice(artefact.price, selectedCurrency)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -229,9 +278,9 @@ export default function Shop() {
|
|||||||
<div
|
<div
|
||||||
className="min-h-screen relative flex flex-col"
|
className="min-h-screen relative flex flex-col"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: "url('/artifacts.jpg')",
|
backgroundImage: "url('/artefacts.jpg')",
|
||||||
backgroundSize: 'cover',
|
backgroundSize: "cover",
|
||||||
backgroundPosition: 'center'
|
backgroundPosition: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Overlay */}
|
{/* Overlay */}
|
||||||
@ -239,16 +288,19 @@ export default function Shop() {
|
|||||||
<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">
|
||||||
{/* Title & Subheading */}
|
{/* Title & Subheading */}
|
||||||
<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-white mb-2 tracking-tight drop-shadow-lg">
|
||||||
Artifact 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 artifacts and collectibles from major seismic events from around the world - now available for purchase.
|
Discover extraordinary historical artefacts and collectibles from major seismic events from around the world - now
|
||||||
|
available for purchase.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Artifact Grid */}
|
{/* Artefact Grid */}
|
||||||
<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 */}
|
<div className="w-full max-w-7xl grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-10 p-2">
|
||||||
{currentArtifacts.map((artifact) => (
|
{" "}
|
||||||
<ArtifactCard key={artifact.id} artifact={artifact} />
|
{/* gap-10 for more spacing */}
|
||||||
|
{currentArtefacts.map((artefact) => (
|
||||||
|
<ArtefactCard key={artefact.id} artefact={artefact} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -266,9 +318,9 @@ export default function Shop() {
|
|||||||
<p className="mx-3 text-lg font-bold">{currentPage}</p>
|
<p className="mx-3 text-lg font-bold">{currentPage}</p>
|
||||||
<button
|
<button
|
||||||
onClick={handleNextPage}
|
onClick={handleNextPage}
|
||||||
disabled={indexOfLastArtifact >= artifacts.length}
|
disabled={indexOfLastArtefact >= artefacts.length}
|
||||||
className={`mx-2 px-4 py-1 bg-blue-500 text-white rounded-md font-bold shadow-md ${
|
className={`mx-2 px-4 py-1 bg-blue-500 text-white rounded-md font-bold shadow-md ${
|
||||||
indexOfLastArtifact >= artifacts.length ? "opacity-50 cursor-not-allowed" : "hover:bg-blue-600"
|
indexOfLastArtefact >= artefacts.length ? "opacity-50 cursor-not-allowed" : "hover:bg-blue-600"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Next →
|
Next →
|
||||||
@ -277,7 +329,7 @@ export default function Shop() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Modal */}
|
{/* Modal */}
|
||||||
{selectedArtifact && <Modal artifact={selectedArtifact} />}
|
{selectedArtefact && <Modal artefact={selectedArtefact} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -32,16 +32,14 @@ const teamMembers = [
|
|||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="min-h-screen relative flex flex-col items-center justify-center px-4 py-20"
|
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('tectonicPlate.jpg')", 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>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="relative z-10 flex flex-col items-center mb-1">
|
<div className="relative z-10 flex flex-col items-center mb-1">
|
||||||
<h1 className="text-4xl font-bold text-center text-white mb-4 tracking-tight drop-shadow-lg">
|
<h1 className="text-4xl font-bold text-center text-white mb-4 tracking-tight drop-shadow-lg">Meet the Team</h1>
|
||||||
Meet the Team
|
|
||||||
</h1>
|
|
||||||
<p className="text-lg text-center max-w-2xl text-white mb-12 drop-shadow-md">
|
<p className="text-lg text-center max-w-2xl text-white mb-12 drop-shadow-md">
|
||||||
Our world-class scientists and engineers drive innovation across the globe. Meet our four department heads:
|
Our world-class scientists and engineers drive innovation across the globe. Meet our four department heads:
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { Dispatch, SetStateAction, useMemo, useState } from 'react';
|
import { Dispatch, SetStateAction, useMemo, useState } from "react";
|
||||||
import { FaTimes } from 'react-icons/fa';
|
import { FaTimes } from "react-icons/fa";
|
||||||
import { FaCalendarPlus, FaCartShopping, FaWarehouse } from 'react-icons/fa6';
|
import { FaCalendarPlus, FaCartShopping, FaWarehouse } from "react-icons/fa6";
|
||||||
import { IoFilter, IoFilterCircleOutline, IoFilterOutline, IoToday } from 'react-icons/io5';
|
import { IoFilter, IoFilterCircleOutline, IoFilterOutline, IoToday } from "react-icons/io5";
|
||||||
|
|
||||||
// import type { Artifact } from "@prisma/client";
|
// import type { Artefact } from "@prisma/client";
|
||||||
|
|
||||||
interface Artifact {
|
interface Artefact {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
@ -18,8 +18,8 @@ interface Artifact {
|
|||||||
dateAdded: string;
|
dateAdded: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warehouse Artifacts Data
|
// Warehouse Artefacts Data
|
||||||
const warehouseArtifacts: Artifact[] = [
|
const warehouseArtefacts: Artefact[] = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "Solidified Lava Chunk",
|
name: "Solidified Lava Chunk",
|
||||||
@ -140,14 +140,14 @@ function FilterInput({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Table Component
|
// Table Component
|
||||||
function ArtifactTable({
|
function ArtefactTable({
|
||||||
artifacts,
|
artefacts,
|
||||||
filters,
|
filters,
|
||||||
setFilters,
|
setFilters,
|
||||||
setEditArtifact,
|
setEditArtefact,
|
||||||
clearSort,
|
clearSort,
|
||||||
}: {
|
}: {
|
||||||
artifacts: Artifact[];
|
artefacts: Artefact[];
|
||||||
filters: Record<string, string>;
|
filters: Record<string, string>;
|
||||||
setFilters: Dispatch<
|
setFilters: Dispatch<
|
||||||
SetStateAction<{
|
SetStateAction<{
|
||||||
@ -162,15 +162,15 @@ function ArtifactTable({
|
|||||||
dateAdded: string;
|
dateAdded: string;
|
||||||
}>
|
}>
|
||||||
>;
|
>;
|
||||||
setEditArtifact: (artifact: Artifact) => void;
|
setEditArtefact: (artefact: Artefact) => void;
|
||||||
clearSort: () => void;
|
clearSort: () => void;
|
||||||
}) {
|
}) {
|
||||||
const [sortConfig, setSortConfig] = useState<{
|
const [sortConfig, setSortConfig] = useState<{
|
||||||
key: keyof Artifact;
|
key: keyof Artefact;
|
||||||
direction: "asc" | "desc";
|
direction: "asc" | "desc";
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
const handleSort = (key: keyof Artifact) => {
|
const handleSort = (key: keyof Artefact) => {
|
||||||
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 ArtifactTable({
|
|||||||
clearSort();
|
clearSort();
|
||||||
};
|
};
|
||||||
|
|
||||||
const sortedArtifacts = useMemo(() => {
|
const sortedArtefacts = useMemo(() => {
|
||||||
if (!sortConfig) return artifacts;
|
if (!sortConfig) return artefacts;
|
||||||
const sorted = [...artifacts].sort((a, b) => {
|
const sorted = [...artefacts].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 ArtifactTable({
|
|||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
return sorted;
|
return sorted;
|
||||||
}, [artifacts, sortConfig]);
|
}, [artefacts, sortConfig]);
|
||||||
|
|
||||||
const columns: { label: string; key: keyof Artifact; width: string }[] = [
|
const columns: { label: string; key: keyof Artefact; 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 ArtifactTable({
|
|||||||
{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 Artifact)}>
|
<div className="flex h-full items-center" onClick={() => handleSort(key as keyof Artefact)}>
|
||||||
<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 ArtifactTable({
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{sortedArtifacts.map((artifact) => (
|
{sortedArtefacts.map((artefact) => (
|
||||||
<tr
|
<tr
|
||||||
key={artifact.id}
|
key={artefact.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={() => setEditArtifact(artifact)}
|
onClick={() => setEditArtefact(artefact)}
|
||||||
>
|
>
|
||||||
{columns.map(({ key, width }) => (
|
{columns.map(({ key, width }) => (
|
||||||
<td
|
<td
|
||||||
@ -263,18 +263,18 @@ function ArtifactTable({
|
|||||||
style={{ width }}
|
style={{ width }}
|
||||||
>
|
>
|
||||||
{key === "isRequired"
|
{key === "isRequired"
|
||||||
? artifact.isRequired
|
? artefact.isRequired
|
||||||
? "Yes"
|
? "Yes"
|
||||||
: "No"
|
: "No"
|
||||||
: key === "isSold"
|
: key === "isSold"
|
||||||
? artifact.isSold
|
? artefact.isSold
|
||||||
? "Yes"
|
? "Yes"
|
||||||
: "No"
|
: "No"
|
||||||
: key === "isCollected"
|
: key === "isCollected"
|
||||||
? artifact.isCollected
|
? artefact.isCollected
|
||||||
? "Yes"
|
? "Yes"
|
||||||
: "No"
|
: "No"
|
||||||
: artifact[key]}
|
: artefact[key]}
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
@ -284,7 +284,7 @@ function ArtifactTable({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modal Component for Logging Artifact
|
// Modal Component for Logging Artefact
|
||||||
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 artifact. Please try again.");
|
setError("Failed to log artefact. 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 Artifact</h3>
|
<h3 className="text-lg font-semibold mb-4 text-neutral-800">Log New Artefact</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="Artifact Name"
|
aria-label="Artefact 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="Artifact Description"
|
aria-label="Artefact 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="Artifact Location"
|
aria-label="Artefact 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 Artifact"
|
aria-label="Required Artefact"
|
||||||
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 Artifact"
|
"Log Artefact"
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -524,16 +524,16 @@ function BulkLogModal({ onClose }: { onClose: () => void }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modal Component for Editing Artifact
|
// Modal Component for Editing Artefact
|
||||||
function EditModal({ artifact, onClose }: { artifact: Artifact; onClose: () => void }) {
|
function EditModal({ artefact, onClose }: { artefact: Artefact; onClose: () => void }) {
|
||||||
const [name, setName] = useState(artifact.name);
|
const [name, setName] = useState(artefact.name);
|
||||||
const [description, setDescription] = useState(artifact.description);
|
const [description, setDescription] = useState(artefact.description);
|
||||||
const [location, setLocation] = useState(artifact.location);
|
const [location, setLocation] = useState(artefact.location);
|
||||||
const [earthquakeId, setEarthquakeId] = useState(artifact.earthquakeId);
|
const [earthquakeId, setEarthquakeId] = useState(artefact.earthquakeId);
|
||||||
const [isRequired, setIsRequired] = useState(artifact.isRequired);
|
const [isRequired, setIsRequired] = useState(artefact.isRequired);
|
||||||
const [isSold, setIsSold] = useState(artifact.isSold);
|
const [isSold, setIsSold] = useState(artefact.isSold);
|
||||||
const [isCollected, setIsCollected] = useState(artifact.isCollected);
|
const [isCollected, setIsCollected] = useState(artefact.isCollected);
|
||||||
const [dateAdded, setDateAdded] = useState(artifact.dateAdded);
|
const [dateAdded, setDateAdded] = useState(artefact.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({ artifact, onClose }: { artifact: Artifact; 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 artifact ${name}`);
|
alert(`Updated artefact ${name}`);
|
||||||
onClose();
|
onClose();
|
||||||
} catch {
|
} catch {
|
||||||
setError("Failed to update artifact. Please try again.");
|
setError("Failed to update artefact. Please try again.");
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
}
|
}
|
||||||
@ -566,7 +566,7 @@ function EditModal({ artifact, onClose }: { artifact: Artifact; 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 Artifact</h3>
|
<h3 className="text-lg font-semibold mb-4 text-neutral-800">Edit Artefact</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({ artifact, onClose }: { artifact: Artifact; 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="Artifact Name"
|
aria-label="Artefact 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="Artifact Description"
|
aria-label="Artefact Description"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
@ -589,7 +589,7 @@ function EditModal({ artifact, onClose }: { artifact: Artifact; 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="Artifact Location"
|
aria-label="Artefact Location"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
@ -606,7 +606,7 @@ function EditModal({ artifact, onClose }: { artifact: Artifact; 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 Artifact"
|
aria-label="Required Artefact"
|
||||||
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({ artifact, onClose }: { artifact: Artifact; 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 Artifact"
|
aria-label="Sold Artefact"
|
||||||
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({ artifact, onClose }: { artifact: Artifact; 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 Artifact"
|
aria-label="Collected Artefact"
|
||||||
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({ artifact, onClose }: { artifact: Artifact; onClose: () => v
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Filter Logic
|
// Filter Logic
|
||||||
const applyFilters = (artifacts: Artifact[], filters: Record<string, string>): Artifact[] => {
|
const applyFilters = (artefacts: Artefact[], filters: Record<string, string>): Artefact[] => {
|
||||||
return artifacts.filter((artifact) => {
|
return artefacts.filter((artefact) => {
|
||||||
return (
|
return (
|
||||||
(filters.id === "" || artifact.id.toString().includes(filters.id)) &&
|
(filters.id === "" || artefact.id.toString().includes(filters.id)) &&
|
||||||
(filters.name === "" || artifact.name.toLowerCase().includes(filters.name.toLowerCase())) &&
|
(filters.name === "" || artefact.name.toLowerCase().includes(filters.name.toLowerCase())) &&
|
||||||
(filters.earthquakeId === "" || artifact.earthquakeId.toLowerCase().includes(filters.earthquakeId.toLowerCase())) &&
|
(filters.earthquakeId === "" || artefact.earthquakeId.toLowerCase().includes(filters.earthquakeId.toLowerCase())) &&
|
||||||
(filters.location === "" || artifact.location.toLowerCase().includes(filters.location.toLowerCase())) &&
|
(filters.location === "" || artefact.location.toLowerCase().includes(filters.location.toLowerCase())) &&
|
||||||
(filters.description === "" || artifact.description.toLowerCase().includes(filters.description.toLowerCase())) &&
|
(filters.description === "" || artefact.description.toLowerCase().includes(filters.description.toLowerCase())) &&
|
||||||
(filters.isRequired === "" || (filters.isRequired === "true" ? artifact.isRequired : !artifact.isRequired)) &&
|
(filters.isRequired === "" || (filters.isRequired === "true" ? artefact.isRequired : !artefact.isRequired)) &&
|
||||||
(filters.isSold === "" || (filters.isSold === "true" ? artifact.isSold : !artifact.isSold)) &&
|
(filters.isSold === "" || (filters.isSold === "true" ? artefact.isSold : !artefact.isSold)) &&
|
||||||
(filters.isCollected === "" || (filters.isCollected === "true" ? artifact.isCollected : !artifact.isCollected)) &&
|
(filters.isCollected === "" || (filters.isCollected === "true" ? artefact.isCollected : !artefact.isCollected)) &&
|
||||||
(filters.dateAdded === "" || artifact.dateAdded === filters.dateAdded)
|
(filters.dateAdded === "" || artefact.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 [editArtifact, setEditArtifact] = useState<Artifact | null>(null);
|
const [editArtefact, setEditArtefact] = useState<Artefact | 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 Artifact;
|
key: keyof Artefact;
|
||||||
direction: "asc" | "desc";
|
direction: "asc" | "desc";
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
const artifactsPerPage = 10;
|
const artefactsPerPage = 10;
|
||||||
const indexOfLastArtifact = currentPage * artifactsPerPage;
|
const indexOfLastArtefact = currentPage * artefactsPerPage;
|
||||||
const indexOfFirstArtifact = indexOfLastArtifact - artifactsPerPage;
|
const indexOfFirstArtefact = indexOfLastArtefact - artefactsPerPage;
|
||||||
|
|
||||||
// Apply filters with loading state
|
// Apply filters with loading state
|
||||||
const filteredArtifacts = useMemo(() => {
|
const filteredArtefacts = useMemo(() => {
|
||||||
setIsFiltering(true);
|
setIsFiltering(true);
|
||||||
const result = applyFilters(warehouseArtifacts, filters);
|
const result = applyFilters(warehouseArtefacts, filters);
|
||||||
setIsFiltering(false);
|
setIsFiltering(false);
|
||||||
return result;
|
return result;
|
||||||
}, [filters]);
|
}, [filters]);
|
||||||
|
|
||||||
const currentArtifacts = filteredArtifacts.slice(indexOfFirstArtifact, indexOfLastArtifact);
|
const currentArtefacts = filteredArtefacts.slice(indexOfFirstArtefact, indexOfLastArtefact);
|
||||||
|
|
||||||
// Overview stats
|
// Overview stats
|
||||||
const totalArtifacts = warehouseArtifacts.length;
|
const totalArtefacts = warehouseArtefacts.length;
|
||||||
const today = "2025-05-04";
|
const today = "2025-05-04";
|
||||||
const artifactsAddedToday = warehouseArtifacts.filter((a) => a.dateAdded === today).length;
|
const artefactsAddedToday = warehouseArtefacts.filter((a) => a.dateAdded === today).length;
|
||||||
const artifactsSoldToday = warehouseArtifacts.filter((a) => a.isSold && a.dateAdded === today).length;
|
const artefactsSoldToday = warehouseArtefacts.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 Artifacts: <span className="font-semibold ml-1">{totalArtifacts}</span>
|
Total Artefacts: <span className="font-semibold ml-1">{totalArtefacts}</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">{artifactsAddedToday}</span>
|
Added Today: <span className="font-semibold ml-1">{artefactsAddedToday}</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">{artifactsSoldToday}</span>
|
Sold Today: <span className="font-semibold ml-1">{artefactsSoldToday}</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 Artifact
|
Log Single Artefact
|
||||||
</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">
|
||||||
<ArtifactTable
|
<ArtefactTable
|
||||||
artifacts={currentArtifacts}
|
artefacts={currentArtefacts}
|
||||||
filters={filters}
|
filters={filters}
|
||||||
setFilters={setFilters}
|
setFilters={setFilters}
|
||||||
setEditArtifact={setEditArtifact}
|
setEditArtefact={setEditArtefact}
|
||||||
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)} />}
|
||||||
{editArtifact && <EditModal artifact={editArtifact} onClose={() => setEditArtifact(null)} />}
|
{editArtefact && <EditModal artefact={editArtefact} onClose={() => setEditArtefact(null)} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname, useRouter } from "next/navigation";
|
||||||
import { Dispatch, SetStateAction, useMemo, useState } from "react";
|
import { Dispatch, SetStateAction, useMemo, useState } from "react";
|
||||||
import { FaRegUserCircle } from "react-icons/fa";
|
import { FaRegUserCircle } from "react-icons/fa";
|
||||||
|
|
||||||
import { Currency } from "@appTypes/StoreModel";
|
import { Currency } from "@appTypes/StoreModel";
|
||||||
import AuthModal from "@components/AuthModal";
|
import AuthModal from "@components/AuthModal";
|
||||||
import { useStoreActions, useStoreState } from "@hooks/store";
|
import { useStoreActions, useStoreState } from "@hooks/store";
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
|
|
||||||
export default function Navbar({}: // currencySelector,
|
export default function Navbar({}: // currencySelector,
|
||||||
{
|
{
|
||||||
@ -18,7 +17,7 @@ export default function Navbar({}: // currencySelector,
|
|||||||
const user = useStoreState((state) => state.user);
|
const user = useStoreState((state) => state.user);
|
||||||
const selectedCurrency = useStoreState((state) => state.currency.selectedCurrency);
|
const selectedCurrency = useStoreState((state) => state.currency.selectedCurrency);
|
||||||
const setSelectedCurrency = useStoreActions((actions) => actions.currency.setSelectedCurrency);
|
const setSelectedCurrency = useStoreActions((actions) => actions.currency.setSelectedCurrency);
|
||||||
const navOptions = useMemo(() => ["Earthquakes", "Observatories", "Shop","Administrator"], []);
|
const navOptions = useMemo(() => ["Earthquakes", "Observatories", "Shop"], []);
|
||||||
// const navOptions = useMemo(() => ["Earthquakes"], []);
|
// const navOptions = useMemo(() => ["Earthquakes"], []);
|
||||||
const aboutDropdown = ["Contact Us", "Our Mission", "The Team"];
|
const aboutDropdown = ["Contact Us", "Our Mission", "The Team"];
|
||||||
// { label: "Our Mission", path: "/our-mission" },
|
// { label: "Our Mission", path: "/our-mission" },
|
||||||
@ -109,7 +108,6 @@ export default function Navbar({}: // currencySelector,
|
|||||||
<NavbarButton name="About Us" href="/about" dropdownItems={aboutDropdown} />
|
<NavbarButton name="About Us" href="/about" dropdownItems={aboutDropdown} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-grow" />
|
<div className="flex-grow" />
|
||||||
|
|
||||||
{pathname.includes("shop") && (
|
{pathname.includes("shop") && (
|
||||||
<button className="flex items-center justify-center mr-3 py-4 relative group">
|
<button className="flex items-center justify-center mr-3 py-4 relative group">
|
||||||
<span className={`px-4 py-1.5 rounded-md transition-colors`}>{selectedCurrency}</span>
|
<span className={`px-4 py-1.5 rounded-md transition-colors`}>{selectedCurrency}</span>
|
||||||
@ -130,13 +128,16 @@ export default function Navbar({}: // currencySelector,
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{user && (user.role === "SCIENTIST" || user.role === "ADMIN") && (
|
{user && (user.role === "SCIENTIST" || user.role === "ADMIN") && (
|
||||||
<div className="flex h-full mr-5">
|
<div className="flex h-full mr-5">
|
||||||
<ManagementNavbarButton name="Warehouse" href="/warehouse"></ManagementNavbarButton>
|
<ManagementNavbarButton name="Warehouse" href="/warehouse"></ManagementNavbarButton>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{user && user.role === "ADMIN" && (
|
||||||
|
<div className="flex h-full mr-5">
|
||||||
|
<ManagementNavbarButton name="Admin" href="/administrator"></ManagementNavbarButton>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<button className="my-auto mr-4" onClick={() => (user ? router.push("/profile") : setIsModalOpen(true))}>
|
<button className="my-auto mr-4" onClick={() => (user ? router.push("/profile") : setIsModalOpen(true))}>
|
||||||
<FaRegUserCircle size={22} />
|
<FaRegUserCircle size={22} />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
1
src/databases/Artefacts.csv
Normal file
1
src/databases/Artefacts.csv
Normal file
@ -0,0 +1 @@
|
|||||||
|
Artefacts
|
||||||
|
@ -1 +0,0 @@
|
|||||||
Artifacts
|
|
||||||
|
@ -1,85 +0,0 @@
|
|||||||
// Datasource configuration
|
|
||||||
datasource db {
|
|
||||||
provider = "sqlserver"
|
|
||||||
url = env("DATABASE_URL")
|
|
||||||
}
|
|
||||||
|
|
||||||
// User model
|
|
||||||
model User {
|
|
||||||
id Int @id @default(autoincrement())
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
name String
|
|
||||||
email String @unique
|
|
||||||
passwordHash String
|
|
||||||
role String @default("GUEST") @db.VarChar(10) // ADMIN, SCIENTIST, GUEST
|
|
||||||
scientist Scientist? @relation
|
|
||||||
purchasedArtifacts Artifact[] @relation("UserPurchasedArtifacts")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scientist model
|
|
||||||
model Scientist {
|
|
||||||
id Int @id @default(autoincrement())
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
name String
|
|
||||||
level String @db.VarChar(10) // JUNIOR, SENIOR
|
|
||||||
user User @relation(fields: [userId], references: [id])
|
|
||||||
userId Int @unique
|
|
||||||
superior Scientist? @relation("SuperiorRelation", fields: [superiorId], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
|
||||||
superiorId Int?
|
|
||||||
subordinates Scientist[] @relation("SuperiorRelation")
|
|
||||||
earthquakes Earthquake[] @relation("ScientistEarthquakeCreator")
|
|
||||||
observatories Observatory[] @relation("ScientistObservatoryCreator")
|
|
||||||
artifacts Artifact[] @relation("ScientistArtifactCreator")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Earthquake model
|
|
||||||
model Earthquake {
|
|
||||||
id Int @id @default(autoincrement())
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
date DateTime
|
|
||||||
locaiton String
|
|
||||||
latitude String
|
|
||||||
longitude String
|
|
||||||
magnitude Float
|
|
||||||
depth Float
|
|
||||||
creatorId Int?
|
|
||||||
creator Scientist? @relation("ScientistEarthquakeCreator", fields: [creatorId], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
|
||||||
artifacts Artifact[]
|
|
||||||
observatories Observatory[] @relation("EarthquakeObservatory")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Observatory model
|
|
||||||
model Observatory {
|
|
||||||
id Int @id @default(autoincrement())
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
name String
|
|
||||||
location String
|
|
||||||
longitude String
|
|
||||||
latitude String
|
|
||||||
dateEstablished Int?
|
|
||||||
functional Boolean
|
|
||||||
seismicSensorOnline Boolean @default(true)
|
|
||||||
creatorId Int?
|
|
||||||
creator Scientist? @relation("ScientistObservatoryCreator", fields: [creatorId], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
|
||||||
earthquakes Earthquake[] @relation("EarthquakeObservatory")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Artifact model
|
|
||||||
model Artifact {
|
|
||||||
id Int @id @default(autoincrement())
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
type String @db.VarChar(50) // Lava, Tephra, Ash, Soil
|
|
||||||
warehouseArea String // Examples: "ZoneA-Shelf1", "ZoneB-Rack2", "ZoneC-Bin3"
|
|
||||||
earthquakeId Int
|
|
||||||
earthquake Earthquake @relation(fields: [earthquakeId], references: [id])
|
|
||||||
creatorId Int?
|
|
||||||
creator Scientist? @relation("ScientistArtifactCreator", fields: [creatorId], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
|
||||||
required Boolean @default(true)
|
|
||||||
shopPrice Float? // In Euros
|
|
||||||
purchasedById Int?
|
|
||||||
purchasedBy User? @relation("UserPurchasedArtifacts", fields: [purchasedById], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
|
||||||
pickedUp Boolean @default(false)
|
|
||||||
}
|
|
||||||
@ -1,11 +1,14 @@
|
|||||||
interface Artifact {
|
interface Artefact {
|
||||||
// todo change to string
|
// todo change to string
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
location: string;
|
location: string;
|
||||||
|
earthquakeID: string;
|
||||||
|
observatory: string;
|
||||||
|
dateReleased: string;
|
||||||
image: string;
|
image: string;
|
||||||
price: number;
|
price: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Artifact;
|
export default Artefact;
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { Action } from 'easy-peasy';
|
import { Action } from "easy-peasy";
|
||||||
|
|
||||||
// import type { User } from "@prisma/client";
|
// import type { User } from "@prisma/client";
|
||||||
|
|
||||||
@ -14,10 +14,10 @@ interface Scientist {
|
|||||||
subordinates: Scientist[];
|
subordinates: Scientist[];
|
||||||
// earthquakes: Earthquake[];
|
// earthquakes: Earthquake[];
|
||||||
// observatories: Observatory[];
|
// observatories: Observatory[];
|
||||||
artifacts: Artifact[];
|
artefacts: Artefact[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Artifact {
|
interface Artefact {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
@ -37,7 +37,7 @@ interface User {
|
|||||||
passwordHash: string;
|
passwordHash: string;
|
||||||
role: string;
|
role: string;
|
||||||
scientist: Scientist | undefined;
|
scientist: Scientist | undefined;
|
||||||
purchasedArtifacts: Artifact[];
|
purchasedArtefacts: Artefact[];
|
||||||
}
|
}
|
||||||
|
|
||||||
type Currency = "GBP" | "USD" | "EUR";
|
type Currency = "GBP" | "USD" | "EUR";
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user