From 229718414e8febd77a51b1a96265435d99d73a5f Mon Sep 17 00:00:00 2001 From: aandrx Date: Tue, 17 Mar 2026 19:15:56 -0400 Subject: [PATCH 01/33] init: project file structure --- {src/app => public}/favicon.ico | Bin server/mongodb/actions/.gitkeep | 0 server/mongodb/index.js | 0 server/mongodb/models/.gitkeep | 0 server/utils/.gitkeep | 0 src/components/.gitkeep | 0 src/pages/api/.gitkeep | 0 src/pages/index.js | 0 src/{app => pages}/layout.tsx | 0 src/{app => pages}/page.tsx | 0 src/{app => styles}/globals.css | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename {src/app => public}/favicon.ico (100%) create mode 100644 server/mongodb/actions/.gitkeep create mode 100644 server/mongodb/index.js create mode 100644 server/mongodb/models/.gitkeep create mode 100644 server/utils/.gitkeep create mode 100644 src/components/.gitkeep create mode 100644 src/pages/api/.gitkeep create mode 100644 src/pages/index.js rename src/{app => pages}/layout.tsx (100%) rename src/{app => pages}/page.tsx (100%) rename src/{app => styles}/globals.css (100%) diff --git a/src/app/favicon.ico b/public/favicon.ico similarity index 100% rename from src/app/favicon.ico rename to public/favicon.ico diff --git a/server/mongodb/actions/.gitkeep b/server/mongodb/actions/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/server/mongodb/index.js b/server/mongodb/index.js new file mode 100644 index 0000000..e69de29 diff --git a/server/mongodb/models/.gitkeep b/server/mongodb/models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/server/utils/.gitkeep b/server/utils/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/components/.gitkeep b/src/components/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/api/.gitkeep b/src/pages/api/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/index.js b/src/pages/index.js new file mode 100644 index 0000000..e69de29 diff --git a/src/app/layout.tsx b/src/pages/layout.tsx similarity index 100% rename from src/app/layout.tsx rename to src/pages/layout.tsx diff --git a/src/app/page.tsx b/src/pages/page.tsx similarity index 100% rename from src/app/page.tsx rename to src/pages/page.tsx diff --git a/src/app/globals.css b/src/styles/globals.css similarity index 100% rename from src/app/globals.css rename to src/styles/globals.css From b909b34d4a31467a4557c06059cf95d312da7d64 Mon Sep 17 00:00:00 2001 From: aandrx Date: Tue, 17 Mar 2026 19:17:38 -0400 Subject: [PATCH 02/33] add name to readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 4e89a20..caffc39 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Project 3 - Animal Training App +Andrew Liu + ## Description Welcome to the final project of this year's dev bootcamp! For this project, you will create a full-stack animal training management app (this is a mini version of an app that BoG developed for Healing4Heroes). Your job is to develop a frontend and backend that interact with each other for deployment functionality to manage different users, animals, and training logs. Schemas for these data models can be found in `Schemas.md`. From 79c31627b199cf527058f72499ee9b703afad597 Mon Sep 17 00:00:00 2001 From: jtoolsidas Date: Tue, 17 Mar 2026 19:18:43 -0400 Subject: [PATCH 03/33] branch check --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 4e89a20..90cd1cf 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Project 3 - Animal Training App +Jaahnvi Toolsidas + ## Description Welcome to the final project of this year's dev bootcamp! For this project, you will create a full-stack animal training management app (this is a mini version of an app that BoG developed for Healing4Heroes). Your job is to develop a frontend and backend that interact with each other for deployment functionality to manage different users, animals, and training logs. Schemas for these data models can be found in `Schemas.md`. From fcf00267caff0e5338be3b0a460b39883dbfdef2 Mon Sep 17 00:00:00 2001 From: aandrx Date: Tue, 17 Mar 2026 19:31:54 -0400 Subject: [PATCH 04/33] test repo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index caffc39..b532526 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Project 3 - Animal Training App -Andrew Liu +Andrew Liu - new ## Description From 099df9b7881d239dfb73b9e237e59416957a0354 Mon Sep 17 00:00:00 2001 From: aandrx Date: Tue, 17 Mar 2026 21:01:29 -0400 Subject: [PATCH 05/33] feat: backend basics added +actions/models/types added +mongodb connection +adjusted frontend structure in index.js --- package-lock.json | 345 ++++++++++++++++++ package.json | 12 +- .../mongodb/actions/{.gitkeep => animal.ts} | 0 .../.gitkeep => actions/traininglog.ts} | 0 server/mongodb/actions/user.ts | 22 ++ server/mongodb/connectDb.ts | 10 + server/mongodb/models/Animal.ts | 35 ++ server/mongodb/models/TrainingLog.ts | 42 +++ server/mongodb/models/User.ts | 31 ++ server/mongodb/types/types.ts | 22 ++ src/pages/api/.gitkeep | 0 src/pages/api/hello.ts | 13 + src/pages/api/user.ts | 47 +++ src/pages/index.js | 7 + tsconfig.json | 3 +- 15 files changed, 583 insertions(+), 6 deletions(-) rename server/mongodb/actions/{.gitkeep => animal.ts} (100%) rename server/mongodb/{models/.gitkeep => actions/traininglog.ts} (100%) create mode 100644 server/mongodb/actions/user.ts create mode 100644 server/mongodb/connectDb.ts create mode 100644 server/mongodb/models/Animal.ts create mode 100644 server/mongodb/models/TrainingLog.ts create mode 100644 server/mongodb/models/User.ts create mode 100644 server/mongodb/types/types.ts delete mode 100644 src/pages/api/.gitkeep create mode 100644 src/pages/api/hello.ts create mode 100644 src/pages/api/user.ts diff --git a/package-lock.json b/package-lock.json index f15a1ff..d8df4a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,8 @@ "name": "project2-f25", "version": "0.1.0", "dependencies": { + "argon2": "^0.44.0", + "mongoose": "^9.3.1", "next": "16.0.1", "react": "19.2.0", "react-dom": "19.2.0" @@ -44,6 +46,12 @@ "tslib": "^2.4.0" } }, + "node_modules/@epic-web/invariant": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", + "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", + "license": "MIT" + }, "node_modules/@img/colour": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", @@ -522,6 +530,15 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.6.tgz", + "integrity": "sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, "node_modules/@next/env": { "version": "16.0.1", "resolved": "https://registry.npmjs.org/@next/env/-/env-16.0.1.tgz", @@ -656,6 +673,15 @@ "node": ">= 10" } }, + "node_modules/@phc/format": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz", + "integrity": "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -966,6 +992,46 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/argon2": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.44.0.tgz", + "integrity": "sha512-zHPGN3S55sihSQo0dBbK0A5qpi2R31z7HZDZnry3ifOyj8bZZnpZND2gpmhnRGO1V/d555RwBqIK5W4Mrmv3ig==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@phc/format": "^1.0.0", + "cross-env": "^10.0.0", + "node-addon-api": "^8.5.0", + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/bson": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-7.2.0.tgz", + "integrity": "sha512-YCEo7KjMlbNlyHhz7zAZNDpIpQbd+wOEHJYezv0nMYTn4x31eIUM2yomNNubclAt63dObUzKHWsBLJ9QcZNSnQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=20.19.0" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001751", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz", @@ -992,6 +1058,37 @@ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/cross-env": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz", + "integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==", + "license": "MIT", + "dependencies": { + "@epic-web/invariant": "^1.0.0", + "cross-spawn": "^7.0.6" + }, + "bin": { + "cross-env": "dist/bin/cross-env.js", + "cross-env-shell": "dist/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -1030,6 +1127,12 @@ "dev": true, "license": "ISC" }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, "node_modules/jiti": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", @@ -1040,6 +1143,15 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/kareem": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-3.2.0.tgz", + "integrity": "sha512-VS8MWZz/cT+SqBCpVfNN4zoVz5VskR3N4+sTmUXme55e9avQHntpwpNq0yjnosISXqwJ3AQVjlbI4Dyzv//JtA==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/lightningcss": { "version": "1.30.2", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", @@ -1311,6 +1423,116 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" + }, + "node_modules/mongodb": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.1.0.tgz", + "integrity": "sha512-kMfnKunbolQYwCIyrkxNJFB4Ypy91pYqua5NargS/f8ODNSJxT03ZU3n1JqL4mCzbSih8tvmMEMLpKTT7x5gCg==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^7.1.1", + "mongodb-connection-string-url": "^7.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.806.0", + "@mongodb-js/zstd": "^7.0.0", + "gcp-metadata": "^7.0.1", + "kerberos": "^7.0.0", + "mongodb-client-encryption": ">=7.0.0 <7.1.0", + "snappy": "^7.3.2", + "socks": "^2.8.6" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.1.tgz", + "integrity": "sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^13.0.0", + "whatwg-url": "^14.1.0" + }, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/mongoose": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-9.3.1.tgz", + "integrity": "sha512-58DuQti+LlRS74/UfWN4F3wZsC0Yr1dgTWZ2Wd3/TuSvm6rIdyAjDWbx2xGyuBooqJYdAWotVv4mQgVdivh+3Q==", + "license": "MIT", + "dependencies": { + "kareem": "3.2.0", + "mongodb": "~7.1", + "mpath": "0.9.0", + "mquery": "6.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-6.0.0.tgz", + "integrity": "sha512-b2KQNsmgtkscfeDgkYMcWGn9vZI9YoXh802VDEwE6qc50zxBFQ0Oo8ROkawbPAsXCY1/Z1yp0MagqsZStPWJjw==", + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -1409,6 +1631,35 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-addon-api": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.6.0.tgz", + "integrity": "sha512-gBVjCaqDlRUk0EwoPNKzIr9KkS9041G/q31IBShPs1Xz6UTA+EXdZADbzqAJQrpDRq71CIMnOP5VMut3SL0z5Q==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "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/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1444,6 +1695,15 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/react": { "version": "19.2.0", "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", @@ -1527,6 +1787,33 @@ "@img/sharp-win32-x64": "0.34.4" } }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sift": { + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", + "license": "MIT" + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -1536,6 +1823,15 @@ "node": ">=0.10.0" } }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, "node_modules/styled-jsx": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", @@ -1580,6 +1876,18 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -1606,6 +1914,43 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } } } } diff --git a/package.json b/package.json index 693e5fc..b6d2e99 100644 --- a/package.json +++ b/package.json @@ -8,16 +8,18 @@ "start": "next start" }, "dependencies": { + "argon2": "^0.44.0", + "mongoose": "^9.3.1", + "next": "16.0.1", "react": "19.2.0", - "react-dom": "19.2.0", - "next": "16.0.1" + "react-dom": "19.2.0" }, "devDependencies": { - "typescript": "^5", + "@tailwindcss/postcss": "^4", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", - "@tailwindcss/postcss": "^4", - "tailwindcss": "^4" + "tailwindcss": "^4", + "typescript": "^5" } } diff --git a/server/mongodb/actions/.gitkeep b/server/mongodb/actions/animal.ts similarity index 100% rename from server/mongodb/actions/.gitkeep rename to server/mongodb/actions/animal.ts diff --git a/server/mongodb/models/.gitkeep b/server/mongodb/actions/traininglog.ts similarity index 100% rename from server/mongodb/models/.gitkeep rename to server/mongodb/actions/traininglog.ts diff --git a/server/mongodb/actions/user.ts b/server/mongodb/actions/user.ts new file mode 100644 index 0000000..c53b53c --- /dev/null +++ b/server/mongodb/actions/user.ts @@ -0,0 +1,22 @@ +import { UserData } from "../types/types"; +import User from "../models/User"; + +export async function createUser(userData: UserData) { + const newUser = new User(userData); + await newUser.save(); + return newUser; +} + +export async function getUser(userId: string) { + const retrievedUser = await User.findById(userId); + return retrievedUser; +} + +export async function updateUser(userId: string, newData: UserData) { + const updatedUser = await User.findByIdAndUpdate(userId, newData, { new: true }); + return updatedUser; +} + +export async function deleteUser(userId: string) { + await User.findByIdAndDelete(userId); +} diff --git a/server/mongodb/connectDb.ts b/server/mongodb/connectDb.ts new file mode 100644 index 0000000..b8188f2 --- /dev/null +++ b/server/mongodb/connectDb.ts @@ -0,0 +1,10 @@ +import mongoose from "mongoose" + +export default async function conectDb() { + try { + await mongoose.connect(process.env.DB_URL!); + console.log("connected to mongodb"); + } catch (e) { + console.log("unable to connect", e); + } +} diff --git a/server/mongodb/models/Animal.ts b/server/mongodb/models/Animal.ts new file mode 100644 index 0000000..9fe82ec --- /dev/null +++ b/server/mongodb/models/Animal.ts @@ -0,0 +1,35 @@ +// Animal { +// _id: ObjectId // animal's ID +// name: string // animal's name +// breed: string // animal's breed +// owner: ObjectId // id of the animal's owner +// hoursTrained: number // total number of hours the animal has been trained for +// profilePicture: string // url to an image that can be displayed in an tag +// } + +import mongoose from "mongoose" + +const animalSchema = new mongoose.Schema({ + name: { + type: String, + required: true, + }, + breed: { + type: String, + required: true, + }, + owner: { + type: mongoose.Schema.Types.ObjectId, + ref: "User", + required: true, + }, + hoursTrained: { + type: Number, + default: 0, + }, + profilePicture: { + type: String, + } +}); + +export default mongoose.models.Animal || mongoose.model("Animal", animalSchema); diff --git a/server/mongodb/models/TrainingLog.ts b/server/mongodb/models/TrainingLog.ts new file mode 100644 index 0000000..cbddd4d --- /dev/null +++ b/server/mongodb/models/TrainingLog.ts @@ -0,0 +1,42 @@ +// TrainingLog { +// _id: ObjectId // training log's id +// user: ObjectId // user this training log corresponds to +// animal: ObjectId // animal this training log corresponds to +// title: string // title of training log +// date: Date // date of training log +// description: string // description of training log +// hours: number // number of hours the training log records +// } + +import mongoose from "mongoose" + +const trainingLogSchema = new mongoose.Schema({ + user: { + type: mongoose.Schema.Types.ObjectId, + ref: "User", + required: true, + }, + animal: { + type: mongoose.Schema.Types.ObjectId, + ref: "Animal", + required: true, + }, + title: { + type: String, + required: true, + }, + date: { + type: Date, + required: true, + }, + description: { + type: String, + required: true, + }, + hours: { + type: Number, + required: true, + } +}); + +export default mongoose.models.TrainingLog || mongoose.model("TrainingLog", trainingLogSchema); diff --git a/server/mongodb/models/User.ts b/server/mongodb/models/User.ts new file mode 100644 index 0000000..b31c0b4 --- /dev/null +++ b/server/mongodb/models/User.ts @@ -0,0 +1,31 @@ +// User { +// _id: ObjectId // user's ID +// fullName: string // user's full name +// email: string // user's email +// password: string // user's password +// admin: boolean // holds whether or not a user is an admin +// } + +import mongoose from "mongoose" + +const userSchema = new mongoose.Schema({ + fullName: { + type: String, + required: true, + }, + email: { + type: String, + required: true, + unique: true + }, + password: { + type: String, + required: true + }, + admin: { + type: Boolean, + default: false + } +}); + +export default mongoose.models.User || mongoose.model("User", userSchema); diff --git a/server/mongodb/types/types.ts b/server/mongodb/types/types.ts new file mode 100644 index 0000000..7ec4fa3 --- /dev/null +++ b/server/mongodb/types/types.ts @@ -0,0 +1,22 @@ +export interface UserData { + fullName: string; + email: string; + password: string; + admin?: boolean; +} + +export interface AnimalData { + name: string; + breed: string; + owner: string; // ObjectId as a string + hoursTrained?: number; + profilePicture?: string; +} + +export interface TrainingLogData { + animal: string; // ObjectId as a string + title: string; + date: Date; + description: string; + hours: number; // in hours +} diff --git a/src/pages/api/.gitkeep b/src/pages/api/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/pages/api/hello.ts b/src/pages/api/hello.ts new file mode 100644 index 0000000..ea77e8f --- /dev/null +++ b/src/pages/api/hello.ts @@ -0,0 +1,13 @@ +// Next.js API route support: https://nextjs.org/docs/api-routes/introduction +import type { NextApiRequest, NextApiResponse } from "next"; + +type Data = { + name: string; +}; + +export default function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + res.status(200).json({ name: "John Doe" }); +} diff --git a/src/pages/api/user.ts b/src/pages/api/user.ts new file mode 100644 index 0000000..bfe289c --- /dev/null +++ b/src/pages/api/user.ts @@ -0,0 +1,47 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { UserData } from "@server/mongodb/types/types"; +import { createUser, getUser, updateUser, deleteUser } from "@server/mongodb/actions/user"; +import connectDb from "../../../server/mongodb/connectDb"; + +type UserApiData = { + userData?: UserData; + message: string; +}; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + if ( req.method === "POST" ) { + try { + if (!req.body.fullName || !req.body.email || !req.body.password) { + return res.status(400).json({ message: "Missing required fields" }); + } + const userData = { + fullName: req.body.fullName, + email: req.body.email, + password: req.body.password, + admin: req.body.admin || false, + } as UserData; + + connectDb(); + const user = await createUser(userData); + res.status(201).json({ + userData: user, + message: "User created successfully" + }); + } catch (error) { + res.status(500).json({ + message: "Error creating user" + }); + } + } else if ( req.method === "GET" ) { + + } else if ( req.method === "PUT" ) { + + } else if ( req.method === "DELETE" ) { + + } else { + res.status(405).json({ message: "Method not allowed" }); + } +} diff --git a/src/pages/index.js b/src/pages/index.js index e69de29..b42a1f5 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -0,0 +1,7 @@ +export default function Home() { + return ( +
+

Hello World

+
+ ); +} diff --git a/tsconfig.json b/tsconfig.json index cf9c65d..92b857a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,7 +19,8 @@ } ], "paths": { - "@/*": ["./src/*"] + "@/*": ["./src/*"], + "@server/*": ["./server/*"] } }, "include": [ From fa846bad32692c2b416a38fc33f339d577451cd2 Mon Sep 17 00:00:00 2001 From: aandrx Date: Thu, 19 Mar 2026 14:27:33 -0400 Subject: [PATCH 06/33] feat: user endpoints complete +JWT +verification +argon +delete user --- package-lock.json | 19 +++++++++ package.json | 1 + server/jwt.ts | 11 +++++ server/mongodb/actions/animal.ts | 22 ++++++++++ server/mongodb/actions/traininglog.ts | 22 ++++++++++ server/mongodb/actions/user.ts | 11 +++-- server/utils/.gitkeep | 0 src/pages/api/animal.ts | 40 ++++++++++++++++++ src/pages/api/training.ts | 41 +++++++++++++++++++ src/pages/api/user.ts | 43 +++++++++++++++----- src/pages/api/user/verify.ts | 58 +++++++++++++++++++++++++++ src/pages/index.js | 2 + 12 files changed, 258 insertions(+), 12 deletions(-) create mode 100644 server/jwt.ts delete mode 100644 server/utils/.gitkeep create mode 100644 src/pages/api/animal.ts create mode 100644 src/pages/api/training.ts create mode 100644 src/pages/api/user/verify.ts diff --git a/package-lock.json b/package-lock.json index d8df4a2..1b1851f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4", + "@types/jsonwebtoken": "^9.0.10", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", @@ -962,6 +963,24 @@ "tailwindcss": "4.1.16" } }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.19.24", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.24.tgz", diff --git a/package.json b/package.json index b6d2e99..a9a3de5 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4", + "@types/jsonwebtoken": "^9.0.10", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", diff --git a/server/jwt.ts b/server/jwt.ts new file mode 100644 index 0000000..3690cb5 --- /dev/null +++ b/server/jwt.ts @@ -0,0 +1,11 @@ +import jwt from "jsonwebtoken"; + +const JWT_SECRET = process.env.JWT_SECRET as string; + +export function signToken(payload: { userId: string; admin: boolean }) { + return jwt.sign(payload, JWT_SECRET, { expiresIn: "7d" }); +} + +export function verifyToken(token: string) { + return jwt.verify(token, JWT_SECRET) as { userId: string; admin: boolean }; +} diff --git a/server/mongodb/actions/animal.ts b/server/mongodb/actions/animal.ts index e69de29..a1a88a4 100644 --- a/server/mongodb/actions/animal.ts +++ b/server/mongodb/actions/animal.ts @@ -0,0 +1,22 @@ +import { AnimalData } from "../types/types"; +import Animal from "../models/Animal"; + +export async function createAnimal(animalData: AnimalData) { + const newAnimal = new Animal(animalData); + await newAnimal.save(); + return newAnimal; +} + +export async function getAnimal(animalId: string) { + const retrievedAnimal = await Animal.findById(animalId); + return retrievedAnimal; +} + +export async function updateAnimal(animalId: string, newData: AnimalData) { + const updatedAnimal = await Animal.findByIdAndUpdate(animalId, newData, { new: true }); + return updatedAnimal; +} + +export async function deleteAnimal(animalId: string) { + await Animal.findByIdAndDelete(animalId); +} diff --git a/server/mongodb/actions/traininglog.ts b/server/mongodb/actions/traininglog.ts index e69de29..a6acf9a 100644 --- a/server/mongodb/actions/traininglog.ts +++ b/server/mongodb/actions/traininglog.ts @@ -0,0 +1,22 @@ +import { TrainingLogData } from "../types/types"; +import TrainingLog from "../models/TrainingLog"; + +export async function createTrainingLog(trainingLogData: TrainingLogData) { + const newTrainingLog = new TrainingLog(trainingLogData); + await newTrainingLog.save(); + return newTrainingLog; +} + +export async function getTrainingLog(trainingLogId: string) { + const retrievedTrainingLog = await TrainingLog.findById(trainingLogId); + return retrievedTrainingLog; +} + +export async function updateTrainingLog(trainingLogId: string, newData: TrainingLogData) { + const updatedTrainingLog = await TrainingLog.findByIdAndUpdate(trainingLogId, newData, { new: true }); + return updatedTrainingLog; +} + +export async function deleteTrainingLog(trainingLogId: string) { + await TrainingLog.findByIdAndDelete(trainingLogId); +} diff --git a/server/mongodb/actions/user.ts b/server/mongodb/actions/user.ts index c53b53c..d823f1b 100644 --- a/server/mongodb/actions/user.ts +++ b/server/mongodb/actions/user.ts @@ -12,9 +12,14 @@ export async function getUser(userId: string) { return retrievedUser; } -export async function updateUser(userId: string, newData: UserData) { - const updatedUser = await User.findByIdAndUpdate(userId, newData, { new: true }); - return updatedUser; +export async function getUsers() { + const retrievedUsers = await User.find({}, { password: 0}); + return retrievedUsers; +} + +export async function getUserByEmail(email: string) { + const retrievedUser = await User.findOne({ email }); + return retrievedUser; } export async function deleteUser(userId: string) { diff --git a/server/utils/.gitkeep b/server/utils/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/pages/api/animal.ts b/src/pages/api/animal.ts new file mode 100644 index 0000000..308b8a0 --- /dev/null +++ b/src/pages/api/animal.ts @@ -0,0 +1,40 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { AnimalData } from "@server/mongodb/types/types"; +import { createAnimal, getAnimal, updateAnimal, deleteAnimal } from "@server/mongodb/actions/animal"; +import connectDb from "../../../server/mongodb/connectDb"; + +type AnimalApiData = { + animalData?: AnimalData; + message: string; +}; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + if ( req.method === "POST" ) { + try { + if (!req.body.name || !req.body.breed || !req.body.owner) { + return res.status(400).json({ message: "Missing required fields" }); + } + const animalData = { + name: req.body.name, + breed: req.body.breed, + owner: req.body.owner, + hoursTrained: req.body.hoursTrained || 0, + profilePicture: req.body.profilePicture || "", + } as AnimalData; + + connectDb(); + const animal = await createAnimal(animalData); + res.status(200).json({ + animalData: animal, + message: "Animal created successfully" + }); + } catch (error) { + res.status(500).json({ + message: "Error creating animal" + }); + } + } +} diff --git a/src/pages/api/training.ts b/src/pages/api/training.ts new file mode 100644 index 0000000..7dd0a86 --- /dev/null +++ b/src/pages/api/training.ts @@ -0,0 +1,41 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { TrainingLogData } from "@server/mongodb/types/types"; +import { createTrainingLog, getTrainingLog, updateTrainingLog, deleteTrainingLog } from "@server/mongodb/actions/traininglog"; +import connectDb from "../../../server/mongodb/connectDb"; + +type TrainingLogApiData = { + trainingLogData?: TrainingLogData; + message: string; +}; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + if ( req.method === "POST" ) { + try { + if (!req.body.name || !req.body.date || !req.body.duration) { + return res.status(400).json({ message: "Missing required fields" }); + } + const trainingLogData = { + user: req.body.name, + animal: req.body.date, + title: req.body.owner, + date: req.body.duration || 0, + description: req.body.profilePicture || "", + hours: req.body.hoursTrained || 0, + } as TrainingLogData; + + connectDb(); + const trainingLog = await createTrainingLog(trainingLogData); + res.status(200).json({ + trainingLogData: trainingLog, + message: "Training log created successfully" + }); + } catch (error) { + res.status(500).json({ + message: "Error creating training log" + }); + } + } +} diff --git a/src/pages/api/user.ts b/src/pages/api/user.ts index bfe289c..1c4988c 100644 --- a/src/pages/api/user.ts +++ b/src/pages/api/user.ts @@ -1,10 +1,11 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { UserData } from "@server/mongodb/types/types"; -import { createUser, getUser, updateUser, deleteUser } from "@server/mongodb/actions/user"; -import connectDb from "../../../server/mongodb/connectDb"; +import { createUser, getUser, getUsers, deleteUser } from "@server/mongodb/actions/user"; +import connectDb from "@server/mongodb/connectDb"; +import argon2 from "argon2"; type UserApiData = { - userData?: UserData; + userData?: UserData | Omit[]; // user data w/o password for GET message: string; }; @@ -17,30 +18,54 @@ export default async function handler( if (!req.body.fullName || !req.body.email || !req.body.password) { return res.status(400).json({ message: "Missing required fields" }); } + + const hashedPassword = await argon2.hash(req.body.password, { type: argon2.argon2d }); + const userData = { fullName: req.body.fullName, email: req.body.email, - password: req.body.password, + password: hashedPassword, admin: req.body.admin || false, } as UserData; connectDb(); const user = await createUser(userData); - res.status(201).json({ + res.status(200).json({ userData: user, message: "User created successfully" }); } catch (error) { + console.error("Error creating user:", error); res.status(500).json({ - message: "Error creating user" + message: "Error creating user" }); } } else if ( req.method === "GET" ) { + try { + connectDb(); + const users = await getUsers(); - } else if ( req.method === "PUT" ) { - + res.status(200).json({ + userData: users, + message: "Users retrieved successfully" + }); + } catch (error) { + res.status(500).json({ + message: "Error retrieving users" + }); + } } else if ( req.method === "DELETE" ) { - + try { + if (!req.body.userId) { + return res.status(500).json({ message: "Deleting requires user ID" }); + } + + connectDb(); + await deleteUser(req.body.userId); + res.status(200).json({ message: "User deleted successfully" }); + } catch (error) { + res.status(500).json({ message: "Error deleting user" }); + } } else { res.status(405).json({ message: "Method not allowed" }); } diff --git a/src/pages/api/user/verify.ts b/src/pages/api/user/verify.ts new file mode 100644 index 0000000..8538798 --- /dev/null +++ b/src/pages/api/user/verify.ts @@ -0,0 +1,58 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { getUserByEmail } from "@server/mongodb/actions/user"; +import connectDb from "@server/mongodb/connectDb"; +import { signToken } from "@server/jwt"; +import argon2 from "argon2"; + +type VerifyApiData = { + userId?: string; + admin?: boolean; + message: string; +}; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + if ( req.method === "POST" ) { + try { + if (!req.body.email || !req.body.password) { + return res.status(400).json({ message: "Missing required fields" }); + } + + connectDb(); + const user = await getUserByEmail(req.body.email); + + if (!user) { + return res.status(500).json({ message: "User not found" }); + } + + const passwordMatch = await argon2.verify(user.password, req.body.password); + + if (!passwordMatch) { + return res.status(500).json({ message: "Invalid password" }); + } + + const token = signToken({ + userId: user._id.toString(), + admin: user.admin, + }); + + res.setHeader("Set-Cookie", `token=${token}; HttpOnly; Path=/; Max-Age=3600`); + + res.status(200).json({ + userId: user._id.toString(), + admin: user.admin, + message: "User verified successfully" + }); + } catch (error) { + res.status(500).json({ + message: "Error verifying user" + }); + } + } else { + res.status(500).json({ + message: "Invalid request method" + }); + } +} diff --git a/src/pages/index.js b/src/pages/index.js index b42a1f5..810124a 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -1,3 +1,5 @@ +import React from 'react'; + export default function Home() { return (
From b30bd90574c6f8b3e1aeffbe3dfefe7c7b4e6b2e Mon Sep 17 00:00:00 2001 From: aandrx Date: Thu, 19 Mar 2026 14:59:14 -0400 Subject: [PATCH 07/33] user endpoint completed +animal wip +traininglog wip --- src/pages/api/training.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pages/api/training.ts b/src/pages/api/training.ts index 7dd0a86..fa6c554 100644 --- a/src/pages/api/training.ts +++ b/src/pages/api/training.ts @@ -14,16 +14,16 @@ export default async function handler( ) { if ( req.method === "POST" ) { try { - if (!req.body.name || !req.body.date || !req.body.duration) { + if (!req.body.user || !req.body.date || !req.body.hours) { return res.status(400).json({ message: "Missing required fields" }); } const trainingLogData = { - user: req.body.name, - animal: req.body.date, - title: req.body.owner, - date: req.body.duration || 0, - description: req.body.profilePicture || "", - hours: req.body.hoursTrained || 0, + user: req.body.user, + animal: req.body.animal, + title: req.body.title, + date: req.body.date || 0, + description: req.body.description || "", + hours: req.body.hours || 0, } as TrainingLogData; connectDb(); From c72dff3c8beae3ac557de746f52b1870fd04f529 Mon Sep 17 00:00:00 2001 From: aandrx Date: Fri, 20 Mar 2026 13:49:38 -0400 Subject: [PATCH 08/33] feat: added admin/ endpoints+actions for getAll endpoints --- server/mongodb/actions/animal.ts | 9 +++++++- server/mongodb/actions/traininglog.ts | 9 +++++++- server/mongodb/actions/user.ts | 6 ++++-- src/pages/api/admin/animals.ts | 30 +++++++++++++++++++++++++++ src/pages/api/admin/training.ts | 30 +++++++++++++++++++++++++++ src/pages/api/admin/users.ts | 30 +++++++++++++++++++++++++++ src/pages/api/animal.ts | 20 ++++++++++++++++++ src/pages/api/training.ts | 30 +++++++++++++++++++++++++++ 8 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 src/pages/api/admin/animals.ts create mode 100644 src/pages/api/admin/training.ts create mode 100644 src/pages/api/admin/users.ts diff --git a/server/mongodb/actions/animal.ts b/server/mongodb/actions/animal.ts index a1a88a4..b7d99c2 100644 --- a/server/mongodb/actions/animal.ts +++ b/server/mongodb/actions/animal.ts @@ -1,5 +1,6 @@ import { AnimalData } from "../types/types"; import Animal from "../models/Animal"; +import mongoose from "mongoose"; export async function createAnimal(animalData: AnimalData) { const newAnimal = new Animal(animalData); @@ -12,7 +13,13 @@ export async function getAnimal(animalId: string) { return retrievedAnimal; } -export async function updateAnimal(animalId: string, newData: AnimalData) { +export async function getAllAnimals(limit: number = 10, lastId?: string) { + const query: any = lastId ? { _id: { $gt: new mongoose.Types.ObjectId(lastId) } } : {}; + const retrievedAnimals = await Animal.find(query).limit(limit); + return retrievedAnimals; +} + +export async function updateAnimal(animalId: string, newData: Partial) { const updatedAnimal = await Animal.findByIdAndUpdate(animalId, newData, { new: true }); return updatedAnimal; } diff --git a/server/mongodb/actions/traininglog.ts b/server/mongodb/actions/traininglog.ts index a6acf9a..d6bab84 100644 --- a/server/mongodb/actions/traininglog.ts +++ b/server/mongodb/actions/traininglog.ts @@ -1,5 +1,6 @@ import { TrainingLogData } from "../types/types"; import TrainingLog from "../models/TrainingLog"; +import mongoose from "mongoose"; export async function createTrainingLog(trainingLogData: TrainingLogData) { const newTrainingLog = new TrainingLog(trainingLogData); @@ -12,7 +13,13 @@ export async function getTrainingLog(trainingLogId: string) { return retrievedTrainingLog; } -export async function updateTrainingLog(trainingLogId: string, newData: TrainingLogData) { +export async function getAllTrainingLogs(limit: number = 10, lastId?: string) { + const query: any = lastId ? { _id: { $gt: new mongoose.Types.ObjectId(lastId) } } : {}; + const retrievedTrainingLogs = await TrainingLog.find(query).limit(limit); + return retrievedTrainingLogs; +} + +export async function updateTrainingLog(trainingLogId: string, newData: Partial) { const updatedTrainingLog = await TrainingLog.findByIdAndUpdate(trainingLogId, newData, { new: true }); return updatedTrainingLog; } diff --git a/server/mongodb/actions/user.ts b/server/mongodb/actions/user.ts index d823f1b..fedb213 100644 --- a/server/mongodb/actions/user.ts +++ b/server/mongodb/actions/user.ts @@ -1,5 +1,6 @@ import { UserData } from "../types/types"; import User from "../models/User"; +import mongoose from "mongoose"; export async function createUser(userData: UserData) { const newUser = new User(userData); @@ -12,8 +13,9 @@ export async function getUser(userId: string) { return retrievedUser; } -export async function getUsers() { - const retrievedUsers = await User.find({}, { password: 0}); +export async function getAllUsers(limit: number = 10, lastId?: string) { + const query: any = lastId ? { _id: { $gt: new mongoose.Types.ObjectId(lastId) } } : {}; + const retrievedUsers = await User.find(query, { password: 0 }).limit(limit); return retrievedUsers; } diff --git a/src/pages/api/admin/animals.ts b/src/pages/api/admin/animals.ts new file mode 100644 index 0000000..f5c6a3f --- /dev/null +++ b/src/pages/api/admin/animals.ts @@ -0,0 +1,30 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { AnimalData } from "@server/mongodb/types/types"; +import { getAllAnimals } from "@server/mongodb/actions/animal"; +import connectDb from "@server/mongodb/connectDb"; + +type AnimalApiData = { + animalData?: AnimalData[]; + message: string; +}; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + if ( req.method === "GET" ) { + try { + connectDb(); + const animals = await getAllAnimals(); + + res.status(200).json({ + animalData: animals, + message: "Animals retrieved successfully" + }); + } catch (error) { + res.status(500).json({ + message: "Error retrieving animals" + }); + } + } +} diff --git a/src/pages/api/admin/training.ts b/src/pages/api/admin/training.ts new file mode 100644 index 0000000..a84079d --- /dev/null +++ b/src/pages/api/admin/training.ts @@ -0,0 +1,30 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { TrainingLogData } from "@server/mongodb/types/types"; +import { getAllTrainingLogs } from "@server/mongodb/actions/traininglog"; +import connectDb from "@server/mongodb/connectDb"; + +type TrainingLogApiData = { + trainingLogData?: TrainingLogData[]; + message: string; +}; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + if ( req.method === "GET" ) { + try { + connectDb(); + const trainingLogs = await getAllTrainingLogs(); + + res.status(200).json({ + trainingLogData: trainingLogs, + message: "Training logs retrieved successfully" + }); + } catch (error) { + res.status(500).json({ + message: "Error retrieving training logs" + }); + } + } +} diff --git a/src/pages/api/admin/users.ts b/src/pages/api/admin/users.ts new file mode 100644 index 0000000..42b4974 --- /dev/null +++ b/src/pages/api/admin/users.ts @@ -0,0 +1,30 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { UserData } from "@server/mongodb/types/types"; +import { getAllUsers } from "@server/mongodb/actions/user"; +import connectDb from "@server/mongodb/connectDb"; + +type UserApiData = { + userData?: UserData[]; + message: string; +}; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + if ( req.method === "GET" ) { + try { + connectDb(); + const users = await getAllUsers(); + + res.status(200).json({ + userData: users, + message: "Users retrieved successfully" + }); + } catch (error) { + res.status(500).json({ + message: "Error retrieving users" + }); + } + } +} diff --git a/src/pages/api/animal.ts b/src/pages/api/animal.ts index 308b8a0..370cab0 100644 --- a/src/pages/api/animal.ts +++ b/src/pages/api/animal.ts @@ -36,5 +36,25 @@ export default async function handler( message: "Error creating animal" }); } + } else if ( req.method === "PATCH" ) { + try { + if (!req.body.id || req.body.hoursTrained === undefined) { + return res.status(400).json({ message: "Missing animal ID or hoursTrained" }); + } + + connectDb(); + const animal = await updateAnimal(req.body.id, { hoursTrained: req.body.hoursTrained }); + if (!animal) { + return res.status(500).json({ message: "Animal not found" }); + } + res.status(200).json({ + animalData: animal, + message: "Animal updated successfully" + }); + } catch (error) { + res.status(500).json({ + message: "Error updating animal" + }); + } } } diff --git a/src/pages/api/training.ts b/src/pages/api/training.ts index fa6c554..59a969d 100644 --- a/src/pages/api/training.ts +++ b/src/pages/api/training.ts @@ -37,5 +37,35 @@ export default async function handler( message: "Error creating training log" }); } + } else if ( req.method === "PATCH" ) { + try { + if (!req.body.id || !req.body.user) { + return res.status(400).json({ message: "Missing training log ID or user" }); + } + + connectDb(); + + const existingLog = await getTrainingLog(req.body.id); + if (!existingLog) { + return res.status(500).json({ message: "Training log not found" }); + } + if (existingLog.user.toString() !== req.body.user) { + return res.status(500).json({ message: "Unauthorized to update this training log" }); + } + + const trainingLog = await updateTrainingLog(req.body.id, { hours: req.body.hours }); + if (!trainingLog) { + return res.status(500).json({ message: "Training log not found" }); + } + + res.status(200).json({ + trainingLogData: trainingLog, + message: "Training log updated successfully" + }); + } catch (error) { + res.status(500).json({ + message: "Error updating training log" + }); + } } } From 7de1c71a2d5b6632e7080f0b91edfceca78f2770 Mon Sep 17 00:00:00 2001 From: aandrx Date: Mon, 30 Mar 2026 19:12:32 -0400 Subject: [PATCH 09/33] updated frontend empty file structure --- src/components/{.gitkeep => AnimalCard.tsx} | 0 src/components/Sidebar.tsx | 0 src/components/Titlebar.tsx | 0 src/components/TrainingLogCard.tsx | 0 src/components/UserCard.tsx | 0 src/pages/api/user.ts | 4 ++-- src/pages/dashboard/all-animals.tsx | 0 src/pages/dashboard/all-training-logs.tsx | 0 src/pages/dashboard/all-users.tsx | 0 src/pages/dashboard/animals/[id]/edit.tsx | 0 src/pages/dashboard/animals/create.tsx | 0 src/pages/dashboard/animals/index.tsx | 0 src/pages/dashboard/index.tsx | 0 src/pages/dashboard/training-logs/[id]/edit.tsx | 0 src/pages/dashboard/training-logs/create.tsx | 0 src/pages/dashboard/training-logs/index.tsx | 0 src/pages/login.tsx | 0 src/pages/signup.tsx | 0 18 files changed, 2 insertions(+), 2 deletions(-) rename src/components/{.gitkeep => AnimalCard.tsx} (100%) create mode 100644 src/components/Sidebar.tsx create mode 100644 src/components/Titlebar.tsx create mode 100644 src/components/TrainingLogCard.tsx create mode 100644 src/components/UserCard.tsx create mode 100644 src/pages/dashboard/all-animals.tsx create mode 100644 src/pages/dashboard/all-training-logs.tsx create mode 100644 src/pages/dashboard/all-users.tsx create mode 100644 src/pages/dashboard/animals/[id]/edit.tsx create mode 100644 src/pages/dashboard/animals/create.tsx create mode 100644 src/pages/dashboard/animals/index.tsx create mode 100644 src/pages/dashboard/index.tsx create mode 100644 src/pages/dashboard/training-logs/[id]/edit.tsx create mode 100644 src/pages/dashboard/training-logs/create.tsx create mode 100644 src/pages/dashboard/training-logs/index.tsx create mode 100644 src/pages/login.tsx create mode 100644 src/pages/signup.tsx diff --git a/src/components/.gitkeep b/src/components/AnimalCard.tsx similarity index 100% rename from src/components/.gitkeep rename to src/components/AnimalCard.tsx diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/Titlebar.tsx b/src/components/Titlebar.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/TrainingLogCard.tsx b/src/components/TrainingLogCard.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/UserCard.tsx b/src/components/UserCard.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/api/user.ts b/src/pages/api/user.ts index 1c4988c..9b6fe09 100644 --- a/src/pages/api/user.ts +++ b/src/pages/api/user.ts @@ -1,6 +1,6 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { UserData } from "@server/mongodb/types/types"; -import { createUser, getUser, getUsers, deleteUser } from "@server/mongodb/actions/user"; +import { createUser, getUser, getAllUsers, deleteUser } from "@server/mongodb/actions/user"; import connectDb from "@server/mongodb/connectDb"; import argon2 from "argon2"; @@ -43,7 +43,7 @@ export default async function handler( } else if ( req.method === "GET" ) { try { connectDb(); - const users = await getUsers(); + const users = await getAllUsers(); res.status(200).json({ userData: users, diff --git a/src/pages/dashboard/all-animals.tsx b/src/pages/dashboard/all-animals.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/dashboard/all-training-logs.tsx b/src/pages/dashboard/all-training-logs.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/dashboard/all-users.tsx b/src/pages/dashboard/all-users.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/dashboard/animals/[id]/edit.tsx b/src/pages/dashboard/animals/[id]/edit.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/dashboard/animals/create.tsx b/src/pages/dashboard/animals/create.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/dashboard/animals/index.tsx b/src/pages/dashboard/animals/index.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/dashboard/index.tsx b/src/pages/dashboard/index.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/dashboard/training-logs/[id]/edit.tsx b/src/pages/dashboard/training-logs/[id]/edit.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/dashboard/training-logs/create.tsx b/src/pages/dashboard/training-logs/create.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/dashboard/training-logs/index.tsx b/src/pages/dashboard/training-logs/index.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/login.tsx b/src/pages/login.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/signup.tsx b/src/pages/signup.tsx new file mode 100644 index 0000000..e69de29 From 4ca033d875071b1af2e3d7fc7226c35e5bf25e05 Mon Sep 17 00:00:00 2001 From: aandrx Date: Tue, 31 Mar 2026 16:17:27 -0400 Subject: [PATCH 10/33] feat: titlebar update +main page routing --- src/components/Titlebar.tsx | 24 ++++++++++++ src/pages/{index.js => index.tsx} | 3 +- src/pages/page.tsx | 65 ------------------------------- tsconfig.json | 3 +- 4 files changed, 28 insertions(+), 67 deletions(-) rename src/pages/{index.js => index.tsx} (60%) delete mode 100644 src/pages/page.tsx diff --git a/src/components/Titlebar.tsx b/src/components/Titlebar.tsx index e69de29..b0078d5 100644 --- a/src/components/Titlebar.tsx +++ b/src/components/Titlebar.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import appLogo from "@images/appLogo.png"; + +const TitleBar = () => { + return ( +
+ + +
+ +
+
+ ); +} + +export default TitleBar; diff --git a/src/pages/index.js b/src/pages/index.tsx similarity index 60% rename from src/pages/index.js rename to src/pages/index.tsx index 810124a..189825b 100644 --- a/src/pages/index.js +++ b/src/pages/index.tsx @@ -1,8 +1,9 @@ -import React from 'react'; +import TitleBar from '@/components/Titlebar'; export default function Home() { return (
+

Hello World

); diff --git a/src/pages/page.tsx b/src/pages/page.tsx deleted file mode 100644 index 295f8fd..0000000 --- a/src/pages/page.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import Image from "next/image"; - -export default function Home() { - return ( -
-
- Next.js logo -
-

- To get started, edit the page.tsx file. -

-

- Looking for a starting point or more instructions? Head over to{" "} - - Templates - {" "} - or the{" "} - - Learning - {" "} - center. -

-
- -
-
- ); -} diff --git a/tsconfig.json b/tsconfig.json index 92b857a..a31603d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,7 +20,8 @@ ], "paths": { "@/*": ["./src/*"], - "@server/*": ["./server/*"] + "@server/*": ["./server/*"], + "@images/*": ["./public/images/*"] } }, "include": [ From 2327400e45c2893598fcf39769a8fab60c1583da Mon Sep 17 00:00:00 2001 From: aandrx Date: Wed, 1 Apr 2026 03:10:38 -0400 Subject: [PATCH 11/33] fixing tailwind --- package-lock.json | 22 ++++++++++++++++++---- package.json | 2 +- src/components/Titlebar.tsx | 32 +++++++++++++++++++------------- src/pages/_app.tsx | 24 ++++++++++++++++++++++++ src/pages/index.tsx | 2 +- src/pages/layout.tsx | 34 ---------------------------------- src/styles/globals.css | 10 +++++++--- tailwind.config.js | 14 ++++++++++++++ 8 files changed, 84 insertions(+), 56 deletions(-) create mode 100644 src/pages/_app.tsx delete mode 100644 src/pages/layout.tsx create mode 100644 tailwind.config.js diff --git a/package-lock.json b/package-lock.json index 1b1851f..6b6508b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", - "tailwindcss": "^4", + "tailwindcss": "^4.2.2", "typescript": "^5" } }, @@ -708,6 +708,13 @@ "tailwindcss": "4.1.16" } }, + "node_modules/@tailwindcss/node/node_modules/tailwindcss": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.16.tgz", + "integrity": "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==", + "dev": true, + "license": "MIT" + }, "node_modules/@tailwindcss/oxide": { "version": "4.1.16", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.16.tgz", @@ -963,6 +970,13 @@ "tailwindcss": "4.1.16" } }, + "node_modules/@tailwindcss/postcss/node_modules/tailwindcss": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.16.tgz", + "integrity": "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/jsonwebtoken": { "version": "9.0.10", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", @@ -1875,9 +1889,9 @@ } }, "node_modules/tailwindcss": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.16.tgz", - "integrity": "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", + "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index a9a3de5..6bbd7b8 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", - "tailwindcss": "^4", + "tailwindcss": "^4.2.2", "typescript": "^5" } } diff --git a/src/components/Titlebar.tsx b/src/components/Titlebar.tsx index b0078d5..70a2432 100644 --- a/src/components/Titlebar.tsx +++ b/src/components/Titlebar.tsx @@ -3,19 +3,25 @@ import appLogo from "@images/appLogo.png"; const TitleBar = () => { return ( -
- - -
- +
+
+ {/* left: logo and title */} +
+ +

Progress

+
+ + {/* middle: search bar */} +
+ +
); diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx new file mode 100644 index 0000000..f58f2e0 --- /dev/null +++ b/src/pages/_app.tsx @@ -0,0 +1,24 @@ +import '@/styles/globals.css' + +import type { AppProps } from 'next/app' +import Head from 'next/head' + +export default function App({ Component, pageProps }: AppProps) { + return ( + <> + + + + + + + + + ) +} diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 189825b..61501c1 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -4,7 +4,7 @@ export default function Home() { return (
-

Hello World

+ {/*

Hello World

*/}
); } diff --git a/src/pages/layout.tsx b/src/pages/layout.tsx deleted file mode 100644 index f7fa87e..0000000 --- a/src/pages/layout.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; -import "./globals.css"; - -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); - -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); - -export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", -}; - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - - {children} - - - ); -} diff --git a/src/styles/globals.css b/src/styles/globals.css index a2dc41e..61a5830 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -1,3 +1,7 @@ +/* font-oswald tailwind override */ +.font-oswald { + font-family: 'Oswald', sans-serif !important; +} @import "tailwindcss"; :root { @@ -12,15 +16,15 @@ --font-mono: var(--font-geist-mono); } -@media (prefers-color-scheme: dark) { +/* @media (prefers-color-scheme: dark) { :root { --background: #0a0a0a; --foreground: #ededed; } -} +} */ body { background: var(--background); color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; + font-family: 'Heebo', 'sans-serif'; } diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..4042193 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,14 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./src/**/*.{js,ts,jsx,tsx}", + ], + theme: { + fontFamily: { + sans: ['Heebo', 'sans-serif'], + oswald: ['Oswald', 'sans-serif'] + }, + extend: {}, + }, + plugins: [], +} From aaada2e0d0e835cf21611c499a6a3bb7f179b015 Mon Sep 17 00:00:00 2001 From: jtoolsidas Date: Wed, 1 Apr 2026 16:14:46 -0400 Subject: [PATCH 12/33] first iteration of log in page --- src/pages/index.tsx | 92 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 3 deletions(-) diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 61501c1..69ee53f 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,10 +1,96 @@ +import { useState } from 'react'; import TitleBar from '@/components/Titlebar'; export default function Home() { + const [formData, setFormData] = useState({ + fullName: '', + email: '', + password: '' + }); + const [status, setStatus] = useState(''); + + const handleChange = (e: React.ChangeEvent) => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setStatus('Loading...'); + + try { + const response = await fetch('/api/users', { // Ensure this matches your file path + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(formData), + }); + + const data = await response.json(); + + if (response.ok) { + setStatus(`Success: ${data.message}`); + setFormData({ fullName: '', email: '', password: '' }); // Reset form + } else { + setStatus(`Error: ${data.message}`); + } + } catch (error) { + setStatus('Failed to connect to the server.'); + } + }; + return ( -
+
- {/*

Hello World

*/} + +
+

Create Account

+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ + {status &&

{status}

} +
); -} +} \ No newline at end of file From 0d5d812a5a8d478d194b222fa570490670828b84 Mon Sep 17 00:00:00 2001 From: jtoolsidas Date: Wed, 1 Apr 2026 16:22:18 -0400 Subject: [PATCH 13/33] log in page complete --- src/pages/index.tsx | 123 +++++++++++++++++++++++++------------------- 1 file changed, 71 insertions(+), 52 deletions(-) diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 69ee53f..db65c19 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -7,7 +7,7 @@ export default function Home() { email: '', password: '' }); - const [status, setStatus] = useState(''); + const [status, setStatus] = useState({ message: '', isError: false }); const handleChange = (e: React.ChangeEvent) => { setFormData({ ...formData, [e.target.name]: e.target.value }); @@ -15,10 +15,10 @@ export default function Home() { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - setStatus('Loading...'); + setStatus({ message: 'Loading...', isError: false }); try { - const response = await fetch('/api/users', { // Ensure this matches your file path + const response = await fetch('/api/user', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData), @@ -27,69 +27,88 @@ export default function Home() { const data = await response.json(); if (response.ok) { - setStatus(`Success: ${data.message}`); - setFormData({ fullName: '', email: '', password: '' }); // Reset form + setStatus({ message: data.message, isError: false }); + setFormData({ fullName: '', email: '', password: '' }); } else { - setStatus(`Error: ${data.message}`); + setStatus({ message: data.message, isError: true }); } } catch (error) { - setStatus('Failed to connect to the server.'); + setStatus({ message: 'Failed to connect to the server.', isError: true }); } }; return ( -
+
-
-

Create Account

-
+
+
+

+ Create Account +

-
- - -
+ +
+ + +
-
- - -
+
+ + +
-
- - -
+
+ + +
- - + + - {status &&

{status}

} + {status.message && ( +
+ {status.message} +
+ )} +
); From 46f22aaf085c2c2582d6072989ec15f458c3759f Mon Sep 17 00:00:00 2001 From: aandrx Date: Thu, 2 Apr 2026 14:19:12 -0400 Subject: [PATCH 14/33] login pagefunctionality +titlebar --- src/components/Titlebar.tsx | 38 +++-- src/pages/dashboard/index.tsx | 15 ++ src/pages/index.tsx | 266 +++++++++++++++++++++++++--------- 3 files changed, 233 insertions(+), 86 deletions(-) diff --git a/src/components/Titlebar.tsx b/src/components/Titlebar.tsx index 70a2432..e7d500f 100644 --- a/src/components/Titlebar.tsx +++ b/src/components/Titlebar.tsx @@ -1,30 +1,36 @@ import React from "react"; import appLogo from "@images/appLogo.png"; +import searchLogo from "@images/searchLogo.png"; const TitleBar = () => { return ( -
-
+
+
{/* left: logo and title */} -
- -

Progress

+
+ Logo + Progress
- + {/* middle: search bar */} -
- +
+
+ Search + +
); -} +}; export default TitleBar; diff --git a/src/pages/dashboard/index.tsx b/src/pages/dashboard/index.tsx index e69de29..935e657 100644 --- a/src/pages/dashboard/index.tsx +++ b/src/pages/dashboard/index.tsx @@ -0,0 +1,15 @@ +import TitleBar from '@/components/Titlebar'; + +export default function Dashboard() { + return ( +
+ +
+
+

Dashboard

+

Coming soon.

+
+
+
+ ); +} diff --git a/src/pages/index.tsx b/src/pages/index.tsx index db65c19..49764e3 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,115 +1,241 @@ import { useState } from 'react'; +import { useRouter } from 'next/router'; import TitleBar from '@/components/Titlebar'; +type View = 'signup' | 'signin'; + export default function Home() { - const [formData, setFormData] = useState({ + const router = useRouter(); + const [view, setView] = useState('signin'); + + const [signUpData, setSignUpData] = useState({ fullName: '', email: '', - password: '' + password: '', + confirmPassword: '', + admin: false, }); - const [status, setStatus] = useState({ message: '', isError: false }); + const [signInData, setSignInData] = useState({ email: '', password: '' }); + const [status, setStatus] = useState({ message: '', isError: false, suggestSignUp: false }); + + const switchView = (v: View) => { + setView(v); + setStatus({ message: '', isError: false, suggestSignUp: false }); + }; - const handleChange = (e: React.ChangeEvent) => { - setFormData({ ...formData, [e.target.name]: e.target.value }); + const handleSignUpChange = (e: React.ChangeEvent) => { + const { name, value, type, checked } = e.target; + setSignUpData({ ...signUpData, [name]: type === 'checkbox' ? checked : value }); }; - const handleSubmit = async (e: React.FormEvent) => { + const handleSignInChange = (e: React.ChangeEvent) => { + setSignInData({ ...signInData, [e.target.name]: e.target.value }); + }; + + const handleSignUp = async (e: React.FormEvent) => { e.preventDefault(); - setStatus({ message: 'Loading...', isError: false }); + if (signUpData.password !== signUpData.confirmPassword) { + setStatus({ message: 'Passwords do not match.', isError: true, suggestSignUp: false }); + return; + } + setStatus({ message: 'Loading...', isError: false, suggestSignUp: false }); try { const response = await fetch('/api/user', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(formData), + body: JSON.stringify({ + fullName: signUpData.fullName, + email: signUpData.email, + password: signUpData.password, + admin: signUpData.admin, + }), + }); + + const data = await response.json(); + + if (response.ok) { + setStatus({ message: 'Account created! You can now sign in.', isError: false, suggestSignUp: false }); + setSignUpData({ fullName: '', email: '', password: '', confirmPassword: '', admin: false }); + } else { + setStatus({ message: data.message || 'Failed to create account.', isError: true, suggestSignUp: false }); + } + } catch { + setStatus({ message: 'Failed to connect to the server.', isError: true, suggestSignUp: false }); + } + }; + + const handleSignIn = async (e: React.FormEvent) => { + e.preventDefault(); + setStatus({ message: 'Loading...', isError: false, suggestSignUp: false }); + + try { + const response = await fetch('/api/user/verify', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(signInData), }); const data = await response.json(); if (response.ok) { - setStatus({ message: data.message, isError: false }); - setFormData({ fullName: '', email: '', password: '' }); + setStatus({ message: 'Signed in! Redirecting...', isError: false, suggestSignUp: false }); + router.push('/dashboard'); } else { - setStatus({ message: data.message, isError: true }); + const notFound = data.message?.toLowerCase().includes('not found'); + setStatus({ + message: notFound + ? 'No account found with that email.' + : data.message?.toLowerCase().includes('password') + ? 'Incorrect password. Please try again.' + : data.message || 'Sign in failed.', + isError: true, + suggestSignUp: notFound, + }); } - } catch (error) { - setStatus({ message: 'Failed to connect to the server.', isError: true }); + } catch { + setStatus({ message: 'Failed to connect to the server.', isError: true, suggestSignUp: false }); } }; + const inputClass = + 'w-full bg-transparent border-0 border-b border-red-500 py-2 text-sm text-gray-800 placeholder-gray-400 focus:outline-none focus:border-red-700 transition-colors'; + return ( -
+
- -
-
-

- Create Account -

- -
-
- +
+
+ + {/* sign in */} + {view === 'signin' && ( + <> +

Login

+ + + + + {status.message && status.message !== 'Loading...' && ( +

+ {status.message} +

+ )} + {status.message === 'Loading...' && ( +

Loading...

+ )} + + + +

+ Don't have an account?{' '} + +

+ + )} + + {/* sign up */} + {view === 'signup' && ( + <> +

Create Account

+
-
- -
- -
- -
- + -
- - - - - {status.message && ( -
- {status.message} -
- )} -
-
+ + + + {status.message && status.message !== 'Loading...' && ( +

+ {status.message} +

+ )} + {status.message === 'Loading...' && ( +

Loading...

+ )} + + + +

+ Already have an account?{' '} + +

+ + )} + +
+
); -} \ No newline at end of file +} From 87a9ade9b74cf4cec26373e60db1053700e56d28 Mon Sep 17 00:00:00 2001 From: jtoolsidas Date: Thu, 2 Apr 2026 18:01:34 -0400 Subject: [PATCH 15/33] test of sidebar in log in screen --- package-lock.json | 10 ++ package.json | 1 + src/components/Sidebar.tsx | 85 ++++++++++++ src/pages/index.tsx | 263 ++++++++++++++++++++++++++++++++++++- 4 files changed, 355 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6b6508b..2c9f00d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "argon2": "^0.44.0", + "lucide-react": "^1.7.0", "mongoose": "^9.3.1", "next": "16.0.1", "react": "19.2.0", @@ -1446,6 +1447,15 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lucide-react": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.7.0.tgz", + "integrity": "sha512-yI7BeItCLZJTXikmK4KNUGCKoGzSvbKlfCvw44bU4fXAL6v3gYS4uHD1jzsLkfwODYwI6Drw5Tu9Z5ulDe0TSg==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", diff --git a/package.json b/package.json index 6bbd7b8..3936026 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "argon2": "^0.44.0", + "lucide-react": "^1.7.0", "mongoose": "^9.3.1", "next": "16.0.1", "react": "19.2.0", diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index e69de29..611dd64 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -0,0 +1,85 @@ +import React from 'react'; +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import { + Pencil, + Bone, + Folder, + Rabbit, + Users, + LogOut +} from 'lucide-react'; + +interface SidebarProps { + user: { + fullName: string; + admin: boolean; + }; +} + +export default function Sidebar({ user }: SidebarProps) { + const router = useRouter(); + + const handleLogout = () => { + //uuhhh figure this out later + router.push('/'); + }; + + const NavItem = ({ href, icon: Icon, label, active = false }: any) => ( + + + {label} + + ); + + return ( + + ); +} \ No newline at end of file diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 49764e3..8d03817 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,4 +1,5 @@ -import { useState } from 'react'; +//sidebar not on this page but here to test +/*import { useState } from 'react'; import { useRouter } from 'next/router'; import TitleBar from '@/components/Titlebar'; @@ -107,7 +108,7 @@ export default function Home() {
- {/* sign in */} + {/* sign in */ /*} {view === 'signin' && ( <>

Login

@@ -156,7 +157,7 @@ export default function Home() { )} - {/* sign up */} + {/* sign up */ /*} {view === 'signup' && ( <>

Create Account

@@ -238,4 +239,258 @@ export default function Home() {
); -} +} */ + + import { useState } from 'react'; +import { useRouter } from 'next/router'; +import TitleBar from '@/components/Titlebar'; +import Sidebar from '@/components/Sidebar'; + +type View = 'signup' | 'signin' | 'authenticated'; + +export default function Home() { + const router = useRouter(); + const [view, setView] = useState('signin'); + + //fix to be dynamic + const [user, setUser] = useState({ + fullName: 'Jaahnvi Toolsidas', + admin: true, + }); + + const [signUpData, setSignUpData] = useState({ + fullName: '', + email: '', + password: '', + confirmPassword: '', + admin: false, + }); + const [signInData, setSignInData] = useState({ email: '', password: '' }); + const [status, setStatus] = useState({ message: '', isError: false, suggestSignUp: false }); + + const switchView = (v: View) => { + setView(v); + setStatus({ message: '', isError: false, suggestSignUp: false }); + }; + + const handleSignUpChange = (e: React.ChangeEvent) => { + const { name, value, type, checked } = e.target; + setSignUpData({ ...signUpData, [name]: type === 'checkbox' ? checked : value }); + }; + + const handleSignInChange = (e: React.ChangeEvent) => { + setSignInData({ ...signInData, [e.target.name]: e.target.value }); + }; + + const handleSignUp = async (e: React.FormEvent) => { + e.preventDefault(); + if (signUpData.password !== signUpData.confirmPassword) { + setStatus({ message: 'Passwords do not match.', isError: true, suggestSignUp: false }); + return; + } + setStatus({ message: 'Loading...', isError: false, suggestSignUp: false }); + + try { + const response = await fetch('/api/user', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + fullName: signUpData.fullName, + email: signUpData.email, + password: signUpData.password, + admin: signUpData.admin, + }), + }); + + const data = await response.json(); + + if (response.ok) { + setStatus({ message: 'Account created! You can now sign in.', isError: false, suggestSignUp: false }); + setSignUpData({ fullName: '', email: '', password: '', confirmPassword: '', admin: false }); + } else { + setStatus({ message: data.message || 'Failed to create account.', isError: true, suggestSignUp: false }); + } + } catch { + setStatus({ message: 'Failed to connect to the server.', isError: true, suggestSignUp: false }); + } + }; + + const handleSignIn = async (e: React.FormEvent) => { + e.preventDefault(); + setStatus({ message: 'Loading...', isError: false, suggestSignUp: false }); + + try { + const response = await fetch('/api/user/verify', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(signInData), + }); + + const data = await response.json(); + + if (response.ok) { + setStatus({ message: 'Signed in! Redirecting...', isError: false, suggestSignUp: false }); + setUser({ fullName: data.userData.fullName, admin: data.userData.admin }); + router.push('/dashboard'); + } else { + const notFound = data.message?.toLowerCase().includes('not found'); + setStatus({ + message: notFound + ? 'No account found with that email.' + : data.message?.toLowerCase().includes('password') + ? 'Incorrect password. Please try again.' + : data.message || 'Sign in failed.', + isError: true, + suggestSignUp: notFound, + }); + } + } catch { + setStatus({ message: 'Failed to connect to the server.', isError: true, suggestSignUp: false }); + } + }; + + const inputClass = + 'w-full bg-transparent border-0 border-b border-red-500 py-2 text-sm text-gray-800 placeholder-gray-400 focus:outline-none focus:border-red-700 transition-colors'; + + return ( +
+ {/* Sidebar - typically hidden on login pages but included here as requested */} + + +
+ + +
+
+ {/* sign in */} + {view === 'signin' && ( + <> +

Login

+
+ + + + {status.message && status.message !== 'Loading...' && ( +

+ {status.message} +

+ )} + {status.message === 'Loading...' && ( +

Loading...

+ )} + + +
+

+ Don't have an account?{' '} + +

+ + )} + + {/* sign up */} + {view === 'signup' && ( + <> +

Create Account

+
+ + + + + + + + {status.message && status.message !== 'Loading...' && ( +

+ {status.message} +

+ )} + {status.message === 'Loading...' && ( +

Loading...

+ )} + + +
+

+ Already have an account?{' '} + +

+ + )} +
+
+
+
+ ); +} \ No newline at end of file From c21932d2c292f2b6a77d216ee1271293aaed71c1 Mon Sep 17 00:00:00 2001 From: jtoolsidas Date: Thu, 2 Apr 2026 19:15:25 -0400 Subject: [PATCH 16/33] sidebar on dashboard --- package-lock.json | 122 +++++++++++++++++++++++++++++++++- package.json | 1 + src/pages/dashboard/index.tsx | 26 ++++++-- src/pages/index.tsx | 25 ++++--- 4 files changed, 155 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2c9f00d..c020ed4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "argon2": "^0.44.0", + "jsonwebtoken": "^9.0.3", "lucide-react": "^1.7.0", "mongoose": "^9.3.1", "next": "16.0.1", @@ -1066,6 +1067,12 @@ "node": ">=20.19.0" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/caniuse-lite": { "version": "1.0.30001751", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz", @@ -1140,6 +1147,15 @@ "node": ">=8" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/enhanced-resolve": { "version": "5.18.3", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", @@ -1177,6 +1193,49 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kareem": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/kareem/-/kareem-3.2.0.tgz", @@ -1447,6 +1506,48 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/lucide-react": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.7.0.tgz", @@ -1768,6 +1869,26 @@ "react": "^19.2.0" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -1779,7 +1900,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", - "optional": true, "bin": { "semver": "bin/semver.js" }, diff --git a/package.json b/package.json index 3936026..c74bc8e 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "argon2": "^0.44.0", + "jsonwebtoken": "^9.0.3", "lucide-react": "^1.7.0", "mongoose": "^9.3.1", "next": "16.0.1", diff --git a/src/pages/dashboard/index.tsx b/src/pages/dashboard/index.tsx index 935e657..af70a8e 100644 --- a/src/pages/dashboard/index.tsx +++ b/src/pages/dashboard/index.tsx @@ -1,15 +1,27 @@ +import { useState } from 'react'; import TitleBar from '@/components/Titlebar'; +import Sidebar from '@/components/Sidebar'; export default function Dashboard() { + //fix this! + const [user] = useState({ + fullName: 'Jaahnvi Toolsidas', + admin: true, + }); + return ( -
+
-
-
-

Dashboard

-

Coming soon.

-
+
+ + +
+
+

Dashboard

+

Coming soon.

+
+
); -} +} \ No newline at end of file diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 8d03817..b3f66c1 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,5 +1,4 @@ -//sidebar not on this page but here to test -/*import { useState } from 'react'; +import { useState } from 'react'; import { useRouter } from 'next/router'; import TitleBar from '@/components/Titlebar'; @@ -108,7 +107,7 @@ export default function Home() {
- {/* sign in */ /*} + {/* sign in */} {view === 'signin' && ( <>

Login

@@ -157,7 +156,7 @@ export default function Home() { )} - {/* sign up */ /*} + {/* sign up */} {view === 'signup' && ( <>

Create Account

@@ -239,9 +238,9 @@ export default function Home() {
); -} */ +} - import { useState } from 'react'; +/**import { useState } from 'react'; import { useRouter } from 'next/router'; import TitleBar from '@/components/Titlebar'; import Sidebar from '@/components/Sidebar'; @@ -317,6 +316,7 @@ export default function Home() { const handleSignIn = async (e: React.FormEvent) => { e.preventDefault(); + console.log("Called handle sign in") setStatus({ message: 'Loading...', isError: false, suggestSignUp: false }); try { @@ -326,7 +326,10 @@ export default function Home() { body: JSON.stringify(signInData), }); + console.log("Finished api call") + const data = await response.json(); + console.log(JSON.stringify(data)) if (response.ok) { setStatus({ message: 'Signed in! Redirecting...', isError: false, suggestSignUp: false }); @@ -344,7 +347,8 @@ export default function Home() { suggestSignUp: notFound, }); } - } catch { + } catch (error) { + console.error('Error during sign-in:', error); setStatus({ message: 'Failed to connect to the server.', isError: true, suggestSignUp: false }); } }; @@ -354,7 +358,6 @@ export default function Home() { return (
- {/* Sidebar - typically hidden on login pages but included here as requested */}
@@ -362,7 +365,7 @@ export default function Home() {
- {/* sign in */} + {/* sign in */ /*} {view === 'signin' && ( <>

Login

@@ -411,7 +414,7 @@ export default function Home() { )} - {/* sign up */} + {/* sign up */ /*} {view === 'signup' && ( <>

Create Account

@@ -493,4 +496,4 @@ export default function Home() {
); -} \ No newline at end of file +} */ \ No newline at end of file From d74c85ce31568df1d8c1a96496a6d3005f3e6d38 Mon Sep 17 00:00:00 2001 From: jtoolsidas Date: Fri, 3 Apr 2026 14:18:40 -0400 Subject: [PATCH 17/33] training log card component, dashboard, and all training page done --- src/components/Sidebar.tsx | 16 ++++-- src/components/TrainingLogCard.tsx | 62 +++++++++++++++++++++ src/pages/dashboard/all-training-logs.tsx | 65 +++++++++++++++++++++++ src/pages/dashboard/index.tsx | 58 +++++++++++++++++--- 4 files changed, 192 insertions(+), 9 deletions(-) diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 611dd64..a28c803 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -37,9 +37,14 @@ export default function Sidebar({ user }: SidebarProps) { ); return ( -