diff --git a/.github/ISSUE_TEMPLATE/new-issue.md b/.github/ISSUE_TEMPLATE/new-issue.md index 6dc29db..7bd092e 100644 --- a/.github/ISSUE_TEMPLATE/new-issue.md +++ b/.github/ISSUE_TEMPLATE/new-issue.md @@ -1,10 +1,9 @@ --- name: New Issue about: Create a new issue -title: '' -labels: '' -assignees: '' - +title: "" +labels: "" +assignees: "" --- **Description** @@ -14,6 +13,7 @@ A clear and concise description of what the problem is. Ex. I'm always frustrate A clear and concise description of what you want to happen. Steps: + - step 1 - step 2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c932ac1..0afb38c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,6 +43,6 @@ jobs: # - Settings -> Secrets and variables -> New repository secret MONGO_URI: ${{ secrets.MONGO_URI }} # Add additional environment variables here - + - name: Run tests run: npm test diff --git a/.gitignore b/.gitignore index 8f322f0..08b183c 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,8 @@ yarn-error.log* # local env files .env*.local +.env +.env.local # vercel .vercel diff --git a/README.md b/README.md index 130bf99..a342efb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# name of non-profit +# Food Pantry -{One sentence description of the project} +Bootcamp mini project designing a fullstack website for the Calpoly Food Pantry. ## Table of Contents @@ -19,11 +19,13 @@ The {non-profit name} team consists of {#} Cal Poly students. Over the course of about 9 months, we worked as a team to deploy this web application. The team members are listed below: -- [First Last](https://www.linkedin.com/) - Project Manager -- [First Last](https://www.linkedin.com/) - Designer -- [First Last](https://www.linkedin.com/) - Tech Lead -- [First Last](https://www.linkedin.com/) - Tech Lead -- [First Last](https://www.linkedin.com/) - Software Developer +- [Chloe Low](https://www.linkedin.com/in/chloe-is-low/) - Tech Lead +- [Cole Porter](https://www.linkedin.com/in/coleporter0/) - Software Developer +- [Rebecca Blau](https://www.linkedin.com/in/rebecca-blau/) - Software Developer +- [Bryan Garcia](https://www.linkedin.com/) - Software Developer +- [Giovanni Cosio](https://www.linkedin.com/in/giovanni-cosio/) - Software Developer +- [Abed Nianaa](https://www.linkedin.com/in/abdelrhaman-nianaa-b83050314/) - Software Developer +- [Nishant Sengupta](https://www.linkedin.com/in/nishant-sengupta-78b309411) - Software Developer ## Getting Started And Contributing diff --git a/docs/getting-started.md b/docs/getting-started.md index 1ae4b17..09f5533 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -13,7 +13,7 @@ Welcome to the getting started page! Here is all you need to know to get this re 1. Clone this repository `git clone ` 2. Run `npm i` at the root of the repository -3. Create a .env file and retrieve secrets from your tech lead +3. Create a .env.local file and paste the Mongo URI from your tech lead within 4. Install IDE Extensions - [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) - [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) diff --git a/next.config.js b/next.config.js index 767719f..658404a 100644 --- a/next.config.js +++ b/next.config.js @@ -1,4 +1,4 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {} +const nextConfig = {}; -module.exports = nextConfig +module.exports = nextConfig; diff --git a/package-lock.json b/package-lock.json index 7d47725..eccd586 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,12 @@ "name": "nextjs-app-template", "version": "0.1.0", "dependencies": { - "mongoose": "^8", + "mongodb": "^7.6.0", + "mongoose": "^8.24.5", "next": "^14", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "react-router-dom": "^7.18.4" }, "devDependencies": { "@types/node": "^20", @@ -163,9 +165,10 @@ } }, "node_modules/@mongodb-js/saslprep": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", - "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.5.4.tgz", + "integrity": "sha512-05UC0jQsjKAOuXQ0H9Ud9vUTJpZIg+n/FinpR30tI5I8pY2inTfPOZ5OF/cg3Ce/N9MoD1xhRCeOsJtuTbFYlw==", + "license": "MIT", "dependencies": { "sparse-bitfield": "^3.0.3" } @@ -454,12 +457,14 @@ "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==" + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" }, "node_modules/@types/whatwg-url": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", - "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "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": "*" } @@ -985,9 +990,10 @@ } }, "node_modules/bson": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-6.8.0.tgz", - "integrity": "sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==", + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", + "license": "Apache-2.0", "engines": { "node": ">=16.20.1" } @@ -1191,6 +1197,19 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -3505,7 +3524,8 @@ "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==" + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" }, "node_modules/merge-stream": { "version": "2.0.0", @@ -3590,25 +3610,26 @@ } }, "node_modules/mongodb": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.9.0.tgz", - "integrity": "sha512-UMopBVx1LmEUbW/QE0Hw18u583PEDVQmUmVzzBRH0o/xtE9DBRA5ZYLOjpLIa03i8FXjzvQECJcqoMvCXftTUA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.6.0.tgz", + "integrity": "sha512-WbZ6OCjYw2c53LOjfkQa+reXr7kIiOVpXXglnASFuiMtif0BvsMwHe3ClJHLm1/r7wJFwaasfFtD6iYIktB01g==", + "license": "Apache-2.0", "dependencies": { - "@mongodb-js/saslprep": "^1.1.5", - "bson": "^6.7.0", - "mongodb-connection-string-url": "^3.0.0" + "@mongodb-js/saslprep": "^1.4.11", + "bson": "^7.2.0", + "mongodb-connection-string-url": "^7.0.1" }, "engines": { - "node": ">=16.20.1" + "node": ">=20.19.0" }, "peerDependencies": { - "@aws-sdk/credential-providers": "^3.188.0", - "@mongodb-js/zstd": "^1.1.0", - "gcp-metadata": "^5.2.0", - "kerberos": "^2.0.1", - "mongodb-client-encryption": ">=6.0.0 <7", - "snappy": "^7.2.2", - "socks": "^2.7.1" + "@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.2.0", + "snappy": "^7.3.2", + "socks": "^2.8.6" }, "peerDependenciesMeta": { "@aws-sdk/credential-providers": { @@ -3635,22 +3656,36 @@ } }, "node_modules/mongodb-connection-string-url": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz", - "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.2.tgz", + "integrity": "sha512-ZoS07RoFqpKYQwAk59qmrx8+jJHNHU30UjlU96QktiGn1ltvDr+vCznLX5DiUBLEpMAHatHNWV1nM/74ul66kA==", + "license": "Apache-2.0", "dependencies": { - "@types/whatwg-url": "^11.0.2", - "whatwg-url": "^13.0.0" + "@types/whatwg-url": "^13.0.0", + "whatwg-url": "^14.1.0" + }, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/mongodb/node_modules/bson": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/bson/-/bson-7.3.3.tgz", + "integrity": "sha512-oz3LwE3qGEhTCSyhzQRLY3Bj1NLOsjFMf6tcmY5J8M+3R7EVcmoBfSHWvjUK3yXCLDUsGpIuB3aQL4nDZ1EV/g==", + "license": "Apache-2.0", + "engines": { + "node": ">=20.19.0" } }, "node_modules/mongoose": { - "version": "8.7.2", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.7.2.tgz", - "integrity": "sha512-Ok4VzMds9p5G3ZSUhmvBm1GdxanbzhS29jpSn02SPj+IXEVFnIdfwAlHHXWkyNscZKlcn8GuMi68FH++jo0flg==", + "version": "8.24.5", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.24.5.tgz", + "integrity": "sha512-S1cvmzLhw5Xthv450UjInCBq2JreRvvyfGSA3FmDw8yThPxUbPAyJqtXMz0dNm8BBFm2ctRBOBKJxDPqM2sG0w==", + "license": "MIT", "dependencies": { - "bson": "^6.7.0", + "bson": "^6.10.4", "kareem": "2.6.3", - "mongodb": "6.9.0", + "mongodb": "~6.20.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", @@ -3664,6 +3699,71 @@ "url": "https://opencollective.com/mongoose" } }, + "node_modules/mongoose/node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/mongoose/node_modules/mongodb": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz", + "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.2" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.3.2", + "socks": "^2.7.1" + }, + "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/mongoose/node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, "node_modules/mpath": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", @@ -4215,6 +4315,44 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, + "node_modules/react-router": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.4.tgz", + "integrity": "sha512-PUPQcMhMGRAslLcvtlPz/kmzBEWPhLdgLFrL7pLNepBL6dX0lWj4WD2cUYVgYCuT3jxvghYFg81cDTj44DhetQ==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.4.tgz", + "integrity": "sha512-yrfmJHIpDG7taCpqKjT1G5B6q3O2K+RN8/fgNf0lTjCwiPbQ0ei6vXX9ZjQR+7ld8Tr7Z5xmyMnZ8YJrphWQUw==", + "license": "MIT", + "dependencies": { + "react-router": "7.18.4" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", @@ -4451,6 +4589,12 @@ "node": ">=10" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -4579,6 +4723,7 @@ "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" } @@ -4933,14 +5078,15 @@ } }, "node_modules/tr46": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", - "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "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.0" + "punycode": "^2.3.1" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/ts-api-utils": { @@ -5116,20 +5262,22 @@ "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": "13.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", - "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "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": "^4.1.1", + "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/which": { diff --git a/package.json b/package.json index 765419b..f84d25e 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,12 @@ "prepare": "husky" }, "dependencies": { - "mongoose": "^8", + "mongodb": "^7.6.0", + "mongoose": "^8.24.5", "next": "^14", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "react-router-dom": "^7.18.4" }, "devDependencies": { "@types/node": "^20", diff --git a/src/app/Home.module.css b/src/app/Home.module.css new file mode 100644 index 0000000..ed199ef --- /dev/null +++ b/src/app/Home.module.css @@ -0,0 +1,66 @@ +.pageWrapper { + min-height: 100vh; + background-color: #fcfcfc; + font-family: + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + Roboto, + sans-serif; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + padding: 60px 20px 40px; + gap: 40px; +} + +.mainCard { + background-color: #b8d0c3; + width: 100%; + max-width: 900px; + padding: 40px 48px; + border-radius: 12px; + box-shadow: -4px 4px 12px rgba(0, 0, 0, 0.08); + box-sizing: border-box; +} + +.mainCard h1 { + margin: 0 0 16px 0; + color: #1b4332; + font-size: 2.3rem; + font-weight: 800; +} + +.description { + color: #111111; + font-size: 1.05rem; + line-height: 1.5; + margin: 0 0 24px 0; + font-weight: 500; +} + +.divider { + border: none; + border-top: 2px solid #1b4332; + margin-top: 30px; + margin-bottom: 10px; + opacity: 0.8; +} + +.cards { + display: flex; + justify-content: space-between; + width: 100%; + max-width: 900px; + gap: 30px; +} + +.quickInfo { + display: flex; + flex-direction: row; + gap: 50px; +} diff --git a/src/app/about/about-styles.module.css b/src/app/about/about-styles.module.css new file mode 100644 index 0000000..bd9a91a --- /dev/null +++ b/src/app/about/about-styles.module.css @@ -0,0 +1,106 @@ +.pageWrapper { + min-height: 100vh; + background-color: #fcfcfc; + font-family: + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + Roboto, + sans-serif; + color: #111111; +} + +.container { + max-width: 1400px; + margin: 0 50px; + padding: 40px 24px 80px; +} + +.title { + color: #1b4332; + font-size: 2.2rem; + font-weight: 800; + margin: 0; +} + +.titleDivider { + border: none; + border-top: 2px solid #1b4332; + margin-bottom: 20px; + width: 100%; +} + +.gridContainer { + display: grid; + grid-template-columns: 1.3fr 1fr; + gap: 60px; + align-items: start; +} + +.aboutContainer { + display: flex; + flex-direction: column; + gap: 20px; +} + +.imageWrapper { + width: 100%; + border-radius: 8px; + overflow: hidden; +} + +.pantryImg { + width: 100%; + height: auto; + object-fit: cover; + border-radius: 8px; + display: block; +} + +.paragraph { + font-size: 0.92rem; + line-height: 1.5; + color: #222222; + margin: 0; + font-weight: 500; +} + +.fullWidthParagraph { + margin-top: 32px; +} + +.infoContainer { + display: flex; + flex-direction: column; + gap: 24px; +} + +.infoDiv { + border-radius: 8px; + padding: 24px; + margin-top: 30px; + background-color: #f8fafc; + border: 1px solid #d1d5db; + box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.06); +} + +.logi_header { + color: #1b4332; + font-size: 1.3rem; + font-weight: 700; + margin: 0 0 16px 0; +} + +.logi_info { + font-size: 0.9rem; + color: #222222; + line-height: 1.6; + margin: 0 0 16px 0; +} + +.disclaimer { + font-size: 0.85rem; + color: #777777; + margin: 0; +} diff --git a/src/app/about/foodPantry.png b/src/app/about/foodPantry.png new file mode 100644 index 0000000..37500cf Binary files /dev/null and b/src/app/about/foodPantry.png differ diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx new file mode 100644 index 0000000..46b5824 --- /dev/null +++ b/src/app/about/page.tsx @@ -0,0 +1,69 @@ +import styles from "./about-styles.module.css"; +import Image from "next/image"; +import Link from "next/link"; +import foodPantryImage from "./foodPantry.png"; + +export default function AboutPage() { + return ( +
+
+

About

+
+
+
+
+ Cal Poly Food Pantry +
+ +

+ The Cal Poly Food Pantry is a student-run resource open to all currently enrolled students. We believe + food insecurity should never stand in the way of academic success. All items are available free of charge, + no proof of need required. +

+ +

+ Our pantry is stocked through donations from the campus community, local businesses, and food banks. We + partner with the ASI Food Pantry and local organizations to maintain a consistent supply of nutritious + food throughout the year. +

+
+
+
+

Hours

+

+ Monday-Friday: 8:30am–6:00pm +
+ Saturday-Sunday: Closed +

+

Closed on University Holidays

+
+ +
+

Location

+

+ Campus Health and Wellbeing +
+ Building 27, Room 208 +
+ Cal Poly, San Luis Obispo, CA 93407 +

+
+
+
+

+ The majority of the food in the Cal Poly Food Pantry comes from the SLO Food Bank, where the Cal Poly Food + Pantry is a regulated, agency partner. Local produce is supplied weekly from the Cal Poly Crops unit. The Food + Pantry also participates in a food rescue program with Sprouts, Grocery Outlet, and Food 4 Less Grocery Stores + in San Luis Obispo. Lastly, community members are welcome to donate to the Food Pantry and as a result, there + is a robust inventory of food and personal hygiene items. +

