diff --git a/.env b/.env new file mode 100644 index 0000000..97160d5 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +DATABASE_URL="" +JWT_SECRET_KEY=mysupersecretkey \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2baa5c1..e0b79c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,15 +9,19 @@ "version": "0.1.0", "dependencies": { "@prisma/client": "^6.4.1", + "@types/jsonwebtoken": "^9.0.9", "@types/mapbox-gl": "^3.4.1", "axios": "^1.9.0", "bcrypt": "^5.1.1", "bcryptjs": "^3.0.2", "body-parser": "^2.2.0", "csv-parser": "^3.2.0", + "dotenv": "^16.5.0", "easy-peasy": "^6.1.0", "express": "^5.1.0", "fs": "^0.0.1-security", + "jose": "^6.0.11", + "jsonwebtoken": "^9.0.2", "jwt-decode": "^4.0.0", "leaflet": "^1.9.4", "lodash": "^4.17.21", @@ -30,7 +34,8 @@ "react-icons": "^5.5.0", "react-leaflet": "^5.0.0", "react-node": "^1.0.2", - "swr": "^2.3.3" + "swr": "^2.3.3", + "zod": "^3.24.4" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -1586,6 +1591,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.9.tgz", + "integrity": "sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, "node_modules/@types/mapbox__point-geometry": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz", @@ -1619,11 +1634,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.17.19", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.19.tgz", "integrity": "sha512-LEwC7o1ifqg/6r2gn9Dns0f1rhK+fPFDoMiceTJ6kWmVk6bgXBI/9IOWfVan4WiAavK9pIVWdX0/e3J+eEUh5A==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.19.2" @@ -2453,6 +2473,12 @@ "node": ">=8" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -3084,6 +3110,18 @@ "node": ">=0.10.0" } }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -3147,6 +3185,15 @@ } } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -5272,6 +5319,15 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jose": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.0.11.tgz", + "integrity": "sha512-QxG7EaliDARm1O1S8BGakqncGT9s25bKL1WSf6/oa17Tkqwi8D2ZNglqCF+DsYF88/rV66Q/Q2mFAy697E1DUg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5326,6 +5382,28 @@ "json5": "lib/cli.js" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, "node_modules/jstransform": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/jstransform/-/jstransform-11.0.3.tgz", @@ -5370,6 +5448,27 @@ "node": ">=4.0" } }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/jwt-decode": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", @@ -5477,6 +5576,42 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -5484,6 +5619,12 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -8145,7 +8286,6 @@ "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, "license": "MIT" }, "node_modules/unpipe": { @@ -8529,6 +8669,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.24.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.4.tgz", + "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 99e4e22..58f3594 100644 --- a/package.json +++ b/package.json @@ -12,15 +12,19 @@ }, "dependencies": { "@prisma/client": "^6.4.1", + "@types/jsonwebtoken": "^9.0.9", "@types/mapbox-gl": "^3.4.1", "axios": "^1.9.0", "bcrypt": "^5.1.1", "bcryptjs": "^3.0.2", "body-parser": "^2.2.0", "csv-parser": "^3.2.0", + "dotenv": "^16.5.0", "easy-peasy": "^6.1.0", "express": "^5.1.0", "fs": "^0.0.1-security", + "jose": "^6.0.11", + "jsonwebtoken": "^9.0.2", "jwt-decode": "^4.0.0", "leaflet": "^1.9.4", "lodash": "^4.17.21", @@ -33,7 +37,8 @@ "react-icons": "^5.5.0", "react-leaflet": "^5.0.0", "react-node": "^1.0.2", - "swr": "^2.3.3" + "swr": "^2.3.3", + "zod": "^3.24.4" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/public/.DS_Store b/public/.DS_Store deleted file mode 100644 index 63b3f4a..0000000 Binary files a/public/.DS_Store and /dev/null differ diff --git a/src/app/api/login/route.ts b/src/app/api/login/route.ts index a5ef4f7..12974e6 100644 --- a/src/app/api/login/route.ts +++ b/src/app/api/login/route.ts @@ -1,5 +1,7 @@ import bcrypt from "bcrypt"; +import { env } from "@utils/env"; import { NextResponse } from "next/server"; +import { SignJWT } from "jose"; import { PrismaClient } from "@prisma/client"; @@ -9,31 +11,64 @@ const usingPrisma = false; let prisma: PrismaClient; if (usingPrisma) prisma = new PrismaClient(); -export async function POST(request: Request) { +export async function POST(req: Request) { try { - const body = await request.json(); // Parse incoming JSON data - const { email, password } = body; + const json = await req.json(); // Parse incoming JSON data + const { email, password } = json.body; const userData = await readUserCsv(); console.log(userData); console.log("Email:", email); // ! remove console.log("Password:", password); // ! remove - let foundUser; + let user; if (usingPrisma) { - foundUser = await prisma.user.findUnique({ + user = await prisma.user.findUnique({ where: { - email: email, // use the email to uniquely identify the user + email, // use the email to uniquely identify the user }, }); } else { - foundUser = findUserByEmail(userData, email); + user = findUserByEmail(userData, email); } - if (foundUser && (await bcrypt.compare(password, usingPrisma ? foundUser.hashedPassword : foundUser.password))) { + if (user && bcrypt.compareSync(password, usingPrisma ? user.hashedPassword : user.password)) { // todo remove password from returned user - return NextResponse.json({ message: "Login successful!", user: foundUser }, { status: 200 }); + + // get user and relations + if (usingPrisma) + user = await prisma.user.findUnique({ + where: { id: user.id }, + include: { + scientist: { + include: { + earthquakes: true, + observatories: true, + artefacts: true, + superior: true, + subordinates: true, + }, + }, + purchasedArtefacts: true, + }, + }); + + const secret = new TextEncoder().encode(env.JWT_SECRET_KEY); + const token = await new SignJWT({ userId: user.id }) + .setProtectedHeader({ alg: "HS256" }) + .setExpirationTime("2w") + .sign(secret); + + const response = NextResponse.json({ message: "Login successful!", user, token }, { status: 200 }); + response.cookies.set("jwt", token, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + maxAge: 3600 * 168 * 2, // 2 weeks + path: "/", + }); + return response; } else { return NextResponse.json({ message: "Email and/or password are invalid" }, { status: 401 }); } diff --git a/src/app/api/logout/route.ts b/src/app/api/logout/route.ts new file mode 100644 index 0000000..45017d2 --- /dev/null +++ b/src/app/api/logout/route.ts @@ -0,0 +1,8 @@ +// app/api/logout/route.ts +import { cookies } from "next/headers"; +import { NextResponse } from "next/server"; + +export async function GET() { + (await cookies()).delete("jwt"); + return NextResponse.json({ message: "Logged out" }); +} diff --git a/src/app/api/signup/route.ts b/src/app/api/signup/route.ts index 9360f6c..82a3076 100644 --- a/src/app/api/signup/route.ts +++ b/src/app/api/signup/route.ts @@ -9,10 +9,10 @@ const usingPrisma = false; let prisma: PrismaClient; if (usingPrisma) prisma = new PrismaClient(); -export async function POST(request: Request) { +export async function POST(req: Request) { try { - const body = await request.json(); // Parse incoming JSON data - let { email, password, name } = body; + const json = await req.json(); // Parse incoming JSON data + let { email, password, name } = json.body; const accessLevel = "basic"; const userData = await readUserCsv(); diff --git a/src/app/api/warehouse/route.ts b/src/app/api/warehouse/route.ts new file mode 100644 index 0000000..027d92e --- /dev/null +++ b/src/app/api/warehouse/route.ts @@ -0,0 +1,105 @@ +import { NextResponse } from "next/server"; +import { env } from "@utils/env"; + +import { PrismaClient } from "@prisma/client"; +import { verifyJwt } from "@utils/verifyJwt"; + +const usingPrisma = false; +let prisma: PrismaClient; +if (usingPrisma) prisma = new PrismaClient(); + +// Artefact type +interface Artefact { + id: number; + name: string; + description: string; + location: string; + earthquakeId: string; + isRequired: boolean; + isSold: boolean; + isCollected: boolean; + dateAdded: string; +} + +export async function POST(req: Request) { + try { + // todo fix, moron the token will be in the cookie header + const json = await req.json(); // Parse incoming JSON data + const { token } = json.body; + if (!token) return NextResponse.json({ message: "Unauthorised" }, { status: 401 }); + await verifyJwt({ token, secret: env.JWT_SECRET_KEY }); + + const warehouseArtefacts: Artefact[] = [ + { + id: 1, + name: "Solidified Lava Chunk", + description: "A chunk of solidified lava from the 2023 Iceland eruption.", + location: "Reykjanes, Iceland", + earthquakeId: "EQ2023ICL", + isRequired: true, + isSold: false, + isCollected: false, + dateAdded: "2025-05-04", + }, + { + id: 2, + name: "Tephra Sample", + description: "Foreign debris from the 2022 Tonga volcanic eruption.", + location: "Tonga", + earthquakeId: "EQ2022TGA", + isRequired: false, + isSold: true, + isCollected: true, + dateAdded: "2025-05-03", + }, + { + id: 3, + name: "Ash Sample", + description: "Volcanic ash from the 2021 La Palma eruption.", + location: "La Palma, Spain", + earthquakeId: "EQ2021LPA", + isRequired: false, + isSold: false, + isCollected: false, + dateAdded: "2025-05-04", + }, + { + id: 4, + name: "Ground Soil", + description: "Soil sample from the 2020 Croatia earthquake site.", + location: "Zagreb, Croatia", + earthquakeId: "EQ2020CRO", + isRequired: true, + isSold: false, + isCollected: false, + dateAdded: "2025-05-02", + }, + { + id: 5, + name: "Basalt Fragment", + description: "Basalt rock from the 2019 New Zealand eruption.", + location: "White Island, New Zealand", + earthquakeId: "EQ2019NZL", + isRequired: false, + isSold: true, + isCollected: false, + dateAdded: "2025-05-04", + }, + ]; + + let artefacts; + if (usingPrisma) artefacts = await prisma.artefacts.findMany(); + + if (artefacts) { + return NextResponse.json({ message: "Got artefacts successfully", artefacts }, { status: 200 }); + } else { + return NextResponse.json({ message: "Got earthquakes successfully", earthquakes: warehouseArtefacts }, { status: 200 }); + // return NextResponse.json({ message: "Failed to get earthquakes" }, { status: 401 }); + } + } catch (error) { + console.error("Error in artefacts endpoint:", error); + return NextResponse.json({ message: "Internal Server Error" }, { status: 500 }); + } finally { + if (usingPrisma) await prisma.$disconnect(); + } +} diff --git a/src/app/contact-us/page.tsx b/src/app/contact-us/page.tsx index 955d99c..42d971c 100644 --- a/src/app/contact-us/page.tsx +++ b/src/app/contact-us/page.tsx @@ -31,11 +31,11 @@ const ContactUs = () => { /> {/* Overlay for readability */} -
Have questions or concerns about earthquake preparedness? Contact us using the form below or through the provided
contact details.
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 4e92c23..c308d00 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -15,14 +15,25 @@ const inter = Inter({
const store = createStore
- At Earthquake Awareness Initiative, our mission is to help people
- worldwide prepare for and recover from earthquakes. Through education, research, and innovative technology, we work
- tirelessly to empower communities with the knowledge they need to stay safe before, during, and after seismic events.
+
+ At Tremor Tracker, we empower communities worldwide to prepare for
+ and recover from earthquakes through education, cutting-edge research, and innovative technology.
- We aim to bridge the gap between scientific research and community awareness by providing resources, tools, and
- real-time updates for earthquake preparedness. Together, we aspire to save lives, mitigate impacts, and foster
- resilience against nature's powerful forces.
+
+ We bridge scientific insights with public awareness, delivering resources, tools, and real-time updates to enhance
+ preparedness, save lives, and build resilience against seismic events.
- Providing accessible resources to educate people about earthquake preparedness.
-
- Supporting scientific studies to enhance understanding of seismic activity.
-
- Leveraging innovation to deliver real-time alerts and safety tools.
-
- At Earthquake Awareness Initiative, our mission is to help people
- worldwide prepare for and recover from earthquakes. Through education, research, and innovative technology, we work
- tirelessly to empower communities with the knowledge they need to stay safe before, during, and after seismic events.
-
- We aim to bridge the gap between scientific research and community awareness by providing resources, tools, and
- real-time updates for earthquake preparedness. Together, we aspire to save lives, mitigate impacts, and foster
- resilience against nature's powerful forces.
-
- Providing accessible resources to educate people about earthquake preparedness.
+
+ Delivering accessible resources to educate communities on earthquake preparedness.
- Supporting scientific studies to enhance understanding of seismic activity.
+
+ Advancing scientific studies to deepen understanding of seismic activity.
- Leveraging innovation to deliver real-time alerts and safety tools.
+
+ Harnessing innovation for real-time alerts and safety solutions.
User
{currencyTickers[selectedCurrency]}
- {convertPrice(artifact.price, selectedCurrency)}
+ {convertPrice(artefact.price, selectedCurrency)}
{artifact.description} Location: {artifact.location} {artefact.description} Location: {artefact.location} {currentPage}
- Our expert team is made up of scientists around the globe. Our heads of department are shown below.
+
+ Our world-class scientists drive innovation across the globe. Meet our department heads below.
{member.title} {member.description} {member.title} {member.description} {error} {error}Our Mission
- Our Mission
+
- Education
-
- Research
-
- Technology
- Our Mission
-
+ Education
-
+ Research
-
+ Technology
- {artifact.name}
+ {artefact.name}
Meet The Team
- Meet Our Team
+
{member.name}
- {member.name}
+
setEditArtifact(artifact)}
+ onClick={() => setEditArtefact(artefact)}
>
{columns.map(({ key, width }) => (
@@ -284,7 +284,7 @@ function ArtifactTable({
);
}
-// Modal Component for Logging Artifact
+// Modal Component for Logging Artefact
function LogModal({ onClose }: { onClose: () => void }) {
const [name, setName] = useState("");
const [description, setDescription] = useState("");
@@ -312,7 +312,7 @@ function LogModal({ onClose }: { onClose: () => void }) {
alert(`Logged ${name} to storage: ${storageLocation}`);
onClose();
} catch {
- setError("Failed to log artifact. Please try again.");
+ setError("Failed to log artefact. Please try again.");
} finally {
setIsSubmitting(false);
}
@@ -324,7 +324,7 @@ function LogModal({ onClose }: { onClose: () => void }) {
onClick={handleOverlayClick}
>
{key === "isRequired"
- ? artifact.isRequired
+ ? artefact.isRequired
? "Yes"
: "No"
: key === "isSold"
- ? artifact.isSold
+ ? artefact.isSold
? "Yes"
: "No"
: key === "isCollected"
- ? artifact.isCollected
+ ? artefact.isCollected
? "Yes"
: "No"
- : artifact[key]}
+ : artefact[key]}
))}
Log New Artifact
+ Log New Artefact
{error && Edit Artifact
+ Edit Artefact
{error &&