From 6d110c45cfece590ba1284ad6e6596c7b4d51216 Mon Sep 17 00:00:00 2001 From: Kushagra Goel Date: Tue, 17 Mar 2026 19:25:31 -0400 Subject: [PATCH 01/14] initial file structure --- .gitignore | 1 + server/mongodb/index.js | 0 src/app/favicon.ico | Bin 25931 -> 0 bytes src/app/globals.css | 26 ---------------- src/app/layout.tsx | 34 --------------------- src/app/page.tsx | 65 ---------------------------------------- src/pages/index.js | 0 7 files changed, 1 insertion(+), 125 deletions(-) create mode 100644 server/mongodb/index.js delete mode 100644 src/app/favicon.ico delete mode 100644 src/app/globals.css delete mode 100644 src/app/layout.tsx delete mode 100644 src/app/page.tsx create mode 100644 src/pages/index.js diff --git a/.gitignore b/.gitignore index 5ef6a52..64e4035 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ yarn-error.log* # env files (can opt-in for committing if needed) .env* +.env # vercel .vercel diff --git a/server/mongodb/index.js b/server/mongodb/index.js new file mode 100644 index 0000000..e69de29 diff --git a/src/app/favicon.ico b/src/app/favicon.ico deleted file mode 100644 index 718d6fea4835ec2d246af9800eddb7ffb276240c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25931 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 LrJugUA?W`A8`#=m diff --git a/src/app/globals.css b/src/app/globals.css deleted file mode 100644 index a2dc41e..0000000 --- a/src/app/globals.css +++ /dev/null @@ -1,26 +0,0 @@ -@import "tailwindcss"; - -:root { - --background: #ffffff; - --foreground: #171717; -} - -@theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); -} - -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } -} - -body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; -} diff --git a/src/app/layout.tsx b/src/app/layout.tsx deleted file mode 100644 index f7fa87e..0000000 --- a/src/app/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/app/page.tsx b/src/app/page.tsx deleted file mode 100644 index 295f8fd..0000000 --- a/src/app/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/src/pages/index.js b/src/pages/index.js new file mode 100644 index 0000000..e69de29 From 521d0d3a94bbc82fe7ca12ac293250d06710c21d Mon Sep 17 00:00:00 2001 From: Kushagra Goel Date: Fri, 27 Mar 2026 16:25:09 -0400 Subject: [PATCH 02/14] created the post,get, and verify pathways for users --- package-lock.json | 315 ++++++++++++++++++++++ package.json | 12 +- server/mongodb/actions/user.ts | 27 ++ server/mongodb/connectDb.ts | 9 + server/mongodb/models/User.ts | 16 ++ src/pages/api/admin/users/route.ts | 37 +++ src/pages/api/admin/users/verify/route.ts | 58 ++++ src/pages/api/user/route.ts | 43 +++ 8 files changed, 512 insertions(+), 5 deletions(-) create mode 100644 server/mongodb/actions/user.ts create mode 100644 server/mongodb/connectDb.ts create mode 100644 server/mongodb/models/User.ts create mode 100644 src/pages/api/admin/users/route.ts create mode 100644 src/pages/api/admin/users/verify/route.ts create mode 100644 src/pages/api/user/route.ts diff --git a/package-lock.json b/package-lock.json index f15a1ff..8d2a6df 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,11 @@ "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==" + }, "node_modules/@img/colour": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", @@ -522,6 +529,14 @@ "@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==", + "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 +671,14 @@ "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==", + "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 +989,42 @@ "@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==" + }, + "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==", + "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, + "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==", + "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 +1051,35 @@ "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==", + "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==", + "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 +1118,11 @@ "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==" + }, "node_modules/jiti": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", @@ -1040,6 +1133,14 @@ "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==", + "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 +1412,109 @@ "@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==" + }, + "node_modules/mongodb": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.1.0.tgz", + "integrity": "sha512-kMfnKunbolQYwCIyrkxNJFB4Ypy91pYqua5NargS/f8ODNSJxT03ZU3n1JqL4mCzbSih8tvmMEMLpKTT7x5gCg==", + "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==", + "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==", + "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==", + "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==", + "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==" + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -1409,6 +1613,32 @@ "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==", + "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==", + "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==", + "engines": { + "node": ">=8" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1444,6 +1674,14 @@ "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==", + "engines": { + "node": ">=6" + } + }, "node_modules/react": { "version": "19.2.0", "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", @@ -1527,6 +1765,30 @@ "@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==", + "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==", + "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==" + }, "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 +1798,14 @@ "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==", + "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 +1850,17 @@ "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==", + "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 +1887,40 @@ "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==", + "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==", + "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==", + "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/user.ts b/server/mongodb/actions/user.ts new file mode 100644 index 0000000..c49be66 --- /dev/null +++ b/server/mongodb/actions/user.ts @@ -0,0 +1,27 @@ +import { UserData } from "@/types/types"; +import user from "@/server/mongodb/models/User"; + +export async function createUser(userData : UserData) { + const newUser = new user(UserData); + await newUser.save(); + return newUser; +} + +export async function getAllUsers() { + const retrievedUser = await user.find({}).select("-password"); + return retrievedUser; +} + +export async function updateUser(userId: string, newData : UserData) { + const updatedUser = await user.findByIdAndUpdate(userId, newData); + return updatedUser; +} + +export async function getUserByUserName(userName: string) { + return await user.findOne({userName}); +} + + +export async function deleteUser(userId: string) { + await user.findByIdAndDelete(userId); +} \ No newline at end of file diff --git a/server/mongodb/connectDb.ts b/server/mongodb/connectDb.ts new file mode 100644 index 0000000..955f436 --- /dev/null +++ b/server/mongodb/connectDb.ts @@ -0,0 +1,9 @@ +import mongoose from "mongoose"; + +export default async function connectDb() { + try { + await mongoose.connect(process.env.DB_URL!); + } catch (e) { + console.log("Unable to connect", e); + } +} \ No newline at end of file diff --git a/server/mongodb/models/User.ts b/server/mongodb/models/User.ts new file mode 100644 index 0000000..1b859a0 --- /dev/null +++ b/server/mongodb/models/User.ts @@ -0,0 +1,16 @@ +import mongoose from "mongoose"; + +const userSchema = new mongoose.Schema({ + + userName: { + type: String, + required: true + }, + + password: { + type: String, + required: true + } +}); + +export default mongoose.model("User", userSchema); \ No newline at end of file diff --git a/src/pages/api/admin/users/route.ts b/src/pages/api/admin/users/route.ts new file mode 100644 index 0000000..ed20345 --- /dev/null +++ b/src/pages/api/admin/users/route.ts @@ -0,0 +1,37 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { getAllUsers } from "@/db/actions/user.ts"; // Rename to match updated action +import connectDb from "server/mongodb/connectDb.ts"; + + +interface UserApiData { + users?: any[]; + message: string; +} + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + if (req.method === 'GET') { + try { + await connectDb(); + const users = await getAllUsers(); + + if (!users || users.length === 0) { + return res.status(404).json({ + message: "There are no users to get" + }); + } + + return res.status(200).json({ + users: users, + message: "Users retrieved successfully" + }); + + } catch (e) { + return res.status(500).json({ + message: "There was an error in retrieving the users" + }); + } + } +} \ No newline at end of file diff --git a/src/pages/api/admin/users/verify/route.ts b/src/pages/api/admin/users/verify/route.ts new file mode 100644 index 0000000..f6b8642 --- /dev/null +++ b/src/pages/api/admin/users/verify/route.ts @@ -0,0 +1,58 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { UserData } from "@/types/types"; +import { createUser, deleteUser, getUser, updateUser, getUserByUserName } from "@/db/actions/user"; +import connectDb from "@/db/connectDb"; +import * as argon2 from "argon2"; +import { connect } from "http2"; + + +interface UserApiData { + verified? : boolean; + userId? : string; + message : string; +} + +export default async function handler ( + req: NextApiRequest, + res: NextApiResponse, +) { + if (req.method === 'POST') { + try { + if (!req.body.userName || !req.body.password) { + res.status(500).json({ + message: "Must provide username and password" + }) + } + + await connectDb(); + const {userName, password} = req.body; + const existUser = await getUserByUserName(userName); + if (!existUser) { + return res.status(500).json({ + verified: false, + message: "User not found" + }); + } + + if (existUser) { + const passwordCheck = await argon2.verify(existUser.password, password); + if (passwordCheck) { + return res.status(200).json({ + message: "User succesfully verified", + userId: existUser._id, + verified: true, + }); + } else { + return res.status(500).json({ + verified : false, + message: "Incorrect password" + }); + } + } + } catch (e) { + res.status(500).json({ + message: "There was an error in verifying the user" + }); + } + } +} \ No newline at end of file diff --git a/src/pages/api/user/route.ts b/src/pages/api/user/route.ts new file mode 100644 index 0000000..4d04dd2 --- /dev/null +++ b/src/pages/api/user/route.ts @@ -0,0 +1,43 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { UserData } from "@/types/types"; +import { createUser, deleteUser, getUser, updateUser } from "@/db/actions/user"; +import connectDb from "@/db/connectDb"; +import * as argon2 from "argon2"; +import { connect } from "http2"; + + +interface UserApiData { + userData? : UserData; + message : string; +} + +export default async function handler ( + req: NextApiRequest, + res: NextApiResponse, +) { + if (req.method === 'POST') { + try { + if (!req.body.userName || !req.body.password) { + res.status(500).json({ + message: "New users must have a username and password" + }); + } + const argon2 = require('argon2'); + const hash = await argon2.hash(req.body.password); + const userData = { + userName : req.body.userName, + password : hash, + } as UserData + await connectDb(); + const user = await createUser(userData); + res.status(200).json({ + userData: userData, + message: "User creation succesful" + }); + } catch (e) { + res.status(500).json({ + message: "There was an error in adding user to the database" + }); + } + } +} \ No newline at end of file From 639ba1e5d5c6c7c37a74305a5be6959032e4f3a6 Mon Sep 17 00:00:00 2001 From: Kushagra Goel Date: Sun, 29 Mar 2026 21:36:43 -0400 Subject: [PATCH 03/14] fixed user api endpoints to match schema.md --- server/mongodb/actions/user.ts | 4 ++-- server/mongodb/models/User.ts | 11 +++++++++-- src/pages/api/admin/users/verify/route.ts | 20 ++++++++++---------- src/pages/api/user/route.ts | 6 ++++-- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/server/mongodb/actions/user.ts b/server/mongodb/actions/user.ts index c49be66..75c7df5 100644 --- a/server/mongodb/actions/user.ts +++ b/server/mongodb/actions/user.ts @@ -17,8 +17,8 @@ export async function updateUser(userId: string, newData : UserData) { return updatedUser; } -export async function getUserByUserName(userName: string) { - return await user.findOne({userName}); +export async function getUserByEmail(email: string) { + return await user.findOne({email}); } diff --git a/server/mongodb/models/User.ts b/server/mongodb/models/User.ts index 1b859a0..9a198a5 100644 --- a/server/mongodb/models/User.ts +++ b/server/mongodb/models/User.ts @@ -2,14 +2,21 @@ import mongoose from "mongoose"; const userSchema = new mongoose.Schema({ - userName: { + fullName: { type: String, required: true }, - password: { type: String, required: true + }, + email: { + type: String, + required: true + }, + admin: { + type: Boolean, + required: true } }); diff --git a/src/pages/api/admin/users/verify/route.ts b/src/pages/api/admin/users/verify/route.ts index f6b8642..48fa9fc 100644 --- a/src/pages/api/admin/users/verify/route.ts +++ b/src/pages/api/admin/users/verify/route.ts @@ -1,13 +1,13 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { UserData } from "@/types/types"; -import { createUser, deleteUser, getUser, updateUser, getUserByUserName } from "@/db/actions/user"; -import connectDb from "@/db/connectDb"; +import { createUser, deleteUser, getUser, updateUser, getUserByEmail } from "@/server/mongodb/actions/user"; +import connectDb from "@/server/connectDb"; import * as argon2 from "argon2"; import { connect } from "http2"; interface UserApiData { - verified? : boolean; + isAdmin? : boolean; userId? : string; message : string; } @@ -18,18 +18,19 @@ export default async function handler ( ) { if (req.method === 'POST') { try { - if (!req.body.userName || !req.body.password) { + if (!req.body.email || !req.body.password) { res.status(500).json({ - message: "Must provide username and password" + message: "Must provide email and/or password" }) } await connectDb(); - const {userName, password} = req.body; - const existUser = await getUserByUserName(userName); + const email = req.body.email; + const password = req.body.password; + const isAdmin = req.body.admin; + const existUser = await getUserByEmail(email); if (!existUser) { return res.status(500).json({ - verified: false, message: "User not found" }); } @@ -40,11 +41,10 @@ export default async function handler ( return res.status(200).json({ message: "User succesfully verified", userId: existUser._id, - verified: true, + isAdmin: isAdmin, }); } else { return res.status(500).json({ - verified : false, message: "Incorrect password" }); } diff --git a/src/pages/api/user/route.ts b/src/pages/api/user/route.ts index 4d04dd2..7d6ba36 100644 --- a/src/pages/api/user/route.ts +++ b/src/pages/api/user/route.ts @@ -17,7 +17,7 @@ export default async function handler ( ) { if (req.method === 'POST') { try { - if (!req.body.userName || !req.body.password) { + if (!req.body.fullName || !req.body.password || !req.body.email || !req.body.admin) { res.status(500).json({ message: "New users must have a username and password" }); @@ -25,8 +25,10 @@ export default async function handler ( const argon2 = require('argon2'); const hash = await argon2.hash(req.body.password); const userData = { - userName : req.body.userName, + fullName : req.body.fullName, password : hash, + email : req.body.email, + admin : req.body.admin, } as UserData await connectDb(); const user = await createUser(userData); From 3ae646e901fe07a37fb5ce4cd349aa0b18b1820d Mon Sep 17 00:00:00 2001 From: Kushagra Goel Date: Mon, 30 Mar 2026 02:15:46 -0400 Subject: [PATCH 04/14] added animal endpoints and fixed imports --- server/mongodb/actions/animal.ts | 27 ++++++++ server/mongodb/actions/user.ts | 9 ++- server/mongodb/models/Animal.ts | 12 ++++ src/pages/api/admin/animals/route.ts | 37 +++++++++++ src/pages/api/admin/users/route.ts | 4 +- src/pages/api/admin/users/verify/route.ts | 8 +-- src/pages/api/animal/route.ts | 77 +++++++++++++++++++++++ src/pages/api/user/route.ts | 6 +- src/types/types.ts | 15 +++++ 9 files changed, 184 insertions(+), 11 deletions(-) create mode 100644 server/mongodb/actions/animal.ts create mode 100644 server/mongodb/models/Animal.ts create mode 100644 src/pages/api/admin/animals/route.ts create mode 100644 src/pages/api/animal/route.ts create mode 100644 src/types/types.ts diff --git a/server/mongodb/actions/animal.ts b/server/mongodb/actions/animal.ts new file mode 100644 index 0000000..5b05d80 --- /dev/null +++ b/server/mongodb/actions/animal.ts @@ -0,0 +1,27 @@ +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 animal; +} + +export async function getAnimal(animalId: string) { + const retrievedAnimal = await animal.findById(animalId); + return retrievedAnimal; +} + +export async function getAllAnimals() { + const allAnimals = await animal.find({}); + return allAnimals; +} + +export async function updateAnimal(animalId: string, newData: AnimalData) { + const updatedAnimal = await animal.findByIdAndUpdate(animalId, newData); + return updatedAnimal; +} + +export async function deleteAnimal(animalId: string) { + await animal.findByIdAndDelete(animalId); +} \ No newline at end of file diff --git a/server/mongodb/actions/user.ts b/server/mongodb/actions/user.ts index 75c7df5..a55865a 100644 --- a/server/mongodb/actions/user.ts +++ b/server/mongodb/actions/user.ts @@ -1,8 +1,8 @@ import { UserData } from "@/types/types"; -import user from "@/server/mongodb/models/User"; +import user from "../models/User"; export async function createUser(userData : UserData) { - const newUser = new user(UserData); + const newUser = new user(userData); await newUser.save(); return newUser; } @@ -17,6 +17,11 @@ export async function updateUser(userId: string, newData : UserData) { return updatedUser; } +export async function getUser(userId: string){ + const retrievedUser = await user.findById(userId); + return retrievedUser; +} + export async function getUserByEmail(email: string) { return await user.findOne({email}); } diff --git a/server/mongodb/models/Animal.ts b/server/mongodb/models/Animal.ts new file mode 100644 index 0000000..1a78841 --- /dev/null +++ b/server/mongodb/models/Animal.ts @@ -0,0 +1,12 @@ +import mongoose from "mongoose" + +const animalSchema = new mongoose.Schema({ + + name: String, + breed: String, + owner: mongoose.Schema.Types.ObjectId, + hoursTrained: mongoose.Schema.Types.Number, + +}); + +export default mongoose.model("Animal", animalSchema); \ No newline at end of file diff --git a/src/pages/api/admin/animals/route.ts b/src/pages/api/admin/animals/route.ts new file mode 100644 index 0000000..ff39e34 --- /dev/null +++ b/src/pages/api/admin/animals/route.ts @@ -0,0 +1,37 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { getAllAnimals } from "../../../../../server/mongodb/actions/animal"; +import connectDb from "../../../../../server/mongodb/connectDb"; + +interface AnimalApiData { + animals?: any[]; + message: string; +}; + +export default async function handler( + req : NextApiRequest, + res: NextApiResponse +) { + if (req.method === 'GET') { + try{ + await connectDb(); + const animals = await getAllAnimals(); + + if (!animals || animals.length === 0) { + return res.status(500).json({ + message: "There are no animals in the database" + }); + } + + return res.status(200).json({ + animals: animals, + message: "Succesfully got all animals" + }) + + } catch (e) { + res.status(500).json({ + message: "There was an error in getting the animals" + }); + } + } + +} \ No newline at end of file diff --git a/src/pages/api/admin/users/route.ts b/src/pages/api/admin/users/route.ts index ed20345..78dc879 100644 --- a/src/pages/api/admin/users/route.ts +++ b/src/pages/api/admin/users/route.ts @@ -1,6 +1,6 @@ import type { NextApiRequest, NextApiResponse } from "next"; -import { getAllUsers } from "@/db/actions/user.ts"; // Rename to match updated action -import connectDb from "server/mongodb/connectDb.ts"; +import { getAllUsers } from "../../../../../server/mongodb/actions/user"; +import connectDb from "../../../../../server/mongodb/connectDb"; interface UserApiData { diff --git a/src/pages/api/admin/users/verify/route.ts b/src/pages/api/admin/users/verify/route.ts index 48fa9fc..413d2e8 100644 --- a/src/pages/api/admin/users/verify/route.ts +++ b/src/pages/api/admin/users/verify/route.ts @@ -1,7 +1,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; -import { UserData } from "@/types/types"; -import { createUser, deleteUser, getUser, updateUser, getUserByEmail } from "@/server/mongodb/actions/user"; -import connectDb from "@/server/connectDb"; +import { UserData } from "../../../../../types/types"; +import { createUser, deleteUser, updateUser, getUserByEmail } from "../../../../../../server/mongodb/actions/user"; +import connectDb from "../../../../../../server/mongodb/connectDb"; import * as argon2 from "argon2"; import { connect } from "http2"; @@ -40,7 +40,7 @@ export default async function handler ( if (passwordCheck) { return res.status(200).json({ message: "User succesfully verified", - userId: existUser._id, + userId: existUser._id.toString(), isAdmin: isAdmin, }); } else { diff --git a/src/pages/api/animal/route.ts b/src/pages/api/animal/route.ts new file mode 100644 index 0000000..72b15a8 --- /dev/null +++ b/src/pages/api/animal/route.ts @@ -0,0 +1,77 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { AnimalData } from "@/types/types"; +import { getUser } from "../../../../server/mongodb/actions/user"; +import { createAnimal, deleteAnimal, updateAnimal, getAnimal} from "../../../../server/mongodb/actions/animal"; +import connectDb from "../../../../server/mongodb/connectDb"; + +interface 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 || !req.body.hoursTrained) { + res.status(500).json({ + message: "All information must be given to create an animal" + }); + } + + const animalData = { + name: req.body.name, + breed: req.body.breed, + owner: req.body.owner, + hoursTrained: req.body.hoursTrained, + } as AnimalData + + await connectDb(); + const owner = await getUser(req.body.owner); + if (!owner) { + res.status(400).json({ + message: "Owner of animal does not exist in database" + }); + } + + const animal = await createAnimal(animalData); + res.status(200).json({ + animalData: animalData, + message: "Animal creation succseful" + }); + } catch (e) { + res.status(500).json({ + message: "There was an error in adding the animal to the database" + }); + } + } else if (req.method === "PATCH") { + try { + if (!req.body._id) { + res.status(400).json({ + message: "No animal id given" + }); + }; + const {_id, ...updateData} = req.body; + await connectDb(); + const updatedAnimal = await updateAnimal(_id, updateData); + if (!updatedAnimal) { + res.status(500).json({ + message: "Failed to update animal" + }); + } + + res.status(200).json({ + animalData: updatedAnimal as unknown as AnimalData, + message: "Succesfully update animal" + }); + + } catch (e) { + res.status(500).json({ + message: "Failed to update animal" + }); + } + } + +} \ No newline at end of file diff --git a/src/pages/api/user/route.ts b/src/pages/api/user/route.ts index 7d6ba36..9bbbfb9 100644 --- a/src/pages/api/user/route.ts +++ b/src/pages/api/user/route.ts @@ -1,7 +1,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; -import { UserData } from "@/types/types"; -import { createUser, deleteUser, getUser, updateUser } from "@/db/actions/user"; -import connectDb from "@/db/connectDb"; +import { UserData } from "../../../types/types"; +import { createUser, deleteUser, updateUser } from "../../../../server/mongodb/actions/user"; +import connectDb from "../../../../server/mongodb/connectDb"; import * as argon2 from "argon2"; import { connect } from "http2"; diff --git a/src/types/types.ts b/src/types/types.ts new file mode 100644 index 0000000..8760a44 --- /dev/null +++ b/src/types/types.ts @@ -0,0 +1,15 @@ +import { ObjectId } from "mongoose"; + +export interface UserData { + fullName: string; + email: string; + password: string; + admin: boolean; +} + +export interface AnimalData { + name: string; + breed: string; + owner: ObjectId; + hoursTrained: number; +} \ No newline at end of file From 54cdc8bb8e9e96b29cb394c2ad86537896c69117 Mon Sep 17 00:00:00 2001 From: Kushagra Goel Date: Tue, 31 Mar 2026 18:43:29 -0400 Subject: [PATCH 05/14] fixed minor logic errors in endpoints --- package-lock.json | 7 ++++--- package.json | 1 + server/mongodb/actions/animal.ts | 2 +- src/pages/api/user/route.ts | 4 +++- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8d2a6df..6a9ffe9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "argon2": "^0.44.0", + "mongodb": "^7.1.1", "mongoose": "^9.3.1", "next": "16.0.1", "react": "19.2.0", @@ -1418,9 +1419,9 @@ "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" }, "node_modules/mongodb": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.1.0.tgz", - "integrity": "sha512-kMfnKunbolQYwCIyrkxNJFB4Ypy91pYqua5NargS/f8ODNSJxT03ZU3n1JqL4mCzbSih8tvmMEMLpKTT7x5gCg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.1.1.tgz", + "integrity": "sha512-067DXiMjcpYQl6bGjWQoTUEE9UoRViTtKFcoqX7z08I+iDZv/emH1g8XEFiO3qiDfXAheT5ozl1VffDTKhIW/w==", "dependencies": { "@mongodb-js/saslprep": "^1.3.0", "bson": "^7.1.1", diff --git a/package.json b/package.json index b6d2e99..64b1024 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "argon2": "^0.44.0", + "mongodb": "^7.1.1", "mongoose": "^9.3.1", "next": "16.0.1", "react": "19.2.0", diff --git a/server/mongodb/actions/animal.ts b/server/mongodb/actions/animal.ts index 5b05d80..6986ea1 100644 --- a/server/mongodb/actions/animal.ts +++ b/server/mongodb/actions/animal.ts @@ -4,7 +4,7 @@ import animal from "../models/Animal"; export async function createAnimal(animalData: AnimalData) { const newAnimal = new animal(animalData); await newAnimal.save(); - return animal; + return newAnimal; } export async function getAnimal(animalId: string) { diff --git a/src/pages/api/user/route.ts b/src/pages/api/user/route.ts index 9bbbfb9..7c1c464 100644 --- a/src/pages/api/user/route.ts +++ b/src/pages/api/user/route.ts @@ -17,9 +17,11 @@ export default async function handler ( ) { if (req.method === 'POST') { try { + const hasAdminValue = req.body.admin === null ? true : false; + if (!req.body.fullName || !req.body.password || !req.body.email || !req.body.admin) { res.status(500).json({ - message: "New users must have a username and password" + message: "New users must have a username,password, email, and whether they are an admin or not" }); } const argon2 = require('argon2'); From 9a3db887e6a856291a28661bcd8e90686f64018d Mon Sep 17 00:00:00 2001 From: Kushagra Goel Date: Wed, 1 Apr 2026 17:58:17 -0400 Subject: [PATCH 06/14] created training log endpoints --- package-lock.json | 10 ++++ package.json | 1 + server/mongodb/actions/training.ts | 18 +++++++ server/mongodb/models/Training.ts | 16 ++++++ src/pages/api/admin/animals/route.ts | 1 + src/pages/api/admin/training/route.ts | 36 +++++++++++++ src/pages/api/training/route.ts | 78 +++++++++++++++++++++++++++ src/types/types.ts | 9 ++++ 8 files changed, 169 insertions(+) create mode 100644 server/mongodb/actions/training.ts create mode 100644 server/mongodb/models/Training.ts create mode 100644 src/pages/api/admin/training/route.ts create mode 100644 src/pages/api/training/route.ts diff --git a/package-lock.json b/package-lock.json index 6a9ffe9..0ffb686 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "argon2": "^0.44.0", + "date-fns": "^4.1.0", "mongodb": "^7.1.1", "mongoose": "^9.3.1", "next": "16.0.1", @@ -1088,6 +1089,15 @@ "dev": true, "license": "MIT" }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", diff --git a/package.json b/package.json index 64b1024..d73c564 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "argon2": "^0.44.0", + "date-fns": "^4.1.0", "mongodb": "^7.1.1", "mongoose": "^9.3.1", "next": "16.0.1", diff --git a/server/mongodb/actions/training.ts b/server/mongodb/actions/training.ts new file mode 100644 index 0000000..fa9f385 --- /dev/null +++ b/server/mongodb/actions/training.ts @@ -0,0 +1,18 @@ +import { TrainingData } from "@/types/types"; +import trainingData from "../models/Training"; + +export async function createLog(log: TrainingData) { + const newLog = new trainingData(log); + await newLog.save(); + return newLog; +} + +export async function updateLog(logId: string, newData: TrainingData) { + const updatedLog = await trainingData.findByIdAndUpdate(logId, newData); + return updatedLog; +} + +export async function getAllLogs() { + const allLogs = await trainingData.find({}); + return allLogs; +} \ No newline at end of file diff --git a/server/mongodb/models/Training.ts b/server/mongodb/models/Training.ts new file mode 100644 index 0000000..ea7253b --- /dev/null +++ b/server/mongodb/models/Training.ts @@ -0,0 +1,16 @@ +import mongoose from "mongoose"; + + +const trainingSchema = new mongoose.Schema({ + user : mongoose.Schema.Types.ObjectId, + animal : mongoose.Schema.Types.ObjectId, + title : String, + date : Date, + description : String, + hours : mongoose.Schema.Types.Number, + + +}); + +export default mongoose.model("Training", trainingSchema); + diff --git a/src/pages/api/admin/animals/route.ts b/src/pages/api/admin/animals/route.ts index ff39e34..f3b682f 100644 --- a/src/pages/api/admin/animals/route.ts +++ b/src/pages/api/admin/animals/route.ts @@ -1,6 +1,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { getAllAnimals } from "../../../../../server/mongodb/actions/animal"; import connectDb from "../../../../../server/mongodb/connectDb"; +import { AnimalData } from "@/types/types"; interface AnimalApiData { animals?: any[]; diff --git a/src/pages/api/admin/training/route.ts b/src/pages/api/admin/training/route.ts new file mode 100644 index 0000000..df7cb51 --- /dev/null +++ b/src/pages/api/admin/training/route.ts @@ -0,0 +1,36 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { getAllLogs } from "../../../../../server/mongodb/actions/training"; +import connectDb from "../../../../../server/mongodb/connectDb"; + +interface TrainingApiData{ + logs?: any[]; + message: string; +} + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method === 'GET') { + try { + await connectDb(); + const logs = await getAllLogs(); + if (!logs || logs.length === 0) { + res.status(500).json({ + message: "There are no logs to return" + }); + } + + res.status(200).json({ + logs: logs, + message: "Succesfully retrieved all logs" + }); + + } catch (e) { + res.status(500).json({ + message: "There was an error in getting the training logs" + }); + } + } + +} diff --git a/src/pages/api/training/route.ts b/src/pages/api/training/route.ts new file mode 100644 index 0000000..2162a41 --- /dev/null +++ b/src/pages/api/training/route.ts @@ -0,0 +1,78 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { TrainingData } from "@/types/types"; +import { getAnimal } from "../../../../server/mongodb/actions/animal"; +import {createLog, updateLog} from "../../../../server/mongodb/actions/training"; +import connectDb from "../../../../server/mongodb/connectDb"; +import { isValid, isFuture} from "date-fns"; + +interface LogApiData{ + logData? : TrainingData; + message : string; +} + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method === 'POST') { + try { + if (!req.body.user || !req.body.animal || !req.body.title || !req.body.date || !req.body.description || !req.body.hours) { + res.status(500).json({ + message: "You must include all information to create a training log" + }); + } + const checkDate = (date: string | Date) => { + if (!isValid(date)) return false; + if (isFuture(date)) return false; + return true; + }; + + if (!checkDate) { + res.status(400).json({ + message : "invalid date given" + }); + } + + + const animalId = req.body.animal; + const user = req.body.user; + await connectDb(); + const animalInfo = await getAnimal(animalId); + const animalOwner = animalInfo?.owner; + if (!animalOwner) { + res.status(400).json({ + message: "Failed to find animal given" + }); + } + if (animalOwner != user) { + res.status(400).json({ + message : "Animal owner and owner given are not the same" + }); + } + + const logData = { + user : req.body.user, + animal : req.body.animal, + title : req.body.title, + date : req.body.date, + description : req.body.description, + hours : req.body.hours + }; + + const log = await createLog(logData) + + + res.status(200).json({ + logData : logData, + message: "Succesfully created log" + }); + + + } catch (e) { + res.status(500).json({ + message: "There was an issue in creating the training log" + }); + + } + } +} diff --git a/src/types/types.ts b/src/types/types.ts index 8760a44..3478595 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -12,4 +12,13 @@ export interface AnimalData { breed: string; owner: ObjectId; hoursTrained: number; +} + +export interface TrainingData { + user : ObjectId; + animal : ObjectId; + title : String; + date : Date; + description : String; + hours : number; } \ No newline at end of file From bd3f1ab0eec8e4a424200dca771b5dc45b81d09f Mon Sep 17 00:00:00 2001 From: Kushagra Goel <93754123+kushgoel07@users.noreply.github.com> Date: Thu, 2 Apr 2026 14:21:20 -0400 Subject: [PATCH 07/14] created training log endpoints (#2) --- package-lock.json | 10 ++++ package.json | 1 + server/mongodb/actions/training.ts | 18 +++++++ server/mongodb/models/Training.ts | 16 ++++++ src/pages/api/admin/animals/route.ts | 1 + src/pages/api/admin/training/route.ts | 36 +++++++++++++ src/pages/api/training/route.ts | 78 +++++++++++++++++++++++++++ src/types/types.ts | 9 ++++ 8 files changed, 169 insertions(+) create mode 100644 server/mongodb/actions/training.ts create mode 100644 server/mongodb/models/Training.ts create mode 100644 src/pages/api/admin/training/route.ts create mode 100644 src/pages/api/training/route.ts diff --git a/package-lock.json b/package-lock.json index 6a9ffe9..0ffb686 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "argon2": "^0.44.0", + "date-fns": "^4.1.0", "mongodb": "^7.1.1", "mongoose": "^9.3.1", "next": "16.0.1", @@ -1088,6 +1089,15 @@ "dev": true, "license": "MIT" }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", diff --git a/package.json b/package.json index 64b1024..d73c564 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "argon2": "^0.44.0", + "date-fns": "^4.1.0", "mongodb": "^7.1.1", "mongoose": "^9.3.1", "next": "16.0.1", diff --git a/server/mongodb/actions/training.ts b/server/mongodb/actions/training.ts new file mode 100644 index 0000000..fa9f385 --- /dev/null +++ b/server/mongodb/actions/training.ts @@ -0,0 +1,18 @@ +import { TrainingData } from "@/types/types"; +import trainingData from "../models/Training"; + +export async function createLog(log: TrainingData) { + const newLog = new trainingData(log); + await newLog.save(); + return newLog; +} + +export async function updateLog(logId: string, newData: TrainingData) { + const updatedLog = await trainingData.findByIdAndUpdate(logId, newData); + return updatedLog; +} + +export async function getAllLogs() { + const allLogs = await trainingData.find({}); + return allLogs; +} \ No newline at end of file diff --git a/server/mongodb/models/Training.ts b/server/mongodb/models/Training.ts new file mode 100644 index 0000000..ea7253b --- /dev/null +++ b/server/mongodb/models/Training.ts @@ -0,0 +1,16 @@ +import mongoose from "mongoose"; + + +const trainingSchema = new mongoose.Schema({ + user : mongoose.Schema.Types.ObjectId, + animal : mongoose.Schema.Types.ObjectId, + title : String, + date : Date, + description : String, + hours : mongoose.Schema.Types.Number, + + +}); + +export default mongoose.model("Training", trainingSchema); + diff --git a/src/pages/api/admin/animals/route.ts b/src/pages/api/admin/animals/route.ts index ff39e34..f3b682f 100644 --- a/src/pages/api/admin/animals/route.ts +++ b/src/pages/api/admin/animals/route.ts @@ -1,6 +1,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { getAllAnimals } from "../../../../../server/mongodb/actions/animal"; import connectDb from "../../../../../server/mongodb/connectDb"; +import { AnimalData } from "@/types/types"; interface AnimalApiData { animals?: any[]; diff --git a/src/pages/api/admin/training/route.ts b/src/pages/api/admin/training/route.ts new file mode 100644 index 0000000..df7cb51 --- /dev/null +++ b/src/pages/api/admin/training/route.ts @@ -0,0 +1,36 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { getAllLogs } from "../../../../../server/mongodb/actions/training"; +import connectDb from "../../../../../server/mongodb/connectDb"; + +interface TrainingApiData{ + logs?: any[]; + message: string; +} + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method === 'GET') { + try { + await connectDb(); + const logs = await getAllLogs(); + if (!logs || logs.length === 0) { + res.status(500).json({ + message: "There are no logs to return" + }); + } + + res.status(200).json({ + logs: logs, + message: "Succesfully retrieved all logs" + }); + + } catch (e) { + res.status(500).json({ + message: "There was an error in getting the training logs" + }); + } + } + +} diff --git a/src/pages/api/training/route.ts b/src/pages/api/training/route.ts new file mode 100644 index 0000000..2162a41 --- /dev/null +++ b/src/pages/api/training/route.ts @@ -0,0 +1,78 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { TrainingData } from "@/types/types"; +import { getAnimal } from "../../../../server/mongodb/actions/animal"; +import {createLog, updateLog} from "../../../../server/mongodb/actions/training"; +import connectDb from "../../../../server/mongodb/connectDb"; +import { isValid, isFuture} from "date-fns"; + +interface LogApiData{ + logData? : TrainingData; + message : string; +} + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method === 'POST') { + try { + if (!req.body.user || !req.body.animal || !req.body.title || !req.body.date || !req.body.description || !req.body.hours) { + res.status(500).json({ + message: "You must include all information to create a training log" + }); + } + const checkDate = (date: string | Date) => { + if (!isValid(date)) return false; + if (isFuture(date)) return false; + return true; + }; + + if (!checkDate) { + res.status(400).json({ + message : "invalid date given" + }); + } + + + const animalId = req.body.animal; + const user = req.body.user; + await connectDb(); + const animalInfo = await getAnimal(animalId); + const animalOwner = animalInfo?.owner; + if (!animalOwner) { + res.status(400).json({ + message: "Failed to find animal given" + }); + } + if (animalOwner != user) { + res.status(400).json({ + message : "Animal owner and owner given are not the same" + }); + } + + const logData = { + user : req.body.user, + animal : req.body.animal, + title : req.body.title, + date : req.body.date, + description : req.body.description, + hours : req.body.hours + }; + + const log = await createLog(logData) + + + res.status(200).json({ + logData : logData, + message: "Succesfully created log" + }); + + + } catch (e) { + res.status(500).json({ + message: "There was an issue in creating the training log" + }); + + } + } +} diff --git a/src/types/types.ts b/src/types/types.ts index 8760a44..3478595 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -12,4 +12,13 @@ export interface AnimalData { breed: string; owner: ObjectId; hoursTrained: number; +} + +export interface TrainingData { + user : ObjectId; + animal : ObjectId; + title : String; + date : Date; + description : String; + hours : number; } \ No newline at end of file From f462549f8cb3b423b2be3a0603ba6b3746b58562 Mon Sep 17 00:00:00 2001 From: nickrobertson256 Date: Thu, 2 Apr 2026 16:23:28 -0400 Subject: [PATCH 08/14] useContext and basic login page front end --- src/components/UserContext.tsx | 32 ++++++++++++++++++++++++++ src/pages/_app.tsx | 11 +++++++++ src/pages/{index.js => index.tsx} | 0 src/pages/login.tsx | 38 +++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 src/components/UserContext.tsx create mode 100644 src/pages/_app.tsx rename src/pages/{index.js => index.tsx} (100%) create mode 100644 src/pages/login.tsx diff --git a/src/components/UserContext.tsx b/src/components/UserContext.tsx new file mode 100644 index 0000000..22e2464 --- /dev/null +++ b/src/components/UserContext.tsx @@ -0,0 +1,32 @@ +import { createContext, useState, useContext } from "react"; + +type UserType = { + id: string; + fullName: string; + isAdmin: boolean; +} | null; + +type UserContextType = { + user: UserType; + setUser: React.Dispatch>; +}; + +const UserContext = createContext(undefined); + +export function UserProvider({ children }: { children: React.ReactNode }) { + const [user, setUser] = useState(null); + + return ( + + {children} + + ); +} + +export function useUser() { + const context = useContext(UserContext); + if (!context) { + throw new Error("useUser must be used within a UserProvider"); + } + return context; +} diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx new file mode 100644 index 0000000..aa50c6c --- /dev/null +++ b/src/pages/_app.tsx @@ -0,0 +1,11 @@ +import type { AppProps } from "next/app"; +import { UserProvider } from "../components/UserContext"; +import "../styles/globals.css"; + +export default function App({ Component, pageProps }: AppProps) { + return ( + + + + ); +} diff --git a/src/pages/index.js b/src/pages/index.tsx similarity index 100% rename from src/pages/index.js rename to src/pages/index.tsx diff --git a/src/pages/login.tsx b/src/pages/login.tsx new file mode 100644 index 0000000..e604322 --- /dev/null +++ b/src/pages/login.tsx @@ -0,0 +1,38 @@ +import React from "react"; +import { useState } from "react"; + +export default function LoginPage() { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + const formData = new FormData(e.currentTarget); + const email = formData.get("email"); + const password = formData.get("password"); + + const response = await fetch("/api/auth/login", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email, password }), + }); + }; + + return ( + <> +

Login

+
+
+ + +
+
+ + +
+ +
+ + ); +} From 98c7b290979e8b547f0c28e5f812752a90218a7f Mon Sep 17 00:00:00 2001 From: nickrobertson256 Date: Thu, 2 Apr 2026 16:30:31 -0400 Subject: [PATCH 09/14] redirect to login page by default --- src/pages/_app.tsx | 1 - src/pages/index.tsx | 12 ++++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index aa50c6c..98775cb 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,6 +1,5 @@ import type { AppProps } from "next/app"; import { UserProvider } from "../components/UserContext"; -import "../styles/globals.css"; export default function App({ Component, pageProps }: AppProps) { return ( diff --git a/src/pages/index.tsx b/src/pages/index.tsx index e69de29..c001558 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -0,0 +1,12 @@ +import { useEffect } from "react"; +import { useRouter } from "next/router"; + +export default function Home() { + const router = useRouter(); + + useEffect(() => { + router.push("/login"); + }, [router]); + + return null; +} From c09fd01593b245f5f2571a1c4d2ca61a55031851 Mon Sep 17 00:00:00 2001 From: nickrobertson256 Date: Sun, 5 Apr 2026 17:41:50 -0400 Subject: [PATCH 10/14] login page UI polishing --- src/pages/_app.tsx | 8 +++- src/pages/login.tsx | 94 +++++++++++++++++++++++++++++++----------- src/styles/globals.css | 1 + 3 files changed, 77 insertions(+), 26 deletions(-) create mode 100644 src/styles/globals.css diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 98775cb..7d02f0e 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,10 +1,16 @@ import type { AppProps } from "next/app"; import { UserProvider } from "../components/UserContext"; +import "../styles/globals.css"; +import { Heebo } from "next/font/google"; + +const heebo = Heebo({ subsets: ["latin"], weight: ["400", "500", "700"] }); export default function App({ Component, pageProps }: AppProps) { return ( - +
+ +
); } diff --git a/src/pages/login.tsx b/src/pages/login.tsx index e604322..87360fb 100644 --- a/src/pages/login.tsx +++ b/src/pages/login.tsx @@ -1,38 +1,82 @@ -import React from "react"; import { useState } from "react"; +import Link from "next/link"; +import { useRouter } from "next/router"; +import { useUser } from "../components/UserContext"; export default function LoginPage() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); - const formData = new FormData(e.currentTarget); - const email = formData.get("email"); - const password = formData.get("password"); + const { setUser } = useUser(); + const router = useRouter(); - const response = await fetch("/api/auth/login", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ email, password }), - }); - }; + async function handleLogin(e: React.FormEvent) { + e.preventDefault(); + setError(""); + setLoading(true); + try { + // Simulate API call + console.log("Logging in with", { email, password }); + } catch (err) { + setError("Failed to log in. Please try again."); + } finally { + setLoading(false); + } + } return ( - <> -

Login

-
-
- - -
-
- - +
+
+
+

+ Login +

+ + +
+ setEmail(e.target.value)} + className="w-full bg-transparent border-b-2 border-[#D21312] outline-none py-2 text-[clamp(16px,2vw,22px)]" + placeholder="Email" + /> +
+ +
+ setPassword(e.target.value)} + className="w-full bg-transparent border-b-2 border-[#D21312] outline-none py-2 text-[clamp(16px,2vw,22px)]" + placeholder="Password" + /> +
+ + + + +

+ Don't have an account?{" "} + + Sign up + +

- - - + +
+
+
); } diff --git a/src/styles/globals.css b/src/styles/globals.css new file mode 100644 index 0000000..a461c50 --- /dev/null +++ b/src/styles/globals.css @@ -0,0 +1 @@ +@import "tailwindcss"; \ No newline at end of file From 76392fc361294e2a71fdae8432ed39d60ae92ff3 Mon Sep 17 00:00:00 2001 From: nickrobertson256 Date: Sun, 5 Apr 2026 18:44:31 -0400 Subject: [PATCH 11/14] user login page working, new verify endpoint --- server/mongodb/models/User.ts | 2 +- src/pages/api/user/verify.ts | 48 +++++++++++++++++++++++++++++++++++ src/pages/login.tsx | 24 ++++++++++++++++-- 3 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 src/pages/api/user/verify.ts diff --git a/server/mongodb/models/User.ts b/server/mongodb/models/User.ts index 9a198a5..097c7e2 100644 --- a/server/mongodb/models/User.ts +++ b/server/mongodb/models/User.ts @@ -20,4 +20,4 @@ const userSchema = new mongoose.Schema({ } }); -export default mongoose.model("User", userSchema); \ No newline at end of file +export default mongoose.models.User || mongoose.model("User", userSchema); \ No newline at end of file diff --git a/src/pages/api/user/verify.ts b/src/pages/api/user/verify.ts new file mode 100644 index 0000000..8ac5618 --- /dev/null +++ b/src/pages/api/user/verify.ts @@ -0,0 +1,48 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { UserData } from "../../../types/types"; +import { getUserByEmail } from "../../../../server/mongodb/actions/user"; +import connectDb from "../../../../server/mongodb/connectDb"; +import * as argon2 from "argon2"; + +interface VerifyApiData { + userId? : string; + isAdmin? : boolean; + message : string; +} + +export default async function handler ( + req: NextApiRequest, + res: NextApiResponse, +) { + if (req.method !== 'POST') { + return res.status(405).json( + { message: "Method not allowed" }); + } + + try { + const { email, password } = req.body; + + if (!email || !password) { + return res.status(500).json({ message: "Email and password are required" }); + } + + await connectDb(); + const user = await getUserByEmail(email); + if (!user) { + return res.status(500).json({ message: "Invalid email or password" }); + } + + const isPasswordValid = await argon2.verify(user.password, password); + if (!isPasswordValid) { + return res.status(500).json({ message: "Invalid email or password" }); + } + + return res.status(200).json({ + userId: user._id.toString(), + isAdmin: user.admin, + message: "Login successful" + }); + } catch (e) { + return res.status(500).json({ message: "An error occurred during login" }); + } +} \ No newline at end of file diff --git a/src/pages/login.tsx b/src/pages/login.tsx index 87360fb..2e27126 100644 --- a/src/pages/login.tsx +++ b/src/pages/login.tsx @@ -18,8 +18,28 @@ export default function LoginPage() { setError(""); setLoading(true); try { - // Simulate API call - console.log("Logging in with", { email, password }); + const res = await fetch("/api/user/verify", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ email, password }), + }); + + const data = await res.json(); + + if (!res.ok) { + setError("Failed to log in. Please try again."); + return; + } + + setUser({ + id: data.userId, + fullName: data.fullName, + isAdmin: data.isAdmin, + }); + + router.push("/dashboard"); } catch (err) { setError("Failed to log in. Please try again."); } finally { From ded9887a77642b174bbcd6a9f1de419c2b6431a1 Mon Sep 17 00:00:00 2001 From: Kushagra Goel Date: Sun, 5 Apr 2026 22:40:29 -0400 Subject: [PATCH 12/14] ui for sidebar, still need to add api endpoints --- package-lock.json | 9 ++ package.json | 1 + ...AnimalLogo.png => inactiveAnimalsLogo.png} | Bin ...iningLogs.png => inactiveTrainingLogo.png} | Bin src/components/SideBar.tsx | 91 ++++++++++++++++++ src/pages/trainingDashboard.tsx | 22 +++++ 6 files changed, 123 insertions(+) rename public/images/{inactiveAnimalLogo.png => inactiveAnimalsLogo.png} (100%) rename public/images/{inactiveTrainingLogs.png => inactiveTrainingLogo.png} (100%) create mode 100644 src/components/SideBar.tsx create mode 100644 src/pages/trainingDashboard.tsx diff --git a/package-lock.json b/package-lock.json index 0ffb686..2aa5cdf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "argon2": "^0.44.0", "date-fns": "^4.1.0", + "lucide-react": "^1.7.0", "mongodb": "^7.1.1", "mongoose": "^9.3.1", "next": "16.0.1", @@ -1413,6 +1414,14 @@ "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==", + "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 d73c564..40661fe 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "dependencies": { "argon2": "^0.44.0", "date-fns": "^4.1.0", + "lucide-react": "^1.7.0", "mongodb": "^7.1.1", "mongoose": "^9.3.1", "next": "16.0.1", diff --git a/public/images/inactiveAnimalLogo.png b/public/images/inactiveAnimalsLogo.png similarity index 100% rename from public/images/inactiveAnimalLogo.png rename to public/images/inactiveAnimalsLogo.png diff --git a/public/images/inactiveTrainingLogs.png b/public/images/inactiveTrainingLogo.png similarity index 100% rename from public/images/inactiveTrainingLogs.png rename to public/images/inactiveTrainingLogo.png diff --git a/src/components/SideBar.tsx b/src/components/SideBar.tsx new file mode 100644 index 0000000..6fcb106 --- /dev/null +++ b/src/components/SideBar.tsx @@ -0,0 +1,91 @@ +import React, { useState } from 'react'; + +type Section = "trainingLogs" | "animals" | "allTraining" | "allAnimals" | "allUsers"; + +interface ButtonData { + id: Section; + label: string; + iconName: string; +} + +export default function SideBar() { + const [activeId, setActiveId] = useState
("trainingLogs"); + + const mainButtons: ButtonData[] = [ + { id: "trainingLogs", label: "Training logs", iconName: "TrainingLogo" }, + { id: "animals", label: "Animals", iconName: "AnimalsLogo" }, + ]; + + const adminButtons: ButtonData[] = [ + { id: "allTraining", label: "All training", iconName: "AllTrainingLogo" }, + { id: "allAnimals", label: "All animals", iconName: "AllAnimalsLogo" }, + { id: "allUsers", label: "All users", iconName: "AllUsersLogo" }, + ]; + + const getButtonStyle = (id: Section) => ` + w-[90%] mx-auto h-[6vh] px-[1.5vw] + flex items-center gap-[1vw] rounded-[15px] + transition-all duration-200 font-['Heebo'] + text-[1.3vw] whitespace-nowrap + ${activeId === id ? "bg-[#D21312] text-white" : "text-[#565252] hover:bg-gray-200"} + `; + + return ( + + ); +} \ No newline at end of file diff --git a/src/pages/trainingDashboard.tsx b/src/pages/trainingDashboard.tsx new file mode 100644 index 0000000..b1ece94 --- /dev/null +++ b/src/pages/trainingDashboard.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import {useState} from 'react'; +import SideBar from "../components/SideBar"; + + +export default function TrainingDashboard() { + + + + return ( +
+ + + + {/* Main Content Area */} +
+ {/* Your page content here */} +
+ +
+ ) +} \ No newline at end of file From 3cacfbe6697f48ed4dab1d6d583267d5587c71ae Mon Sep 17 00:00:00 2001 From: Kushagra Goel <93754123+kushgoel07@users.noreply.github.com> Date: Tue, 7 Apr 2026 19:02:20 -0400 Subject: [PATCH 13/14] Side bar component (#3) * created training log endpoints * ui for sidebar, still need to add api endpoints --- package-lock.json | 9 ++ package.json | 1 + ...AnimalLogo.png => inactiveAnimalsLogo.png} | Bin ...iningLogs.png => inactiveTrainingLogo.png} | Bin src/components/SideBar.tsx | 91 ++++++++++++++++++ src/pages/trainingDashboard.tsx | 22 +++++ 6 files changed, 123 insertions(+) rename public/images/{inactiveAnimalLogo.png => inactiveAnimalsLogo.png} (100%) rename public/images/{inactiveTrainingLogs.png => inactiveTrainingLogo.png} (100%) create mode 100644 src/components/SideBar.tsx create mode 100644 src/pages/trainingDashboard.tsx diff --git a/package-lock.json b/package-lock.json index 0ffb686..2aa5cdf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "argon2": "^0.44.0", "date-fns": "^4.1.0", + "lucide-react": "^1.7.0", "mongodb": "^7.1.1", "mongoose": "^9.3.1", "next": "16.0.1", @@ -1413,6 +1414,14 @@ "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==", + "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 d73c564..40661fe 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "dependencies": { "argon2": "^0.44.0", "date-fns": "^4.1.0", + "lucide-react": "^1.7.0", "mongodb": "^7.1.1", "mongoose": "^9.3.1", "next": "16.0.1", diff --git a/public/images/inactiveAnimalLogo.png b/public/images/inactiveAnimalsLogo.png similarity index 100% rename from public/images/inactiveAnimalLogo.png rename to public/images/inactiveAnimalsLogo.png diff --git a/public/images/inactiveTrainingLogs.png b/public/images/inactiveTrainingLogo.png similarity index 100% rename from public/images/inactiveTrainingLogs.png rename to public/images/inactiveTrainingLogo.png diff --git a/src/components/SideBar.tsx b/src/components/SideBar.tsx new file mode 100644 index 0000000..6fcb106 --- /dev/null +++ b/src/components/SideBar.tsx @@ -0,0 +1,91 @@ +import React, { useState } from 'react'; + +type Section = "trainingLogs" | "animals" | "allTraining" | "allAnimals" | "allUsers"; + +interface ButtonData { + id: Section; + label: string; + iconName: string; +} + +export default function SideBar() { + const [activeId, setActiveId] = useState
("trainingLogs"); + + const mainButtons: ButtonData[] = [ + { id: "trainingLogs", label: "Training logs", iconName: "TrainingLogo" }, + { id: "animals", label: "Animals", iconName: "AnimalsLogo" }, + ]; + + const adminButtons: ButtonData[] = [ + { id: "allTraining", label: "All training", iconName: "AllTrainingLogo" }, + { id: "allAnimals", label: "All animals", iconName: "AllAnimalsLogo" }, + { id: "allUsers", label: "All users", iconName: "AllUsersLogo" }, + ]; + + const getButtonStyle = (id: Section) => ` + w-[90%] mx-auto h-[6vh] px-[1.5vw] + flex items-center gap-[1vw] rounded-[15px] + transition-all duration-200 font-['Heebo'] + text-[1.3vw] whitespace-nowrap + ${activeId === id ? "bg-[#D21312] text-white" : "text-[#565252] hover:bg-gray-200"} + `; + + return ( + + ); +} \ No newline at end of file diff --git a/src/pages/trainingDashboard.tsx b/src/pages/trainingDashboard.tsx new file mode 100644 index 0000000..b1ece94 --- /dev/null +++ b/src/pages/trainingDashboard.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import {useState} from 'react'; +import SideBar from "../components/SideBar"; + + +export default function TrainingDashboard() { + + + + return ( +
+ + + + {/* Main Content Area */} +
+ {/* Your page content here */} +
+ +
+ ) +} \ No newline at end of file From e3ce47261542881270cdc47d49f632508d0496bf Mon Sep 17 00:00:00 2001 From: Kushagra Goel Date: Tue, 7 Apr 2026 19:31:18 -0400 Subject: [PATCH 14/14] added sidebar and connected it to backend --- src/components/SideBar.tsx | 25 +++++++++++++++++++------ src/components/UserContext.tsx | 2 +- src/pages/trainingDashboard.tsx | 32 ++++++++++++++++++++++++++++---- src/types/types.ts | 5 +++-- 4 files changed, 51 insertions(+), 13 deletions(-) diff --git a/src/components/SideBar.tsx b/src/components/SideBar.tsx index 6fcb106..cb9abb1 100644 --- a/src/components/SideBar.tsx +++ b/src/components/SideBar.tsx @@ -1,4 +1,6 @@ import React, { useState } from 'react'; +import {UserType} from "../components/UserContext" +import { useRouter } from 'next/navigation'; // Or 'react-router-dom' depending on your setup type Section = "trainingLogs" | "animals" | "allTraining" | "allAnimals" | "allUsers"; @@ -8,9 +10,15 @@ interface ButtonData { iconName: string; } -export default function SideBar() { - const [activeId, setActiveId] = useState
("trainingLogs"); +interface SideBarProps { + user : UserType + setUser: React.Dispatch>; +} + +export default function SideBar({user, setUser} : SideBarProps) { + const [activeId, setActiveId] = useState
("trainingLogs"); + const router = useRouter(); const mainButtons: ButtonData[] = [ { id: "trainingLogs", label: "Training logs", iconName: "TrainingLogo" }, { id: "animals", label: "Animals", iconName: "AnimalsLogo" }, @@ -30,6 +38,11 @@ export default function SideBar() { ${activeId === id ? "bg-[#D21312] text-white" : "text-[#565252] hover:bg-gray-200"} `; + const handleLogout = () => { + setUser(null); + router.push('/login'); + }; + return (
- Long Lam - Admin + {user?.fullName} + {user?.isAdmin}
- diff --git a/src/components/UserContext.tsx b/src/components/UserContext.tsx index 22e2464..640ca71 100644 --- a/src/components/UserContext.tsx +++ b/src/components/UserContext.tsx @@ -1,6 +1,6 @@ import { createContext, useState, useContext } from "react"; -type UserType = { +export type UserType = { id: string; fullName: string; isAdmin: boolean; diff --git a/src/pages/trainingDashboard.tsx b/src/pages/trainingDashboard.tsx index b1ece94..0e2ec29 100644 --- a/src/pages/trainingDashboard.tsx +++ b/src/pages/trainingDashboard.tsx @@ -1,20 +1,44 @@ import React from "react"; -import {useState} from 'react'; +import {useState, useEffect} from 'react'; import SideBar from "../components/SideBar"; +import { UserProvider } from "../components/UserContext"; +import {useUser} from "../components/UserContext"; +import { TrainingData, AnimalData } from "@/types/types"; export default function TrainingDashboard() { + const { user, setUser } = useUser(); + const [logs, setLogs] = useState([]); + const [loading, setLoading] = useState(true); + useEffect(() => { + async function fetchLogs() { + try { + const res = await fetch('/api/training/route'); + if (!res.ok) { + throw new Error('Failed to fetch logs'); + } + const data = await res.json(); + setLogs(data); + } catch (err) { + console.error("Error fetching logs: ", err); + } finally { + setLoading(false); + } + } + + fetchLogs(); + }); return (
- + - {/* Main Content Area */} +
- {/* Your page content here */} +
diff --git a/src/types/types.ts b/src/types/types.ts index 3478595..a872ba3 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -16,9 +16,10 @@ export interface AnimalData { export interface TrainingData { user : ObjectId; - animal : ObjectId; + animal : AnimalData; title : String; date : Date; description : String; hours : number; -} \ No newline at end of file +} +