+
+
+ ); +} diff --git a/src/app/api/example/route.ts b/src/app/api/example/route.ts deleted file mode 100644 index 52fd379..0000000 --- a/src/app/api/example/route.ts +++ /dev/null @@ -1,11 +0,0 @@ -import connectDB from "@/database/db"; -import { NextResponse } from "next/server"; - -/** - * Example GET API route - * @returns {message: string} - */ -export async function GET() { - await connectDB(); - return NextResponse.json({ message: "Hello from the API!" }); -} diff --git a/src/app/api/items/route.ts b/src/app/api/items/route.ts new file mode 100644 index 0000000..0c5d64a --- /dev/null +++ b/src/app/api/items/route.ts @@ -0,0 +1,89 @@ +import { NextResponse } from "next/server"; +import connectDB from "@/database/db"; +import FoodItemModel from "@/database/foodSchema"; +import { inventory } from "../../../data/mockFoodItems"; + +// Database-backed reads must run when the request arrives instead of using a cached route response. +export const dynamic = "force-dynamic"; + +export async function GET() { + try { + // Connect on the server, then return only the fields the menu needs. + await connectDB(); + const foodItems = await FoodItemModel.find({}).sort({ name: 1 }).lean(); + const menuItems = foodItems.map((item) => ({ + id: item.id, + name: item.name, + expiration_date: item.expiration_date.toISOString(), + category: item.category, + quantity: item.quantity, + })); + + return NextResponse.json(menuItems, { + status: 200, + headers: { "Cache-Control": "no-store" }, + }); + } catch (error) { + console.error("Unable to load inventory from the database:", error); + return NextResponse.json({ error: "Unable to load inventory." }, { status: 500 }); + } +} + +export async function POST(request: Request) { + let item; + try { + item = await request.json(); + } catch { + return NextResponse.json({ error: "Invalid JSON payload received." }, { status: 400 }); + } + + const errors: string[] = []; + if (typeof item.name !== "string" || item.name.trim() === "") { + errors.push("Name is required and must be a non-empty string"); + } + if (typeof item.expiration_date !== "string") { + //maybe change to if(!(item.expiration_date instanceof Date)) + errors.push("Expiration date is required and must be a string"); + } else if (isNaN(new Date(item.expiration_date).getTime())) { + errors.push("Expiration date must be a valid date"); + } + if (typeof item.category !== "string" || item.category.trim() === "") { + errors.push("Category is required and must be a non-empty string"); + } + if (typeof item.quantity !== "number" || item.quantity <= 0) { + errors.push("Quantity is required and must be a positive number"); + } + if (errors.length > 0) { + return NextResponse.json({ errors }, { status: 400 }); + } + + try { + await connectDB(); + const newItem = await FoodItemModel.create({ + id: generateUniqueId(), + name: item.name, + expiration_date: item.expiration_date, + category: item.category, + quantity: item.quantity, + }); + + return NextResponse.json(newItem, { status: 201 }); + //return NextResponse.json(newItem, { status: 201 }); + } catch (error) { + return NextResponse.json({ error: "Invalid JSON payload received." }, { status: 400 }); + } +} + +const generateUniqueId = () => { + let id: string; + + do { + const letters = Array.from({ length: 3 }, () => String.fromCharCode(Math.floor(Math.random() * 26) + 97)).join(""); + + const numbers = Math.floor(Math.random() * 1000).toString(); + + id = letters + numbers; + } while (inventory.some((item) => item.id === id)); + + return id; +}; diff --git a/src/app/api/wishlist/route.ts b/src/app/api/wishlist/route.ts new file mode 100644 index 0000000..77a833f --- /dev/null +++ b/src/app/api/wishlist/route.ts @@ -0,0 +1,77 @@ +import { NextResponse } from "next/server"; +import { inventory } from "../../../data/mockFoodItems"; +import WishlistItemModel from "@/database/wishlistSchema"; +import mongoose from "mongoose"; + +let connection: typeof mongoose | undefined; + +const createConnection = async () => { + connection = connection ?? (await mongoose.connect(process.env.MONGO_URI as string)); + return connection; +}; + +//module.exports = createConnection; + +export async function GET(request: Request, { params }: { params: { category: string } }) { + try { + await createConnection(); + const pantry_inventory = await WishlistItemModel.find({}); + return NextResponse.json(pantry_inventory, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ error: "Failed to fetch inventory" }, { status: 500 }); + } +} + +export async function POST(request: Request) { + let item; + try { + item = await request.json(); + } catch { + return NextResponse.json({ error: "Invalid JSON payload received." }, { status: 400 }); + } + + const errors: string[] = []; + if (typeof item.name !== "string" || item.name.trim() === "") { + errors.push("Name is required and must be a non-empty string"); + } + if (typeof item.category !== "string" || item.category.trim() === "") { + errors.push("Category is required and must be a non-empty string"); + } + if (typeof item.quantity !== "number" || item.quantity < 0) { + errors.push("Quantity is required and must be a positive number"); + } + if (errors.length > 0) { + return NextResponse.json({ errors }, { status: 400 }); + } + + try { + await createConnection(); + const newItem = await WishlistItemModel.create({ + id: generateUniqueId(), + name: item.name, + category: item.category, + quantity: item.quantity, + }); + + //inventory.push(newItem); + //return NextResponse.json(newItem, { status: 201 }); + return NextResponse.json(newItem, { status: 201 }); + } catch (error) { + return NextResponse.json({ error: "Failed to create item" }, { status: 500 }); + } +} + +const generateUniqueId = () => { + let id: string; + + do { + const letters = Array.from({ length: 3 }, () => String.fromCharCode(Math.floor(Math.random() * 26) + 97)).join(""); + + const numbers = Math.floor(Math.random() * 1000).toString(); + + id = letters + numbers; + } while (inventory.some((item) => item.id === id)); + + return id; +}; diff --git a/src/app/contact/Contact.module.css b/src/app/contact/Contact.module.css new file mode 100644 index 0000000..ff79d22 --- /dev/null +++ b/src/app/contact/Contact.module.css @@ -0,0 +1,16 @@ +.page { + width: 100%; + max-width: 40rem; + margin: 0 auto; + padding: 2rem 1rem 3rem; + box-sizing: border-box; +} + +.title { + margin: 0 0 0.5rem; +} + +.intro { + margin: 0 0 1.5rem; + opacity: 0.8; +} diff --git a/src/app/contact/contact.module.css b/src/app/contact/contact.module.css new file mode 100644 index 0000000..ff79d22 --- /dev/null +++ b/src/app/contact/contact.module.css @@ -0,0 +1,16 @@ +.page { + width: 100%; + max-width: 40rem; + margin: 0 auto; + padding: 2rem 1rem 3rem; + box-sizing: border-box; +} + +.title { + margin: 0 0 0.5rem; +} + +.intro { + margin: 0 0 1.5rem; + opacity: 0.8; +} diff --git a/src/app/contact/page.tsx b/src/app/contact/page.tsx new file mode 100644 index 0000000..e23a8c9 --- /dev/null +++ b/src/app/contact/page.tsx @@ -0,0 +1,75 @@ +"use client"; + +import { useState } from "react"; +import formStyles from "@/components/Form.module.css"; +import styles from "./Contact.module.css"; + +export default function Contact() { + const [sent, setSent] = useState(false); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + // No backend for Contact yet. When there is one, send the data here. + e.currentTarget.reset(); // clears the fields + setSent(true); + }; + + return ( +
+

Contact Us

+

Questions or feedback? Send us a message.

+
+
+
+ + setSent(false)} + /> +
+ +
+ + setSent(false)} + /> +
+
+ +
+ +