aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPinapelz <yukais@pinapelz.com>2025-06-29 01:28:39 -0700
committerPinapelz <yukais@pinapelz.com>2025-06-29 01:28:39 -0700
commitff37cca46430ed714015647469f88ce06781457a (patch)
tree63f406a3c7fb463fed7f34efc8d04d58fe96e0cb
parente4fa1e69e7ebfb627c7198fd1a9881e9327ec4d4 (diff)
scaffold register,login,and auth endpoints
-rw-r--r--backend/.env.example5
-rw-r--r--backend/.gitignore1
-rw-r--r--backend/dev.dbbin24576 -> 28672 bytes
-rw-r--r--backend/dev.db-journalbin8720 -> 0 bytes
-rw-r--r--backend/migrations/20250616212004_init/migration.sql9
-rw-r--r--backend/migrations/migration_lock.toml3
-rw-r--r--backend/package-lock.json801
-rw-r--r--backend/package.json11
-rw-r--r--backend/schema.prisma17
-rw-r--r--backend/src/db.ts9
-rw-r--r--backend/src/index.ts258
-rw-r--r--docs/SESSION_GUIDE.md221
-rw-r--r--frontend/.gitignore1
-rw-r--r--frontend/package-lock.json747
-rw-r--r--frontend/package.json5
-rw-r--r--frontend/src/App.css42
-rw-r--r--frontend/src/App.tsx46
-rw-r--r--frontend/src/assets/react.svg1
-rw-r--r--frontend/src/contexts/AuthContext.tsx139
-rw-r--r--frontend/src/index.css69
-rw-r--r--frontend/src/main.tsx7
-rw-r--r--frontend/src/pages/Home.tsx119
-rw-r--r--frontend/src/pages/Landing.tsx154
-rw-r--r--frontend/src/pages/Login.tsx207
-rw-r--r--frontend/src/pages/Register.tsx230
-rw-r--r--frontend/src/utils/api.ts161
-rw-r--r--frontend/vite.config.ts3
27 files changed, 2831 insertions, 435 deletions
diff --git a/backend/.env.example b/backend/.env.example
new file mode 100644
index 0000000..f3d872f
--- /dev/null
+++ b/backend/.env.example
@@ -0,0 +1,5 @@
+DATABASE_URL="file:./dev.db"
+SESSION_SECRET="your-very-secure-secret-key-change-this-in-production-make-it-long-and-random"
+PORT=5000
+NODE_ENV=development
+FRONTEND_URL="http://localhost:3000"
diff --git a/backend/.gitignore b/backend/.gitignore
index 126419d..0d1e10e 100644
--- a/backend/.gitignore
+++ b/backend/.gitignore
@@ -3,3 +3,4 @@ node_modules
.env
/src/generated/prisma
+*.db
diff --git a/backend/dev.db b/backend/dev.db
index 882c166..7bda866 100644
--- a/backend/dev.db
+++ b/backend/dev.db
Binary files differ
diff --git a/backend/dev.db-journal b/backend/dev.db-journal
deleted file mode 100644
index f451d59..0000000
--- a/backend/dev.db-journal
+++ /dev/null
Binary files differ
diff --git a/backend/migrations/20250616212004_init/migration.sql b/backend/migrations/20250616212004_init/migration.sql
deleted file mode 100644
index edeec02..0000000
--- a/backend/migrations/20250616212004_init/migration.sql
+++ /dev/null
@@ -1,9 +0,0 @@
--- CreateTable
-CREATE TABLE "User" (
- "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
- "name" TEXT NOT NULL,
- "email" TEXT NOT NULL
-);
-
--- CreateIndex
-CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
diff --git a/backend/migrations/migration_lock.toml b/backend/migrations/migration_lock.toml
deleted file mode 100644
index 2a5a444..0000000
--- a/backend/migrations/migration_lock.toml
+++ /dev/null
@@ -1,3 +0,0 @@
-# Please do not edit this file manually
-# It should be added in your version-control system (e.g., Git)
-provider = "sqlite"
diff --git a/backend/package-lock.json b/backend/package-lock.json
index 3a08779..6afd91e 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -10,14 +10,21 @@
"license": "ISC",
"dependencies": {
"@prisma/client": "^6.9.0",
+ "@types/bcrypt": "^5.0.2",
+ "@types/cookie-parser": "^1.4.9",
+ "@types/express-session": "^1.18.2",
+ "bcrypt": "^6.0.0",
+ "cookie-parser": "^1.4.7",
"cors": "^2.8.5",
- "express": "^5.1.0",
+ "express": "^4.21.2",
+ "express-session": "^1.18.1",
+ "node-fetch": "^3.3.2",
"sqlite": "^5.1.1",
"sqlite3": "^5.1.7"
},
"devDependencies": {
"@types/cors": "^2.8.19",
- "@types/express": "^5.0.3",
+ "@types/express": "^4.17.23",
"@types/node": "^24.0.3",
"@types/sqlite3": "^5.1.0",
"prisma": "^6.9.0",
@@ -200,11 +207,19 @@
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
"dev": true
},
+ "node_modules/@types/bcrypt": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz",
+ "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/body-parser": {
"version": "1.19.6",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
"integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
- "dev": true,
"dependencies": {
"@types/connect": "*",
"@types/node": "*"
@@ -214,11 +229,19 @@
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
- "dev": true,
"dependencies": {
"@types/node": "*"
}
},
+ "node_modules/@types/cookie-parser": {
+ "version": "1.4.9",
+ "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.9.tgz",
+ "integrity": "sha512-tGZiZ2Gtc4m3wIdLkZ8mkj1T6CEHb35+VApbL2T14Dew8HA7c+04dmKqsKRNC+8RJPm16JEK0tFSwdZqubfc4g==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/express": "*"
+ }
+ },
"node_modules/@types/cors": {
"version": "2.8.19",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
@@ -229,21 +252,22 @@
}
},
"node_modules/@types/express": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz",
- "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==",
- "dev": true,
+ "version": "4.17.23",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz",
+ "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==",
+ "license": "MIT",
"dependencies": {
"@types/body-parser": "*",
- "@types/express-serve-static-core": "^5.0.0",
+ "@types/express-serve-static-core": "^4.17.33",
+ "@types/qs": "*",
"@types/serve-static": "*"
}
},
"node_modules/@types/express-serve-static-core": {
- "version": "5.0.6",
- "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz",
- "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==",
- "dev": true,
+ "version": "4.19.6",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz",
+ "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==",
+ "license": "MIT",
"dependencies": {
"@types/node": "*",
"@types/qs": "*",
@@ -251,23 +275,29 @@
"@types/send": "*"
}
},
+ "node_modules/@types/express-session": {
+ "version": "1.18.2",
+ "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.18.2.tgz",
+ "integrity": "sha512-k+I0BxwVXsnEU2hV77cCobC08kIsn4y44C3gC0b46uxZVMaXA04lSPgRLR/bSL2w0t0ShJiG8o4jPzRG/nscFg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/express": "*"
+ }
+ },
"node_modules/@types/http-errors": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
- "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
- "dev": true
+ "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="
},
"node_modules/@types/mime": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
- "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
- "dev": true
+ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="
},
"node_modules/@types/node": {
"version": "24.0.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.3.tgz",
"integrity": "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==",
- "dev": true,
"dependencies": {
"undici-types": "~7.8.0"
}
@@ -276,19 +306,18 @@
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
"integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
- "dev": true
+ "license": "MIT"
},
"node_modules/@types/range-parser": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
- "dev": true
+ "license": "MIT"
},
"node_modules/@types/send": {
"version": "0.17.5",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz",
"integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==",
- "dev": true,
"dependencies": {
"@types/mime": "^1",
"@types/node": "*"
@@ -298,7 +327,6 @@
"version": "1.15.8",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz",
"integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==",
- "dev": true,
"dependencies": {
"@types/http-errors": "*",
"@types/node": "*",
@@ -334,12 +362,13 @@
"optional": true
},
"node_modules/accepts": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
- "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "license": "MIT",
"dependencies": {
- "mime-types": "^3.0.0",
- "negotiator": "^1.0.0"
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
},
"engines": {
"node": ">= 0.6"
@@ -454,6 +483,12 @@
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true
},
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+ "license": "MIT"
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -479,6 +514,29 @@
}
]
},
+ "node_modules/bcrypt": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz",
+ "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "node-addon-api": "^8.3.0",
+ "node-gyp-build": "^4.8.4"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/bcrypt/node_modules/node-addon-api": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.4.0.tgz",
+ "integrity": "sha512-D9DI/gXHvVmjHS08SVch0Em8G5S1P+QWtU31appcKT/8wFSPRcdHadIFSAntdMMVM5zz+/DL+bL/gz3UDppqtg==",
+ "license": "MIT",
+ "engines": {
+ "node": "^18 || ^20 || >= 21"
+ }
+ },
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -510,24 +568,56 @@
}
},
"node_modules/body-parser": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
- "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
+ "version": "1.20.3",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
+ "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
+ "license": "MIT",
"dependencies": {
- "bytes": "^3.1.2",
- "content-type": "^1.0.5",
- "debug": "^4.4.0",
- "http-errors": "^2.0.0",
- "iconv-lite": "^0.6.3",
- "on-finished": "^2.4.1",
- "qs": "^6.14.0",
- "raw-body": "^3.0.0",
- "type-is": "^2.0.0"
+ "bytes": "3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "on-finished": "2.4.1",
+ "qs": "6.13.0",
+ "raw-body": "2.5.2",
+ "type-is": "~1.6.18",
+ "unpipe": "1.0.0"
},
"engines": {
- "node": ">=18"
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/body-parser/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
}
},
+ "node_modules/body-parser/node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/body-parser/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
@@ -583,6 +673,7 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
"engines": {
"node": ">= 0.8"
}
@@ -620,6 +711,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
@@ -632,6 +724,7 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
@@ -706,9 +799,10 @@
"optional": true
},
"node_modules/content-disposition": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
- "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "license": "MIT",
"dependencies": {
"safe-buffer": "5.2.1"
},
@@ -720,25 +814,47 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
+ "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-parser": {
+ "version": "1.4.7",
+ "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
+ "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "0.7.2",
+ "cookie-signature": "1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/cookie-parser/node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
- "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
- "engines": {
- "node": ">=6.6.0"
- }
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
+ "license": "MIT"
},
"node_modules/cors": {
"version": "2.8.5",
@@ -758,10 +874,20 @@
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true
},
+ "node_modules/data-uri-to-buffer": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
+ "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/debug": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
+ "optional": true,
"dependencies": {
"ms": "^2.1.3"
},
@@ -806,10 +932,21 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
"node_modules/detect-libc": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
@@ -831,6 +968,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
@@ -852,7 +990,8 @@
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
- "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
},
"node_modules/emoji-regex": {
"version": "8.0.0",
@@ -864,6 +1003,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
"engines": {
"node": ">= 0.8"
}
@@ -904,6 +1044,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
"engines": {
"node": ">= 0.4"
}
@@ -912,6 +1053,7 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
"engines": {
"node": ">= 0.4"
}
@@ -920,6 +1062,7 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
@@ -930,12 +1073,14 @@
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
- "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
},
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
"engines": {
"node": ">= 0.6"
}
@@ -949,46 +1094,138 @@
}
},
"node_modules/express": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
- "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
+ "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
+ "license": "MIT",
"dependencies": {
- "accepts": "^2.0.0",
- "body-parser": "^2.2.0",
- "content-disposition": "^1.0.0",
- "content-type": "^1.0.5",
- "cookie": "^0.7.1",
- "cookie-signature": "^1.2.1",
- "debug": "^4.4.0",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "etag": "^1.8.1",
- "finalhandler": "^2.1.0",
- "fresh": "^2.0.0",
- "http-errors": "^2.0.0",
- "merge-descriptors": "^2.0.0",
- "mime-types": "^3.0.0",
- "on-finished": "^2.4.1",
- "once": "^1.4.0",
- "parseurl": "^1.3.3",
- "proxy-addr": "^2.0.7",
- "qs": "^6.14.0",
- "range-parser": "^1.2.1",
- "router": "^2.2.0",
- "send": "^1.1.0",
- "serve-static": "^2.2.0",
- "statuses": "^2.0.1",
- "type-is": "^2.0.1",
- "vary": "^1.1.2"
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.20.3",
+ "content-disposition": "0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "0.7.1",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "1.3.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "merge-descriptors": "1.0.3",
+ "methods": "~1.1.2",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "0.1.12",
+ "proxy-addr": "~2.0.7",
+ "qs": "6.13.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "0.19.0",
+ "serve-static": "1.16.2",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
},
"engines": {
- "node": ">= 18"
+ "node": ">= 0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
+ "node_modules/express-session": {
+ "version": "1.18.1",
+ "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz",
+ "integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "0.7.2",
+ "cookie-signature": "1.0.7",
+ "debug": "2.6.9",
+ "depd": "~2.0.0",
+ "on-headers": "~1.0.2",
+ "parseurl": "~1.3.3",
+ "safe-buffer": "5.2.1",
+ "uid-safe": "~2.1.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/express-session/node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express-session/node_modules/cookie-signature": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
+ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
+ "license": "MIT"
+ },
+ "node_modules/express-session/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/express-session/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/express/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/express/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/fetch-blob": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
+ "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "node-domexception": "^1.0.0",
+ "web-streams-polyfill": "^3.0.3"
+ },
+ "engines": {
+ "node": "^12.20 || >= 14.13"
+ }
+ },
"node_modules/file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
@@ -1007,21 +1244,50 @@
}
},
"node_modules/finalhandler": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
- "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
+ "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
+ "license": "MIT",
"dependencies": {
- "debug": "^4.4.0",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "on-finished": "^2.4.1",
- "parseurl": "^1.3.3",
- "statuses": "^2.0.1"
+ "debug": "2.6.9",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "2.0.1",
+ "unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
+ "node_modules/finalhandler/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/finalhandler/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/formdata-polyfill": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
+ "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
+ "license": "MIT",
+ "dependencies": {
+ "fetch-blob": "^3.1.2"
+ },
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -1031,11 +1297,12 @@
}
},
"node_modules/fresh": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
- "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "license": "MIT",
"engines": {
- "node": ">= 0.8"
+ "node": ">= 0.6"
}
},
"node_modules/fs-constants": {
@@ -1106,6 +1373,7 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
@@ -1129,6 +1397,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
@@ -1179,6 +1448,7 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
"engines": {
"node": ">= 0.4"
},
@@ -1196,6 +1466,7 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
"engines": {
"node": ">= 0.4"
},
@@ -1230,6 +1501,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "license": "MIT",
"dependencies": {
"depd": "2.0.0",
"inherits": "2.0.4",
@@ -1241,14 +1513,6 @@
"node": ">= 0.8"
}
},
- "node_modules/http-errors/node_modules/statuses": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
- "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/http-proxy-agent": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz",
@@ -1289,6 +1553,7 @@
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "optional": true,
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
@@ -1453,11 +1718,6 @@
"node": ">=0.12.0"
}
},
- "node_modules/is-promise": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
- "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="
- },
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -1537,43 +1797,66 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/media-typer": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
- "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "license": "MIT",
"engines": {
- "node": ">= 0.8"
+ "node": ">= 0.6"
}
},
"node_modules/merge-descriptors": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
- "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
- "engines": {
- "node": ">=18"
- },
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+ "license": "MIT",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/mime-db": {
- "version": "1.54.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
- "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
- "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
"dependencies": {
- "mime-db": "^1.54.0"
+ "mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
@@ -1725,9 +2008,10 @@
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="
},
"node_modules/negotiator": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
- "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "license": "MIT",
"engines": {
"node": ">= 0.6"
}
@@ -1748,6 +2032,44 @@
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="
},
+ "node_modules/node-domexception": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
+ "deprecated": "Use your platform's native DOMException instead",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "github",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.5.0"
+ }
+ },
+ "node_modules/node-fetch": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
+ "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
+ "license": "MIT",
+ "dependencies": {
+ "data-uri-to-buffer": "^4.0.0",
+ "fetch-blob": "^3.1.4",
+ "formdata-polyfill": "^4.0.10"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/node-fetch"
+ }
+ },
"node_modules/node-gyp": {
"version": "8.4.1",
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz",
@@ -1772,6 +2094,17 @@
"node": ">= 10.12.0"
}
},
+ "node_modules/node-gyp-build": {
+ "version": "4.8.4",
+ "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
+ "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
+ "license": "MIT",
+ "bin": {
+ "node-gyp-build": "bin.js",
+ "node-gyp-build-optional": "optional.js",
+ "node-gyp-build-test": "build-test.js"
+ }
+ },
"node_modules/nopt": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
@@ -1824,6 +2157,7 @@
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
"engines": {
"node": ">= 0.4"
},
@@ -1835,6 +2169,7 @@
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
"dependencies": {
"ee-first": "1.1.1"
},
@@ -1842,6 +2177,15 @@
"node": ">= 0.8"
}
},
+ "node_modules/on-headers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
+ "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -1869,6 +2213,7 @@
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
"engines": {
"node": ">= 0.8"
}
@@ -1889,12 +2234,10 @@
"dev": true
},
"node_modules/path-to-regexp": {
- "version": "8.2.0",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
- "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
- "engines": {
- "node": ">=16"
- }
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
+ "license": "MIT"
},
"node_modules/picomatch": {
"version": "2.3.1",
@@ -1999,11 +2342,12 @@
}
},
"node_modules/qs": {
- "version": "6.14.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
- "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
+ "version": "6.13.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
+ "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
+ "license": "BSD-3-Clause",
"dependencies": {
- "side-channel": "^1.1.0"
+ "side-channel": "^1.0.6"
},
"engines": {
"node": ">=0.6"
@@ -2012,28 +2356,51 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/random-bytes": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
+ "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/raw-body": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
- "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+ "license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
- "iconv-lite": "0.6.3",
+ "iconv-lite": "0.4.24",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
+ "node_modules/raw-body/node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
@@ -2118,21 +2485,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/router": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
- "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
- "dependencies": {
- "debug": "^4.4.0",
- "depd": "^2.0.0",
- "is-promise": "^4.0.0",
- "parseurl": "^1.3.3",
- "path-to-regexp": "^8.0.0"
- },
- "engines": {
- "node": ">= 18"
- }
- },
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -2169,38 +2521,66 @@
}
},
"node_modules/send": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
- "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
+ "version": "0.19.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
+ "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
+ "license": "MIT",
"dependencies": {
- "debug": "^4.3.5",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "etag": "^1.8.1",
- "fresh": "^2.0.0",
- "http-errors": "^2.0.0",
- "mime-types": "^3.0.1",
- "ms": "^2.1.3",
- "on-finished": "^2.4.1",
- "range-parser": "^1.2.1",
- "statuses": "^2.0.1"
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "2.0.1"
},
"engines": {
- "node": ">= 18"
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/send/node_modules/debug/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/send/node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
}
},
"node_modules/serve-static": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
- "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
+ "version": "1.16.2",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
+ "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
+ "license": "MIT",
"dependencies": {
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "parseurl": "^1.3.3",
- "send": "^1.2.0"
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.19.0"
},
"engines": {
- "node": ">= 18"
+ "node": ">= 0.8.0"
}
},
"node_modules/set-blocking": {
@@ -2212,12 +2592,14 @@
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
- "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
},
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3",
@@ -2236,6 +2618,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3"
@@ -2251,6 +2634,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
@@ -2268,6 +2652,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
@@ -2435,9 +2820,10 @@
}
},
"node_modules/statuses": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
- "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "license": "MIT",
"engines": {
"node": ">= 0.8"
}
@@ -2576,6 +2962,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
"engines": {
"node": ">=0.6"
}
@@ -2703,13 +3090,13 @@
}
},
"node_modules/type-is": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
- "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "license": "MIT",
"dependencies": {
- "content-type": "^1.0.5",
- "media-typer": "^1.1.0",
- "mime-types": "^3.0.0"
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
},
"engines": {
"node": ">= 0.6"
@@ -2728,11 +3115,22 @@
"node": ">=14.17"
}
},
+ "node_modules/uid-safe": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
+ "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
+ "license": "MIT",
+ "dependencies": {
+ "random-bytes": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/undici-types": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
- "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
- "dev": true
+ "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="
},
"node_modules/unique-filename": {
"version": "1.1.1",
@@ -2756,6 +3154,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
"engines": {
"node": ">= 0.8"
}
@@ -2765,6 +3164,15 @@
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
@@ -2779,6 +3187,15 @@
"node": ">= 0.8"
}
},
+ "node_modules/web-streams-polyfill": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
+ "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
diff --git a/backend/package.json b/backend/package.json
index 81cf689..70d2aac 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -12,14 +12,21 @@
"license": "ISC",
"dependencies": {
"@prisma/client": "^6.9.0",
+ "@types/bcrypt": "^5.0.2",
+ "@types/cookie-parser": "^1.4.9",
+ "@types/express-session": "^1.18.2",
+ "bcrypt": "^6.0.0",
+ "cookie-parser": "^1.4.7",
"cors": "^2.8.5",
- "express": "^5.1.0",
+ "express": "^4.21.2",
+ "express-session": "^1.18.1",
+ "node-fetch": "^3.3.2",
"sqlite": "^5.1.1",
"sqlite3": "^5.1.7"
},
"devDependencies": {
"@types/cors": "^2.8.19",
- "@types/express": "^5.0.3",
+ "@types/express": "^4.17.23",
"@types/node": "^24.0.3",
"@types/sqlite3": "^5.1.0",
"prisma": "^6.9.0",
diff --git a/backend/schema.prisma b/backend/schema.prisma
index ce84f61..3872cc4 100644
--- a/backend/schema.prisma
+++ b/backend/schema.prisma
@@ -8,7 +8,18 @@ datasource db {
}
model User {
- id Int @id @default(autoincrement())
- name String
- email String @unique
+ id Int @id @default(autoincrement())
+ username String @unique
+ password String
+ salt String
+ email String @unique
+ sessions Session[]
+}
+
+model Session {
+ id String @id @default(cuid())
+ userId Int
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+ createdAt DateTime @default(now())
+ expiresAt DateTime
}
diff --git a/backend/src/db.ts b/backend/src/db.ts
deleted file mode 100644
index 032e68d..0000000
--- a/backend/src/db.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import sqlite3 from 'sqlite3';
-import { open } from 'sqlite';
-
-export const connectDB = async () => {
- return open({
- filename: './database.sqlite',
- driver: sqlite3.Database
- });
-};
diff --git a/backend/src/index.ts b/backend/src/index.ts
index fef1977..52a7a33 100644
--- a/backend/src/index.ts
+++ b/backend/src/index.ts
@@ -1,18 +1,270 @@
import express from 'express';
import cors from 'cors';
+import bcrypt from 'bcrypt';
+import crypto from 'crypto';
+import session from 'express-session';
+import cookieParser from 'cookie-parser';
import { PrismaClient } from '@prisma/client';
const app = express();
const port = 5000;
-app.use(cors());
+app.use(cors({
+ origin: [
+ 'http://localhost:5173', // Vite dev server default port
+ ],
+ credentials: true
+}));
+
app.use(express.json());
+app.use(cookieParser());
+
+
+app.use(session({
+ secret: process.env.SESSION_SECRET || 'your-secret-key-change-this-in-production',
+ resave: false,
+ saveUninitialized: false,
+ cookie: {
+ secure: false,
+ httpOnly: true,
+ maxAge: 24 * 60 * 60 * 1000 // 24 hours
+ }
+}));
const prisma = new PrismaClient();
+
+declare module 'express-session' {
+ interface SessionData {
+ userId: number;
+ }
+}
+
+// Middleware to check if user is authenticated
+const requireAuth = async (req: express.Request, res: express.Response, next: express.NextFunction) => {
+ if (!req.session.userId) {
+ return res.status(401).json({ error: 'Authentication required' });
+ }
+
+ try {
+ const user = await prisma.user.findUnique({
+ where: { id: req.session.userId },
+ select: { id: true, username: true, email: true }
+ });
+
+ if (!user) {
+ req.session.destroy((err) => {
+ if (err) console.error('Session destroy error:', err);
+ });
+ return res.status(401).json({ error: 'Invalid session' });
+ }
+
+ // Attach user to request object
+ (req as any).user = user;
+ next();
+ } catch (error) {
+ console.error('Auth middleware error:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+};
+
+// Create session in database
+const createSession = async (userId: number): Promise<string> => {
+ const sessionId = crypto.randomUUID();
+ const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
+
+ await prisma.session.create({
+ data: {
+ id: sessionId,
+ userId,
+ expiresAt
+ }
+ });
+
+ return sessionId;
+};
+
+// Clean up expired sessions
+const cleanupExpiredSessions = async () => {
+ try {
+ await prisma.session.deleteMany({
+ where: {
+ expiresAt: {
+ lt: new Date()
+ }
+ }
+ });
+ } catch (error) {
+ console.error('Session cleanup error:', error);
+ }
+};
+
+// Run cleanup every hour
+setInterval(cleanupExpiredSessions, 60 * 60 * 1000);
+
+app.post('/api/register', async (req, res) => {
+ try {
+ const { username, password, email } = req.body;
+
+ if (!username || !password || !email) {
+ return res.status(400).json({ error: 'All fields are required' });
+ }
+
+ const existingUser = await prisma.user.findFirst({
+ where: {
+ OR: [
+ { username },
+ { email }
+ ]
+ }
+ });
+
+ if (existingUser) {
+ return res.status(400).json({ error: 'Username or email already exists' });
+ }
+
+ const salt = crypto.randomBytes(16).toString('hex');
+ const hashedPassword = await bcrypt.hash(password + salt, 12);
+
+ const user = await prisma.user.create({
+ data: {
+ username,
+ password: hashedPassword,
+ salt,
+ email,
+ }
+ });
+
+ // Create session for the new user
+ req.session.userId = user.id;
+ const sessionId = await createSession(user.id);
+
+ res.status(201).json({
+ id: user.id,
+ username: user.username,
+ email: user.email,
+ sessionId
+ });
+ } catch (error) {
+ console.error('Registration error:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+app.post('/api/authenticate', async (req, res) => {
+ try {
+ const { username, password } = req.body;
+
+ if (!username || !password) {
+ return res.status(400).json({ error: 'Username and password are required' });
+ }
+
+ const user = await prisma.user.findUnique({
+ where: { username }
+ });
+
+ if (!user) {
+ return res.status(401).json({ error: 'Invalid credentials' });
+ }
+
+ const isValidPassword = await bcrypt.compare(password + user.salt, user.password);
+ if (!isValidPassword) {
+ return res.status(401).json({ error: 'Invalid credentials' });
+ }
+
+ // Create session
+ req.session.userId = user.id;
+ const sessionId = await createSession(user.id);
+
+ res.json({
+ id: user.id,
+ username: user.username,
+ email: user.email,
+ sessionId
+ });
+ } catch (error) {
+ console.error('Login error:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+app.post('/api/logout', requireAuth, async (req, res) => {
+ try {
+ const userId = req.session.userId;
+
+ // Remove all sessions for this user from database
+ await prisma.session.deleteMany({
+ where: { userId }
+ });
+
+ // Destroy the session
+ req.session.destroy((err) => {
+ if (err) {
+ console.error('Session destroy error:', err);
+ return res.status(500).json({ error: 'Logout failed' });
+ }
+
+ res.clearCookie('connect.sid'); // Clear the session cookie
+ res.json({ message: 'Logged out successfully' });
+ });
+ } catch (error) {
+ console.error('Logout error:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+app.get('/api/me', requireAuth, async (req, res) => {
+ try {
+ const user = (req as any).user;
+ res.json(user);
+ } catch (error) {
+ console.error('Me endpoint error:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
app.get('/api/users', async (req, res) => {
- const users = await prisma.user.findMany();
- res.json(users);
+ try {
+ const users = await prisma.user.findMany({
+ select: {
+ id: true,
+ username: true,
+ }
+ });
+ res.json(users);
+ } catch (error) {
+ console.error('Users endpoint error:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Check session status
+app.get('/api/session', async (req, res) => {
+ try {
+ if (!req.session.userId) {
+ return res.json({ authenticated: false });
+ }
+
+ const user = await prisma.user.findUnique({
+ where: { id: req.session.userId },
+ select: { id: true, username: true, email: true }
+ });
+
+ if (!user) {
+ req.session.destroy((err) => {
+ if (err) console.error('Session destroy error:', err);
+ });
+ return res.json({ authenticated: false });
+ }
+
+ res.json({
+ authenticated: true,
+ user
+ });
+ } catch (error) {
+ console.error('Session check error:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
});
app.listen(port, () => {
diff --git a/docs/SESSION_GUIDE.md b/docs/SESSION_GUIDE.md
new file mode 100644
index 0000000..1f9a228
--- /dev/null
+++ b/docs/SESSION_GUIDE.md
@@ -0,0 +1,221 @@
+# Session Management Guide
+
+This guide explains how to use the session-based authentication system implemented in the backend.
+
+## Overview
+
+The backend now supports persistent login sessions using:
+- **express-session** for server-side session management
+- **HTTP-only cookies** for secure session storage
+- **Database session tracking** with automatic cleanup
+- **CSRF protection** through HTTP-only cookies
+
+## Environment Setup
+
+1. Copy `.env.example` to `.env`:
+```bash
+cp .env.example .env
+```
+
+2. Update the `SESSION_SECRET` in your `.env` file with a secure random string:
+```
+SESSION_SECRET="your-very-secure-secret-key-change-this-in-production-make-it-long-and-random"
+```
+
+## API Endpoints
+
+### Authentication Endpoints
+
+#### Register User
+- **POST** `/api/register`
+- **Body**: `{ username, password, name, email }`
+- **Response**: User data + session created
+- **Sets**: HTTP-only session cookie
+
+#### Login
+- **POST** `/api/authenticate`
+- **Body**: `{ username, password }`
+- **Response**: User data + session created
+- **Sets**: HTTP-only session cookie
+
+#### Logout
+- **POST** `/api/logout`
+- **Requires**: Valid session
+- **Response**: Success message
+- **Clears**: Session cookie and database sessions
+
+### Session Management
+
+#### Check Session Status
+- **GET** `/api/session`
+- **Response**: `{ authenticated: boolean, user?: UserData }`
+- **Use**: Check if user is still logged in
+
+#### Get Current User
+- **GET** `/api/me`
+- **Requires**: Valid session
+- **Response**: Current user data
+
+## Frontend Integration
+
+### Making Authenticated Requests
+
+Always include credentials in your fetch requests:
+
+```javascript
+// Example fetch with credentials
+const response = await fetch('http://localhost:5000/api/me', {
+ method: 'GET',
+ credentials: 'include', // This is crucial!
+ headers: {
+ 'Content-Type': 'application/json',
+ }
+});
+```
+
+### Login Flow Example
+
+```javascript
+// Login
+const loginResponse = await fetch('http://localhost:5000/api/authenticate', {
+ method: 'POST',
+ credentials: 'include',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ username: 'user@example.com',
+ password: 'password123'
+ })
+});
+
+if (loginResponse.ok) {
+ const userData = await loginResponse.json();
+ console.log('Logged in:', userData);
+ // Session cookie is automatically set
+}
+```
+
+### Check Authentication Status
+
+```javascript
+// Check if user is still logged in
+const sessionResponse = await fetch('http://localhost:5000/api/session', {
+ method: 'GET',
+ credentials: 'include'
+});
+
+const sessionData = await sessionResponse.json();
+if (sessionData.authenticated) {
+ console.log('User is logged in:', sessionData.user);
+} else {
+ console.log('User is not logged in');
+}
+```
+
+### Logout
+
+```javascript
+// Logout
+const logoutResponse = await fetch('http://localhost:5000/api/logout', {
+ method: 'POST',
+ credentials: 'include'
+});
+
+if (logoutResponse.ok) {
+ console.log('Logged out successfully');
+ // Session cookie is automatically cleared
+}
+```
+
+## Security Features
+
+### Session Security
+- **HTTP-only cookies**: Prevents XSS attacks
+- **Secure cookies**: Enabled in production (HTTPS)
+- **SameSite protection**: Prevents CSRF attacks
+- **Session expiration**: 24-hour automatic expiry
+
+### Database Sessions
+- Sessions are stored in the database for server-side validation
+- Expired sessions are automatically cleaned up every hour
+- User logout removes all sessions for that user
+
+### Password Security
+- Passwords are hashed with bcrypt (12 rounds)
+- Unique salt per user for additional security
+- Original password never stored
+
+## Middleware
+
+### `requireAuth` Middleware
+Protects routes that need authentication:
+
+```javascript
+app.get('/api/protected-route', requireAuth, (req, res) => {
+ // req.user contains the authenticated user data
+ const user = req.user;
+ res.json({ message: 'This is protected', user });
+});
+```
+
+## Production Considerations
+
+### Environment Variables
+```env
+SESSION_SECRET="your-production-secret-minimum-32-characters-long"
+NODE_ENV="production"
+FRONTEND_URL="https://yourdomain.com"
+```
+
+### Security Settings
+In production, ensure:
+1. `SESSION_SECRET` is a strong, unique string
+2. `secure: true` in cookie settings (HTTPS only)
+3. Proper CORS origin configuration
+4. HTTPS enabled for your domain
+
+### Database
+- Consider using PostgreSQL or MySQL instead of SQLite
+- Implement database connection pooling
+- Set up database backups
+
+## Troubleshooting
+
+### Common Issues
+
+1. **Session not persisting**: Make sure `credentials: 'include'` is set in frontend requests
+2. **CORS errors**: Verify CORS origin matches your frontend URL
+3. **Session expires immediately**: Check system time and session expiration settings
+4. **Authentication fails after server restart**: Sessions are stored in database, so they should persist
+
+### Debug Session Issues
+
+Check session status:
+```javascript
+fetch('http://localhost:5000/api/session', {
+ credentials: 'include'
+}).then(r => r.json()).then(console.log);
+```
+
+### Clear All Sessions
+If you need to force logout all users:
+```sql
+DELETE FROM Session;
+```
+
+## Database Schema
+
+The session system adds a `Session` table:
+
+```prisma
+model Session {
+ id String @id @default(cuid())
+ userId Int
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+ createdAt DateTime @default(now())
+ expiresAt DateTime
+}
+```
+
+Sessions are automatically linked to users and cleaned up when users are deleted. \ No newline at end of file
diff --git a/frontend/.gitignore b/frontend/.gitignore
index a547bf3..7ceb59f 100644
--- a/frontend/.gitignore
+++ b/frontend/.gitignore
@@ -22,3 +22,4 @@ dist-ssr
*.njsproj
*.sln
*.sw?
+.env
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index d8a5110..aaaaa31 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -8,8 +8,11 @@
"name": "frontend",
"version": "0.0.0",
"dependencies": {
+ "@tailwindcss/vite": "^4.1.11",
"react": "^19.1.0",
- "react-dom": "^19.1.0"
+ "react-dom": "^19.1.0",
+ "react-router": "^7.6.3",
+ "tailwindcss": "^4.1.11"
},
"devDependencies": {
"@eslint/js": "^9.25.0",
@@ -29,7 +32,6 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
- "dev": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
@@ -308,7 +310,6 @@
"cpu": [
"ppc64"
],
- "dev": true,
"optional": true,
"os": [
"aix"
@@ -324,7 +325,6 @@
"cpu": [
"arm"
],
- "dev": true,
"optional": true,
"os": [
"android"
@@ -340,7 +340,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"android"
@@ -356,7 +355,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"android"
@@ -372,7 +370,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"darwin"
@@ -388,7 +385,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"darwin"
@@ -404,7 +400,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"freebsd"
@@ -420,7 +415,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"freebsd"
@@ -436,7 +430,6 @@
"cpu": [
"arm"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -452,7 +445,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -468,7 +460,6 @@
"cpu": [
"ia32"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -484,7 +475,6 @@
"cpu": [
"loong64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -500,7 +490,6 @@
"cpu": [
"mips64el"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -516,7 +505,6 @@
"cpu": [
"ppc64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -532,7 +520,6 @@
"cpu": [
"riscv64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -548,7 +535,6 @@
"cpu": [
"s390x"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -564,7 +550,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -580,7 +565,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"netbsd"
@@ -596,7 +580,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"netbsd"
@@ -612,7 +595,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"openbsd"
@@ -628,7 +610,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"openbsd"
@@ -644,7 +625,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"sunos"
@@ -660,7 +640,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"win32"
@@ -676,7 +655,6 @@
"cpu": [
"ia32"
],
- "dev": true,
"optional": true,
"os": [
"win32"
@@ -692,7 +670,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"win32"
@@ -917,11 +894,22 @@
"url": "https://github.com/sponsors/nzakas"
}
},
+ "node_modules/@isaacs/fs-minipass": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
+ "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^7.0.4"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
- "dev": true,
"dependencies": {
"@jridgewell/set-array": "^1.2.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
@@ -935,7 +923,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "dev": true,
"engines": {
"node": ">=6.0.0"
}
@@ -944,7 +931,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
- "dev": true,
"engines": {
"node": ">=6.0.0"
}
@@ -952,14 +938,12 @@
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
- "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
- "dev": true
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
- "dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
@@ -1013,7 +997,6 @@
"cpu": [
"arm"
],
- "dev": true,
"optional": true,
"os": [
"android"
@@ -1026,7 +1009,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"android"
@@ -1039,7 +1021,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"darwin"
@@ -1052,7 +1033,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"darwin"
@@ -1065,7 +1045,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"freebsd"
@@ -1078,7 +1057,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"freebsd"
@@ -1091,7 +1069,6 @@
"cpu": [
"arm"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -1104,7 +1081,6 @@
"cpu": [
"arm"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -1117,7 +1093,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -1130,7 +1105,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -1143,7 +1117,6 @@
"cpu": [
"loong64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -1156,7 +1129,6 @@
"cpu": [
"ppc64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -1169,7 +1141,6 @@
"cpu": [
"riscv64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -1182,7 +1153,6 @@
"cpu": [
"riscv64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -1195,7 +1165,6 @@
"cpu": [
"s390x"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -1208,7 +1177,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -1221,7 +1189,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -1234,7 +1201,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"win32"
@@ -1247,7 +1213,6 @@
"cpu": [
"ia32"
],
- "dev": true,
"optional": true,
"os": [
"win32"
@@ -1260,12 +1225,273 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"win32"
]
},
+ "node_modules/@tailwindcss/node": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz",
+ "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@ampproject/remapping": "^2.3.0",
+ "enhanced-resolve": "^5.18.1",
+ "jiti": "^2.4.2",
+ "lightningcss": "1.30.1",
+ "magic-string": "^0.30.17",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.1.11"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz",
+ "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "detect-libc": "^2.0.4",
+ "tar": "^7.4.3"
+ },
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.1.11",
+ "@tailwindcss/oxide-darwin-arm64": "4.1.11",
+ "@tailwindcss/oxide-darwin-x64": "4.1.11",
+ "@tailwindcss/oxide-freebsd-x64": "4.1.11",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.1.11",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.1.11",
+ "@tailwindcss/oxide-linux-x64-musl": "4.1.11",
+ "@tailwindcss/oxide-wasm32-wasi": "4.1.11",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.1.11"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz",
+ "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz",
+ "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz",
+ "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz",
+ "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz",
+ "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz",
+ "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz",
+ "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz",
+ "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz",
+ "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz",
+ "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.4.3",
+ "@emnapi/runtime": "^1.4.3",
+ "@emnapi/wasi-threads": "^1.0.2",
+ "@napi-rs/wasm-runtime": "^0.2.11",
+ "@tybys/wasm-util": "^0.9.0",
+ "tslib": "^2.8.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz",
+ "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz",
+ "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/vite": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz",
+ "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==",
+ "license": "MIT",
+ "dependencies": {
+ "@tailwindcss/node": "4.1.11",
+ "@tailwindcss/oxide": "4.1.11",
+ "tailwindcss": "4.1.11"
+ },
+ "peerDependencies": {
+ "vite": "^5.2.0 || ^6 || ^7"
+ }
+ },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -1776,6 +2002,15 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/chownr": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
+ "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -1806,6 +2041,15 @@
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true
},
+ "node_modules/cookie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -1849,17 +2093,38 @@
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true
},
+ "node_modules/detect-libc": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
+ "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/electron-to-chromium": {
"version": "1.5.168",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.168.tgz",
"integrity": "sha512-RUNQmFLNIWVW6+z32EJQ5+qx8ci6RGvdtDC0Ls+F89wz6I2AthpXF0w0DIrn2jpLX0/PU9ZCo+Qp7bg/EckJmA==",
"dev": true
},
+ "node_modules/enhanced-resolve": {
+ "version": "5.18.2",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz",
+ "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==",
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
"node_modules/esbuild": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
"integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
- "dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
@@ -2202,7 +2467,6 @@
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
@@ -2245,6 +2509,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "license": "ISC"
+ },
"node_modules/graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@@ -2330,6 +2600,15 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true
},
+ "node_modules/jiti": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
+ "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -2412,6 +2691,234 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/lightningcss": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
+ "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-darwin-arm64": "1.30.1",
+ "lightningcss-darwin-x64": "1.30.1",
+ "lightningcss-freebsd-x64": "1.30.1",
+ "lightningcss-linux-arm-gnueabihf": "1.30.1",
+ "lightningcss-linux-arm64-gnu": "1.30.1",
+ "lightningcss-linux-arm64-musl": "1.30.1",
+ "lightningcss-linux-x64-gnu": "1.30.1",
+ "lightningcss-linux-x64-musl": "1.30.1",
+ "lightningcss-win32-arm64-msvc": "1.30.1",
+ "lightningcss-win32-x64-msvc": "1.30.1"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz",
+ "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz",
+ "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz",
+ "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz",
+ "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz",
+ "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz",
+ "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz",
+ "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz",
+ "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz",
+ "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz",
+ "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -2442,6 +2949,15 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/magic-string": {
+ "version": "0.30.17",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
+ "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0"
+ }
+ },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -2476,6 +2992,42 @@
"node": "*"
}
},
+ "node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/minizlib": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz",
+ "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==",
+ "license": "MIT",
+ "dependencies": {
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/mkdirp": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
+ "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
+ "license": "MIT",
+ "bin": {
+ "mkdirp": "dist/cjs/src/bin.js"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -2486,7 +3038,6 @@
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -2592,8 +3143,7 @@
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "dev": true
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
},
"node_modules/picomatch": {
"version": "2.3.1",
@@ -2611,7 +3161,6 @@
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
- "dev": true,
"funding": [
{
"type": "opencollective",
@@ -2701,6 +3250,28 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-router": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.3.tgz",
+ "integrity": "sha512-zf45LZp5skDC6I3jDLXQUu0u26jtuP4lEGbc7BbdyxenBN1vJSTA18czM2D+h5qyMBuMrD+9uB+mU37HIoKGRA==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -2724,7 +3295,6 @@
"version": "4.43.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.43.0.tgz",
"integrity": "sha512-wdN2Kd3Twh8MAEOEJZsuxuLKCsBEo4PVNLK6tQWAn10VhsVewQLzcucMgLolRlhFybGxfclbPeEYBaP6RvUFGg==",
- "dev": true,
"dependencies": {
"@types/estree": "1.0.7"
},
@@ -2762,8 +3332,7 @@
"node_modules/rollup/node_modules/@types/estree": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
- "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
- "dev": true
+ "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="
},
"node_modules/run-parallel": {
"version": "1.2.0",
@@ -2802,6 +3371,12 @@
"semver": "bin/semver.js"
}
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
+ "license": "MIT"
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -2827,7 +3402,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -2856,11 +3430,51 @@
"node": ">=8"
}
},
+ "node_modules/tailwindcss": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz",
+ "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==",
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz",
+ "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/tar": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
+ "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
+ "license": "ISC",
+ "dependencies": {
+ "@isaacs/fs-minipass": "^4.0.0",
+ "chownr": "^3.0.0",
+ "minipass": "^7.1.2",
+ "minizlib": "^3.0.1",
+ "mkdirp": "^3.0.1",
+ "yallist": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tar/node_modules/yallist": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
+ "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/tinyglobby": {
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
"integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
- "dev": true,
"dependencies": {
"fdir": "^6.4.4",
"picomatch": "^4.0.2"
@@ -2876,7 +3490,6 @@
"version": "6.4.6",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
"integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
- "dev": true,
"peerDependencies": {
"picomatch": "^3 || ^4"
},
@@ -2890,7 +3503,6 @@
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
- "dev": true,
"engines": {
"node": ">=12"
},
@@ -3012,7 +3624,6 @@
"version": "6.3.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
- "dev": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
@@ -3086,7 +3697,6 @@
"version": "6.4.6",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
"integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
- "dev": true,
"peerDependencies": {
"picomatch": "^3 || ^4"
},
@@ -3100,7 +3710,6 @@
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
- "dev": true,
"engines": {
"node": ">=12"
},
diff --git a/frontend/package.json b/frontend/package.json
index 5c32b8a..533a099 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -10,8 +10,11 @@
"preview": "vite preview"
},
"dependencies": {
+ "@tailwindcss/vite": "^4.1.11",
"react": "^19.1.0",
- "react-dom": "^19.1.0"
+ "react-dom": "^19.1.0",
+ "react-router": "^7.6.3",
+ "tailwindcss": "^4.1.11"
},
"devDependencies": {
"@eslint/js": "^9.25.0",
diff --git a/frontend/src/App.css b/frontend/src/App.css
deleted file mode 100644
index b9d355d..0000000
--- a/frontend/src/App.css
+++ /dev/null
@@ -1,42 +0,0 @@
-#root {
- max-width: 1280px;
- margin: 0 auto;
- padding: 2rem;
- text-align: center;
-}
-
-.logo {
- height: 6em;
- padding: 1.5em;
- will-change: filter;
- transition: filter 300ms;
-}
-.logo:hover {
- filter: drop-shadow(0 0 2em #646cffaa);
-}
-.logo.react:hover {
- filter: drop-shadow(0 0 2em #61dafbaa);
-}
-
-@keyframes logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
-
-@media (prefers-reduced-motion: no-preference) {
- a:nth-of-type(2) .logo {
- animation: logo-spin infinite 20s linear;
- }
-}
-
-.card {
- padding: 2em;
-}
-
-.read-the-docs {
- color: #888;
-}
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 3d7ded3..d317805 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,35 +1,21 @@
-import { useState } from 'react'
-import reactLogo from './assets/react.svg'
-import viteLogo from '/vite.svg'
-import './App.css'
+import { Routes, Route } from 'react-router';
+import { AuthProvider } from './contexts/AuthContext';
+import Landing from './pages/Landing';
+import Login from './pages/Login';
+import Register from './pages/Register';
+import Home from './pages/Home';
function App() {
- const [count, setCount] = useState(0)
-
return (
- <>
- <div>
- <a href="https://vite.dev" target="_blank">
- <img src={viteLogo} className="logo" alt="Vite logo" />
- </a>
- <a href="https://react.dev" target="_blank">
- <img src={reactLogo} className="logo react" alt="React logo" />
- </a>
- </div>
- <h1>Vite + React</h1>
- <div className="card">
- <button onClick={() => setCount((count) => count + 1)}>
- count is {count}
- </button>
- <p>
- Edit <code>src/App.tsx</code> and save to test HMR
- </p>
- </div>
- <p className="read-the-docs">
- Click on the Vite and React logos to learn more
- </p>
- </>
- )
+ <AuthProvider>
+ <Routes>
+ <Route path="/" element={<Landing />} />
+ <Route path="/login" element={<Login />} />
+ <Route path="/register" element={<Register />} />
+ <Route path="/home" element={<Home />} />
+ </Routes>
+ </AuthProvider>
+ );
}
-export default App
+export default App; \ No newline at end of file
diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg
deleted file mode 100644
index 6c87de9..0000000
--- a/frontend/src/assets/react.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg> \ No newline at end of file
diff --git a/frontend/src/contexts/AuthContext.tsx b/frontend/src/contexts/AuthContext.tsx
new file mode 100644
index 0000000..7e2668d
--- /dev/null
+++ b/frontend/src/contexts/AuthContext.tsx
@@ -0,0 +1,139 @@
+import React, { createContext, useContext, useState, useEffect } from 'react';
+import type { ReactNode } from 'react';
+import { authApi } from '../utils/api';
+import type { User as ApiUser, SessionResponse } from '../utils/api';
+
+interface User {
+ id: string;
+ username: string;
+ email: string;
+}
+
+interface AuthContextType {
+ user: User | null;
+ isLoading: boolean;
+ isAuthenticated: boolean;
+ login: (username: string, password: string) => Promise<{ success: boolean; error?: string }>;
+ register: (userData: { username: string; email: string; password: string }) => Promise<{ success: boolean; error?: string }>;
+ logout: () => Promise<void>;
+ checkAuth: () => Promise<void>;
+}
+
+const AuthContext = createContext<AuthContextType | undefined>(undefined);
+
+export const useAuth = () => {
+ const context = useContext(AuthContext);
+ if (context === undefined) {
+ throw new Error('useAuth must be used within an AuthProvider');
+ }
+ return context;
+};
+
+interface AuthProviderProps {
+ children: ReactNode;
+}
+
+export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
+ const [user, setUser] = useState<User | null>(null);
+ const [isLoading, setIsLoading] = useState(true);
+
+ const isAuthenticated = user !== null;
+
+ const transformApiUser = (apiUser: ApiUser): User => ({
+ id: apiUser.id.toString(),
+ username: apiUser.username,
+ email: apiUser.email,
+ });
+
+ const checkAuth = async () => {
+ try {
+ const response = await authApi.getSession();
+
+ if (response.error || !response.data) {
+ setUser(null);
+ return;
+ }
+
+ const sessionData = response.data as SessionResponse;
+
+ if (sessionData.authenticated && sessionData.user) {
+ setUser(transformApiUser(sessionData.user));
+ } else {
+ setUser(null);
+ }
+ } catch (error) {
+ console.error('Auth check failed:', error);
+ setUser(null);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const login = async (username: string, password: string) => {
+ try {
+ const response = await authApi.login({ username, password });
+
+ if (response.error) {
+ return { success: false, error: response.error };
+ }
+
+ if (response.data) {
+ setUser(transformApiUser(response.data as ApiUser));
+ }
+
+ return { success: true };
+ } catch (error) {
+ console.error('Login failed:', error);
+ return { success: false, error: 'Network error. Please try again.' };
+ }
+ };
+
+ const register = async (userData: { username: string; email: string; password: string }) => {
+ try {
+ const response = await authApi.register(userData);
+
+ if (response.error) {
+ return { success: false, error: response.error };
+ }
+
+ if (response.data) {
+ setUser(transformApiUser(response.data as ApiUser));
+ }
+
+ return { success: true };
+ } catch (error) {
+ console.error('Registration failed:', error);
+ return { success: false, error: 'Network error. Please try again.' };
+ }
+ };
+
+ const logout = async () => {
+ try {
+ await authApi.logout();
+ } catch (error) {
+ console.error('Logout error:', error);
+ } finally {
+ setUser(null);
+ }
+ };
+
+ useEffect(() => {
+ checkAuth();
+ }, []);
+
+ const value: AuthContextType = {
+ user,
+ isLoading,
+ isAuthenticated,
+ login,
+ register,
+ logout,
+ checkAuth,
+ };
+
+ return (
+ <AuthContext.Provider value={value}>
+ {children}
+ </AuthContext.Provider>
+ );
+};
diff --git a/frontend/src/index.css b/frontend/src/index.css
index 08a3ac9..f1d8c73 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -1,68 +1 @@
-:root {
- font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
- line-height: 1.5;
- font-weight: 400;
-
- color-scheme: light dark;
- color: rgba(255, 255, 255, 0.87);
- background-color: #242424;
-
- font-synthesis: none;
- text-rendering: optimizeLegibility;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-a {
- font-weight: 500;
- color: #646cff;
- text-decoration: inherit;
-}
-a:hover {
- color: #535bf2;
-}
-
-body {
- margin: 0;
- display: flex;
- place-items: center;
- min-width: 320px;
- min-height: 100vh;
-}
-
-h1 {
- font-size: 3.2em;
- line-height: 1.1;
-}
-
-button {
- border-radius: 8px;
- border: 1px solid transparent;
- padding: 0.6em 1.2em;
- font-size: 1em;
- font-weight: 500;
- font-family: inherit;
- background-color: #1a1a1a;
- cursor: pointer;
- transition: border-color 0.25s;
-}
-button:hover {
- border-color: #646cff;
-}
-button:focus,
-button:focus-visible {
- outline: 4px auto -webkit-focus-ring-color;
-}
-
-@media (prefers-color-scheme: light) {
- :root {
- color: #213547;
- background-color: #ffffff;
- }
- a:hover {
- color: #747bff;
- }
- button {
- background-color: #f9f9f9;
- }
-}
+@import "tailwindcss";
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index bef5202..0467d26 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -1,10 +1,13 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
-import App from './App.tsx'
+import App from './App'
+import { BrowserRouter } from "react-router";
createRoot(document.getElementById('root')!).render(
<StrictMode>
- <App />
+ <BrowserRouter>
+ <App />
+ </BrowserRouter>
</StrictMode>,
)
diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx
new file mode 100644
index 0000000..1d87c8a
--- /dev/null
+++ b/frontend/src/pages/Home.tsx
@@ -0,0 +1,119 @@
+import { Link, useNavigate } from 'react-router';
+import { useAuth } from '../contexts/AuthContext';
+
+const Home = () => {
+ const { user, isLoading, logout } = useAuth();
+ const navigate = useNavigate();
+
+ const handleLogout = async () => {
+ try {
+ await logout();
+ navigate('/');
+ } catch (error) {
+ console.error('Logout failed:', error);
+ alert('Network error during logout. Please try again.');
+ }
+ };
+
+ if (isLoading) {
+ return (
+ <div className="min-h-screen bg-slate-950 flex items-center justify-center">
+ <div className="text-center">
+ <div className="w-8 h-8 border-2 border-violet-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"></div>
+ <p className="text-slate-300">Loading dashboard...</p>
+ </div>
+ </div>
+ );
+ }
+
+ if (!user) {
+ return (
+ <div className="min-h-screen bg-slate-950 flex items-center justify-center">
+ <div className="text-center max-w-md mx-auto px-6">
+ <div className="bg-slate-900 rounded-lg p-8 border border-slate-700">
+ <h2 className="text-2xl font-bold text-white mb-4">Session Expired</h2>
+ <p className="text-slate-300 mb-6">Please sign in to access your dashboard.</p>
+ <div className="space-y-3">
+ <Link
+ to="/login"
+ className="block w-full bg-violet-600 hover:bg-violet-700 text-white py-3 rounded-md font-medium transition-colors"
+ >
+ Sign In
+ </Link>
+ <Link
+ to="/"
+ className="block w-full border border-slate-600 hover:border-violet-500 text-slate-300 hover:text-white py-3 rounded-md font-medium transition-colors"
+ >
+ Back to Home
+ </Link>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ return (
+ <div className="min-h-screen bg-slate-950">
+ {/* Navigation */}
+ <nav className="border-b border-slate-800 bg-slate-950/95 backdrop-blur-sm sticky top-0 z-50">
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+ <div className="flex items-center justify-between h-16">
+ <div className="flex items-center space-x-3">
+ <div className="w-8 h-8 bg-violet-600 rounded-md flex items-center justify-center">
+ <span className="text-white font-bold text-sm">M</span>
+ </div>
+ <span className="text-white font-semibold text-lg">Mirage</span>
+ </div>
+ <div className="flex items-center space-x-4">
+ <span className="text-slate-300 text-sm">Welcome back, {user.username}</span>
+ <button
+ onClick={handleLogout}
+ className="text-slate-300 hover:text-white px-3 py-2 rounded-md text-sm font-medium transition-colors"
+ >
+ Sign Out
+ </button>
+ </div>
+ </div>
+ </div>
+ </nav>
+
+ {/* Main Content */}
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
+ {/* Header */}
+ <div className="mb-8">
+ <h1 className="text-3xl font-bold text-white mb-2">Dashboard</h1>
+ <p className="text-slate-400">Track your rhythm game progress and performance</p>
+ </div>
+
+ {/* Coming Soon Card */}
+ <div className="bg-slate-900 rounded-lg border border-slate-700 p-12 text-center">
+ <div className="w-16 h-16 bg-violet-600/20 rounded-full flex items-center justify-center mx-auto mb-6">
+ <svg className="w-8 h-8 text-violet-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
+ </svg>
+ </div>
+ <h2 className="text-2xl font-bold text-white mb-4">Dashboard Coming Soon</h2>
+ <p className="text-slate-300 mb-8 max-w-2xl mx-auto">
+ We're working hard to bring you an amazing dashboard experience. Track your scores,
+ analyze your performance, and compete with friends - all coming soon!
+ </p>
+ <div className="flex flex-col sm:flex-row items-center justify-center gap-4">
+ <div className="bg-slate-800 px-6 py-3 rounded-lg border border-slate-600">
+ <p className="text-sm text-slate-300">
+ <span className="font-semibold text-violet-300">User ID:</span> {user.id}
+ </p>
+ </div>
+ <div className="bg-slate-800 px-6 py-3 rounded-lg border border-slate-600">
+ <p className="text-sm text-slate-300">
+ <span className="font-semibold text-violet-300">Email:</span> {user.email}
+ </p>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+};
+
+export default Home;
diff --git a/frontend/src/pages/Landing.tsx b/frontend/src/pages/Landing.tsx
new file mode 100644
index 0000000..7cd0f43
--- /dev/null
+++ b/frontend/src/pages/Landing.tsx
@@ -0,0 +1,154 @@
+import { Link } from 'react-router';
+
+const Landing = () => {
+ return (
+ <div className="min-h-screen bg-slate-950 text-slate-100">
+ {/* Navigation */}
+ <nav className="border-b border-slate-800 bg-slate-950/95 backdrop-blur-sm sticky top-0 z-50">
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+ <div className="flex items-center justify-between h-16">
+ <div className="flex items-center space-x-3">
+ <div className="w-8 h-8 bg-violet-600 rounded-md flex items-center justify-center">
+ <span className="text-white font-bold text-sm">M</span>
+ </div>
+ <span className="text-white font-semibold text-lg">Mirage</span>
+ </div>
+ <div className="flex items-center space-x-4">
+ <Link
+ to="/login"
+ className="text-slate-300 hover:text-white px-3 py-2 rounded-md text-sm font-medium transition-colors"
+ >
+ Log In
+ </Link>
+ <Link
+ to="/register"
+ className="bg-violet-600 hover:bg-violet-700 text-white px-4 py-2 rounded-md text-sm font-medium transition-colors"
+ >
+ Create Account
+ </Link>
+ </div>
+ </div>
+ </div>
+ </nav>
+
+ {/* Hero Section with Banner */}
+ <section className="relative py-32 lg:py-40 overflow-hidden">
+ {/* Background Image */}
+ <div className="absolute inset-0 bg-cover bg-center bg-no-repeat opacity-20"
+ style={{backgroundImage: "url('https://images.unsplash.com/photo-1493225457124-a3eb161ffa5f?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80')"}}></div>
+
+ {/* Overlay */}
+ <div className="absolute inset-0 bg-slate-950/60"></div>
+
+ {/* Content */}
+ <div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+ <div className="max-w-4xl">
+ <h1 className="text-5xl md:text-6xl font-bold text-white mb-6 leading-tight">
+ Welcome to <span className="text-violet-400">Mirage!</span>
+ </h1>
+ <p className="text-xl text-slate-300 mb-8 leading-relaxed">
+ Looks like you're not logged in. If you've got an account, <Link to="/login" className="text-violet-400 hover:text-violet-300 underline">Login!</Link>
+ </p>
+ </div>
+ </div>
+ </section>
+
+ {/* Introduction Section */}
+ <section className="py-16 border-t border-slate-800">
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+ <div className="max-w-4xl">
+ <h2 className="text-3xl md:text-4xl font-bold text-white mb-6">
+ I'm New Around Here, What is this?
+ </h2>
+ <p className="text-lg text-slate-300 leading-relaxed">
+ <span className="font-semibold text-violet-300">Mirage</span> is a Rhythm Game Score Tracker. That means we...
+ </p>
+ </div>
+ </div>
+ </section>
+
+ {/* Track Your Scores Section */}
+ <section className="py-16 border-t border-slate-800">
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+ <div className="max-w-4xl">
+ <h2 className="text-3xl md:text-4xl font-bold text-white mb-6">
+ <span className="text-violet-400">Track</span> Your Scores.
+ </h2>
+ <p className="text-lg text-slate-300 leading-relaxed">
+ Mirage supports a bunch of your favourite games, and integrates with many existing services to
+ make sure no score is lost to the void. Furthermore, it's backed by an Open-Source API, so your
+ scores are always available!
+ </p>
+ </div>
+ </div>
+ </section>
+
+ {/* Analyse Your Scores Section */}
+ <section className="py-16 border-t border-slate-800">
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+ <div className="max-w-4xl">
+ <h2 className="text-3xl md:text-4xl font-bold text-white mb-6">
+ <span className="text-purple-400">Analyse</span> Your Scores.
+ </h2>
+ <p className="text-lg text-slate-300 leading-relaxed">
+ Mirage analyses your scores for you, breaking them down into all the statistics you'll ever need.
+ No more spreadsheets!
+ </p>
+ </div>
+ </div>
+ </section>
+
+ {/* Provide Cool Features Section */}
+ <section className="py-16 border-t border-slate-800">
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+ <div className="max-w-4xl">
+ <h2 className="text-3xl md:text-4xl font-bold text-white mb-6">
+ Provide <span className="text-violet-400">Cool Features</span>.
+ </h2>
+ <p className="text-lg text-slate-300 leading-relaxed">
+ Mirage implements the features rhythm gamers already talk about. Break your scores down
+ into sessions, showcase your best metrics on your profile, study your progress on folders - it's all
+ there, and done for you!
+ </p>
+ </div>
+ </div>
+ </section>
+
+ {/* Call to Action Section */}
+ <section className="py-20 border-t border-slate-800 bg-slate-900">
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+ <div className="text-center max-w-2xl mx-auto">
+ <h2 className="text-3xl md:text-4xl font-bold text-white mb-8">
+ Ready to Start <span className="text-violet-400">Tracking?</span>
+ </h2>
+ <div className="flex flex-col sm:flex-row items-center justify-center gap-4">
+ <Link
+ to="/register"
+ className="bg-violet-600 hover:bg-violet-700 text-white px-8 py-3 rounded-md text-lg font-medium transition-colors min-w-[200px] shadow-lg shadow-violet-600/25"
+ >
+ Create Account
+ </Link>
+ <Link
+ to="/login"
+ className="border border-slate-600 hover:border-violet-500 text-slate-300 hover:text-white px-8 py-3 rounded-md text-lg font-medium transition-colors min-w-[200px]"
+ >
+ Log In
+ </Link>
+ </div>
+ </div>
+ </div>
+ </section>
+
+ {/* Footer */}
+ <footer className="border-t border-slate-800 py-12 bg-slate-950">
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+ <div className="text-center text-slate-400">
+ <a className="hover:underline" href="">GitHub</a>
+ </div>
+ </div>
+ </footer>
+ </div>
+ );
+};
+
+export default Landing;
diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx
new file mode 100644
index 0000000..5bbdefc
--- /dev/null
+++ b/frontend/src/pages/Login.tsx
@@ -0,0 +1,207 @@
+import { useState } from "react";
+import { Link, useNavigate } from "react-router";
+import { useAuth } from "../contexts/AuthContext";
+
+const Login = () => {
+ const [formData, setFormData] = useState({
+ username: "",
+ password: "",
+ });
+ const [errors, setErrors] = useState<Record<string, string>>({});
+ const [isLoading, setIsLoading] = useState(false);
+
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ const { name, value } = e.target;
+ setFormData((prev) => ({
+ ...prev,
+ [name]: value,
+ }));
+ // Clear error when user starts typing
+ if (errors[name]) {
+ setErrors((prev) => ({
+ ...prev,
+ [name]: "",
+ }));
+ }
+ };
+
+ const validateForm = () => {
+ const newErrors: Record<string, string> = {};
+
+ if (!formData.username.trim()) {
+ newErrors.username = "Username is required";
+ } else if (formData.username.length < 3) {
+ newErrors.username = "Username must be at least 3 characters";
+ }
+
+ if (!formData.password) {
+ newErrors.password = "Password is required";
+ }
+
+ setErrors(newErrors);
+ return Object.keys(newErrors).length === 0;
+ };
+
+ const { login } = useAuth();
+ const navigate = useNavigate();
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+
+ if (!validateForm()) return;
+
+ setIsLoading(true);
+ try {
+ const result = await login(formData.username, formData.password);
+
+ if (!result.success) {
+ setErrors({
+ general: result.error || "Login failed. Please try again.",
+ });
+ return;
+ }
+
+ // Redirect to home page on successful login
+ navigate("/home");
+ } catch (error) {
+ console.error("Login failed:", error);
+ setErrors({ general: "Network error. Please try again." });
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return (
+ <div className="min-h-screen bg-slate-950 flex items-center justify-center px-4 sm:px-6 lg:px-8">
+ <div className="max-w-md w-full space-y-8">
+ {/* Header */}
+ <div className="text-center">
+ <Link to="/" className="inline-flex items-center space-x-3 mb-8">
+ <div className="w-10 h-10 bg-violet-600 rounded-md flex items-center justify-center">
+ <span className="text-white font-bold text-lg">M</span>
+ </div>
+ <span className="text-white font-semibold text-2xl">Mirage</span>
+ </Link>
+ <h2 className="text-3xl font-bold text-white">
+ Sign in to your account
+ </h2>
+ <p className="mt-2 text-sm text-slate-400">
+ Or{" "}
+ <Link
+ to="/register"
+ className="font-medium text-violet-400 hover:text-violet-300 transition-colors"
+ >
+ create a new account
+ </Link>
+ </p>
+ </div>
+
+ {/* Form */}
+ <div className="bg-slate-900 rounded-lg p-8 border border-slate-700">
+ {errors.general && (
+ <div className="mb-6 bg-red-900/50 border border-red-700 rounded-md p-4">
+ <div className="flex">
+ <div className="flex-shrink-0">
+ <svg
+ className="h-5 w-5 text-red-400"
+ viewBox="0 0 20 20"
+ fill="currentColor"
+ >
+ <path
+ fillRule="evenodd"
+ d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
+ clipRule="evenodd"
+ />
+ </svg>
+ </div>
+ <div className="ml-3">
+ <p className="text-sm text-red-300">{errors.general}</p>
+ </div>
+ </div>
+ </div>
+ )}
+
+ <form onSubmit={handleSubmit} className="space-y-6">
+ <div>
+ <label
+ htmlFor="username"
+ className="block text-sm font-medium text-slate-300 mb-2"
+ >
+ Username
+ </label>
+ <input
+ type="text"
+ id="username"
+ name="username"
+ value={formData.username}
+ onChange={handleChange}
+ className="block w-full px-3 py-2 border border-slate-600 rounded-md shadow-sm bg-slate-800 text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-violet-500 focus:border-violet-500 transition-colors"
+ placeholder="Enter your username"
+ />
+ {errors.username && (
+ <p className="mt-1 text-sm text-red-400">{errors.username}</p>
+ )}
+ </div>
+
+ <div>
+ <label
+ htmlFor="password"
+ className="block text-sm font-medium text-slate-300 mb-2"
+ >
+ Password
+ </label>
+ <input
+ type="password"
+ id="password"
+ name="password"
+ value={formData.password}
+ onChange={handleChange}
+ className="block w-full px-3 py-2 border border-slate-600 rounded-md shadow-sm bg-slate-800 text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-violet-500 focus:border-violet-500 transition-colors"
+ placeholder="Enter your password"
+ />
+ {errors.password && (
+ <p className="mt-1 text-sm text-red-400">{errors.password}</p>
+ )}
+ </div>
+
+ <button
+ type="submit"
+ disabled={isLoading}
+ className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-violet-600 hover:bg-violet-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-violet-500 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
+ >
+ {isLoading ? (
+ <div className="flex items-center">
+ <svg
+ className="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ viewBox="0 0 24 24"
+ >
+ <circle
+ className="opacity-25"
+ cx="12"
+ cy="12"
+ r="10"
+ stroke="currentColor"
+ strokeWidth="4"
+ ></circle>
+ <path
+ className="opacity-75"
+ fill="currentColor"
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
+ ></path>
+ </svg>
+ Signing in...
+ </div>
+ ) : (
+ "Sign in"
+ )}
+ </button>
+ </form>
+ </div>
+ </div>
+ </div>
+ );
+};
+
+export default Login;
diff --git a/frontend/src/pages/Register.tsx b/frontend/src/pages/Register.tsx
new file mode 100644
index 0000000..1f8dde8
--- /dev/null
+++ b/frontend/src/pages/Register.tsx
@@ -0,0 +1,230 @@
+import { useState } from 'react';
+import { Link, useNavigate } from 'react-router';
+import { useAuth } from '../contexts/AuthContext';
+
+const Register = () => {
+ const [formData, setFormData] = useState({
+ username: '',
+ email: '',
+ password: '',
+ confirmPassword: ''
+ });
+ const [errors, setErrors] = useState<Record<string, string>>({});
+ const [isLoading, setIsLoading] = useState(false);
+
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ const { name, value } = e.target;
+ setFormData(prev => ({
+ ...prev,
+ [name]: value
+ }));
+ if (errors[name]) {
+ setErrors(prev => ({
+ ...prev,
+ [name]: ''
+ }));
+ }
+ };
+
+ const validateForm = () => {
+ const newErrors: Record<string, string> = {};
+
+ if (!formData.username.trim()) {
+ newErrors.username = 'Username is required';
+ } else if (formData.username.length < 3) {
+ newErrors.username = 'Username must be at least 3 characters';
+ }
+
+ if (!formData.email.trim()) {
+ newErrors.email = 'Email is required';
+ } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
+ newErrors.email = 'Please enter a valid email address';
+ }
+
+ if (!formData.password) {
+ newErrors.password = 'Password is required';
+ } else if (formData.password.length < 6) {
+ newErrors.password = 'Password must be at least 6 characters';
+ }
+
+ if (!formData.confirmPassword) {
+ newErrors.confirmPassword = 'Please confirm your password';
+ } else if (formData.password !== formData.confirmPassword) {
+ newErrors.confirmPassword = 'Passwords do not match';
+ }
+
+ setErrors(newErrors);
+ return Object.keys(newErrors).length === 0;
+ };
+
+ const { register } = useAuth();
+ const navigate = useNavigate();
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+
+ if (!validateForm()) return;
+
+ setIsLoading(true);
+ try {
+ const result = await register({
+ username: formData.username,
+ email: formData.email,
+ password: formData.password,
+ });
+
+ if (!result.success) {
+ setErrors({ general: result.error || 'Registration failed. Please try again.' });
+ return;
+ }
+
+ // Redirect to home page on successful registration
+ navigate('/home');
+ } catch (error) {
+ console.error('Registration failed:', error);
+ setErrors({ general: 'Network error. Please try again.' });
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return (
+ <div className="min-h-screen bg-slate-950 flex items-center justify-center px-4 sm:px-6 lg:px-8">
+ <div className="max-w-md w-full space-y-8">
+ {/* Header */}
+ <div className="text-center">
+ <Link to="/" className="inline-flex items-center space-x-3 mb-8">
+ <div className="w-10 h-10 bg-violet-600 rounded-md flex items-center justify-center">
+ <span className="text-white font-bold text-lg">M</span>
+ </div>
+ <span className="text-white font-semibold text-2xl">Mirage</span>
+ </Link>
+ <h2 className="text-3xl font-bold text-white">
+ Create your account
+ </h2>
+ <p className="mt-2 text-sm text-slate-400">
+ Already have an account?{' '}
+ <Link
+ to="/login"
+ className="font-medium text-violet-400 hover:text-violet-300 transition-colors"
+ >
+ Sign in here
+ </Link>
+ </p>
+ </div>
+
+ {/* Form */}
+ <div className="bg-slate-900 rounded-lg p-8 border border-slate-700">
+ {errors.general && (
+ <div className="mb-6 bg-red-900/50 border border-red-700 rounded-md p-4">
+ <div className="flex">
+ <div className="flex-shrink-0">
+ <svg className="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
+ <path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
+ </svg>
+ </div>
+ <div className="ml-3">
+ <p className="text-sm text-red-300">{errors.general}</p>
+ </div>
+ </div>
+ </div>
+ )}
+
+ <form onSubmit={handleSubmit} className="space-y-6">
+ <div>
+ <label htmlFor="username" className="block text-sm font-medium text-slate-300 mb-2">
+ Username
+ </label>
+ <input
+ type="text"
+ id="username"
+ name="username"
+ value={formData.username}
+ onChange={handleChange}
+ className="block w-full px-3 py-2 border border-slate-600 rounded-md shadow-sm bg-slate-800 text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-violet-500 focus:border-violet-500 transition-colors"
+ placeholder="Choose a username"
+ />
+ {errors.username && (
+ <p className="mt-1 text-sm text-red-400">{errors.username}</p>
+ )}
+ </div>
+
+ <div>
+ <label htmlFor="email" className="block text-sm font-medium text-slate-300 mb-2">
+ Email address
+ </label>
+ <input
+ type="email"
+ id="email"
+ name="email"
+ value={formData.email}
+ onChange={handleChange}
+ className="block w-full px-3 py-2 border border-slate-600 rounded-md shadow-sm bg-slate-800 text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-violet-500 focus:border-violet-500 transition-colors"
+ placeholder="Enter your email"
+ />
+ {errors.email && (
+ <p className="mt-1 text-sm text-red-400">{errors.email}</p>
+ )}
+ </div>
+
+ <div>
+ <label htmlFor="password" className="block text-sm font-medium text-slate-300 mb-2">
+ Password
+ </label>
+ <input
+ type="password"
+ id="password"
+ name="password"
+ value={formData.password}
+ onChange={handleChange}
+ className="block w-full px-3 py-2 border border-slate-600 rounded-md shadow-sm bg-slate-800 text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-violet-500 focus:border-violet-500 transition-colors"
+ placeholder="Create a password"
+ />
+ {errors.password && (
+ <p className="mt-1 text-sm text-red-400">{errors.password}</p>
+ )}
+ </div>
+
+ <div>
+ <label htmlFor="confirmPassword" className="block text-sm font-medium text-slate-300 mb-2">
+ Confirm password
+ </label>
+ <input
+ type="password"
+ id="confirmPassword"
+ name="confirmPassword"
+ value={formData.confirmPassword}
+ onChange={handleChange}
+ className="block w-full px-3 py-2 border border-slate-600 rounded-md shadow-sm bg-slate-800 text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-violet-500 focus:border-violet-500 transition-colors"
+ placeholder="Confirm your password"
+ />
+ {errors.confirmPassword && (
+ <p className="mt-1 text-sm text-red-400">{errors.confirmPassword}</p>
+ )}
+ </div>
+
+ <button
+ type="submit"
+ disabled={isLoading}
+ className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-violet-600 hover:bg-violet-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-violet-500 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
+ >
+ {isLoading ? (
+ <div className="flex items-center">
+ <svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
+ </svg>
+ Creating account...
+ </div>
+ ) : (
+ 'Create account'
+ )}
+ </button>
+ </form>
+ </div>
+ </div>
+ </div>
+ );
+};
+
+export default Register;
diff --git a/frontend/src/utils/api.ts b/frontend/src/utils/api.ts
new file mode 100644
index 0000000..528c170
--- /dev/null
+++ b/frontend/src/utils/api.ts
@@ -0,0 +1,161 @@
+const API_BASE_URL = import.meta.env.VITE_API_URL;
+
+// Auth API functions
+export const authApi = {
+ async login(credentials: { username: string; password: string }) {
+ try {
+ const response = await fetch(`${API_BASE_URL}/authenticate`, {
+ method: 'POST',
+ credentials: 'include',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(credentials),
+ });
+
+ const data = await response.json();
+
+ if (!response.ok) {
+ return { error: data.error || `HTTP ${response.status}` };
+ }
+
+ return { data };
+ } catch (error) {
+ console.error('Login failed:', error);
+ return { error: 'Network error. Please check your connection.' };
+ }
+ },
+
+ async register(userData: {
+ username: string;
+ email: string;
+ password: string;
+ }) {
+ try {
+ const response = await fetch(`${API_BASE_URL}/register`, {
+ method: 'POST',
+ credentials: 'include',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(userData),
+ });
+
+ const data = await response.json();
+
+ if (!response.ok) {
+ return { error: data.error || `HTTP ${response.status}` };
+ }
+
+ return { data };
+ } catch (error) {
+ console.error('Registration failed:', error);
+ return { error: 'Network error. Please check your connection.' };
+ }
+ },
+
+ async logout() {
+ try {
+ const response = await fetch(`${API_BASE_URL}/logout`, {
+ method: 'POST',
+ credentials: 'include',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+
+ const data = await response.json();
+
+ if (!response.ok) {
+ return { error: data.error || `HTTP ${response.status}` };
+ }
+
+ return { data };
+ } catch (error) {
+ console.error('Logout failed:', error);
+ return { error: 'Network error. Please check your connection.' };
+ }
+ },
+
+ async getSession() {
+ try {
+ const response = await fetch(`${API_BASE_URL}/session`, {
+ method: 'GET',
+ credentials: 'include',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+
+ const data = await response.json();
+
+ if (!response.ok) {
+ return { error: data.error || `HTTP ${response.status}` };
+ }
+
+ return { data };
+ } catch (error) {
+ console.error('Session check failed:', error);
+ return { error: 'Network error. Please check your connection.' };
+ }
+ },
+
+ async getCurrentUser() {
+ try {
+ const response = await fetch(`${API_BASE_URL}/me`, {
+ method: 'GET',
+ credentials: 'include',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+
+ const data = await response.json();
+
+ if (!response.ok) {
+ return { error: data.error || `HTTP ${response.status}` };
+ }
+
+ return { data };
+ } catch (error) {
+ console.error('Get current user failed:', error);
+ return { error: 'Network error. Please check your connection.' };
+ }
+ },
+};
+
+export const infoApi = {
+ async getUsers() {
+ try {
+ const response = await fetch(`${API_BASE_URL}/users`, {
+ method: 'GET',
+ credentials: 'include',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+
+ const data = await response.json();
+
+ if (!response.ok) {
+ return { error: data.error || `HTTP ${response.status}` };
+ }
+
+ return { data };
+ } catch (error) {
+ console.error('Get users failed:', error);
+ return { error: 'Network error. Please check your connection.' };
+ }
+ },
+};
+
+export interface User {
+ id: number;
+ username: string;
+ email: string;
+}
+
+export interface SessionResponse {
+ authenticated: boolean;
+ user?: User;
+}
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index 8b0f57b..c4069b7 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -1,7 +1,8 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
+import tailwindcss from '@tailwindcss/vite'
// https://vite.dev/config/
export default defineConfig({
- plugins: [react()],
+ plugins: [react(), tailwindcss()],
})
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage