From d5c12dcb2b84563eecb4116f28063d650c5231af Mon Sep 17 00:00:00 2001 From: William McGonagle Date: Sun, 13 Aug 2023 22:08:58 -0400 Subject: [PATCH] Added Onboarding Files --- .github/workflows/deploy.yml | 2 +- components/paymentForm.js | 12 + components/schoolSelect.js | 18 + package-lock.json | 482 ++++++++++++++++++++++++- package.json | 8 +- pages/api/charter/join-post-payment.js | 9 + pages/api/charter/join.js | 117 ++++-- pages/api/school/banner.js | 61 ++++ pages/api/school/verify.js | 114 ++++++ pages/charter/[id].js | 3 + pages/dashboard/index.js | 36 +- pages/join.js | 196 ---------- pages/join/index.js | 332 +++++++++++++++++ pages/join/payment.js | 138 +++++++ server/models/charters.js | 1 + server/models/index.js | 13 +- 16 files changed, 1287 insertions(+), 255 deletions(-) create mode 100644 components/paymentForm.js create mode 100644 components/schoolSelect.js create mode 100644 pages/api/school/banner.js create mode 100644 pages/api/school/verify.js delete mode 100644 pages/join.js create mode 100644 pages/join/index.js create mode 100644 pages/join/payment.js diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index eab1b71..3be7655 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -34,4 +34,4 @@ jobs: run: ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts - name: Deploy with rsync - run: rsync -avz --delete . ${{ secrets.SSH_USERNAME }}@${{ secrets.SSH_HOST }}:/prod + run: rsync -avz --delete . ${{ secrets.SSH_USERNAME }}@${{ secrets.SSH_HOST }}:~/prod diff --git a/components/paymentForm.js b/components/paymentForm.js new file mode 100644 index 0000000..355dd13 --- /dev/null +++ b/components/paymentForm.js @@ -0,0 +1,12 @@ +import {PaymentElement} from '@stripe/react-stripe-js'; + +const PaymentForm = () => { + return ( +
+ + + + ); +}; + +export default PaymentForm; \ No newline at end of file diff --git a/components/schoolSelect.js b/components/schoolSelect.js new file mode 100644 index 0000000..a709c79 --- /dev/null +++ b/components/schoolSelect.js @@ -0,0 +1,18 @@ +import * as React from "react" +import Link from 'next/link' + +const SchoolSelect = ({ data, onClick, selectedIndex }) => { + + const { name, id, type, address, grades } = data || {} + + return + +} + +export default SchoolSelect diff --git a/package-lock.json b/package-lock.json index 8e45fcb..9e8ba8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,11 @@ "name": "new-charter", "version": "0.1.0", "dependencies": { + "@stripe/react-stripe-js": "^2.1.2", + "@stripe/stripe-js": "^2.0.0", "@tinymce/tinymce-react": "^4.3.0", "bcrypt": "^5.1.0", + "cheerio": "^1.0.0-rc.12", "cookie": "^0.5.0", "duckgen": "^1.1.1", "jsonwebtoken": "^9.0.0", @@ -18,6 +21,7 @@ "next": "13.2.4", "pg": "^8.10.0", "pg-hstore": "^2.3.4", + "probe-image-size": "^7.2.3", "react": "18.2.0", "react-cookie": "^4.1.1", "react-credit-cards": "^0.8.3", @@ -466,6 +470,24 @@ "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==", "dev": true }, + "node_modules/@stripe/react-stripe-js": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-2.1.2.tgz", + "integrity": "sha512-2JcKRvOdMD698uGrY0cINLHb6XHnY24L+wlWlw2+TbI2aXlQZ9t8Mc+KssFES+EuNgISbrp7fJR3w/13epfiOw==", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "@stripe/stripe-js": "^1.44.1 || ^2.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@stripe/stripe-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-2.0.0.tgz", + "integrity": "sha512-SxZnf192En0uAfgbigUIj7oJYaXgGc5AI1aos59YXvO8DPeLI0AtT4oMg/Wuk17wbpquEv73+akyCe7xdEjGlA==" + }, "node_modules/@swc/helpers": { "version": "0.4.14", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", @@ -1037,6 +1059,11 @@ "node": ">=8" } }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1189,6 +1216,42 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -1308,6 +1371,32 @@ "node": ">= 8" } }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -1497,6 +1586,57 @@ "node": ">=6.0.0" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dottie": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.3.tgz", @@ -1549,6 +1689,17 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -2606,6 +2757,24 @@ "react-is": "^16.7.0" } }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, "node_modules/http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", @@ -3274,8 +3443,7 @@ "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, "node_modules/long": { "version": "5.2.1", @@ -3580,6 +3748,41 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/needle": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz", + "integrity": "sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==", + "dependencies": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/needle/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -3811,6 +4014,17 @@ "set-blocking": "^2.0.0" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4043,6 +4257,29 @@ "node": ">=6" } }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dependencies": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4375,6 +4612,16 @@ "node": ">= 0.8.0" } }, + "node_modules/probe-image-size": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/probe-image-size/-/probe-image-size-7.2.3.tgz", + "integrity": "sha512-HubhG4Rb2UH8YtV4ba0Vp5bQ7L78RTONYu/ujmCu5nBI8wGv24s4E9xSKBi0N1MowRpxk76pFCpJtW0KPzOK0w==", + "dependencies": { + "lodash.merge": "^4.6.2", + "needle": "^2.5.2", + "stream-parser": "~0.3.1" + } + }, "node_modules/promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -4717,6 +4964,11 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "node_modules/scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -4974,6 +5226,27 @@ "node": ">= 0.4" } }, + "node_modules/stream-parser": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/stream-parser/-/stream-parser-0.3.1.tgz", + "integrity": "sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ==", + "dependencies": { + "debug": "2" + } + }, + "node_modules/stream-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/stream-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -5890,6 +6163,19 @@ "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==", "dev": true }, + "@stripe/react-stripe-js": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-2.1.2.tgz", + "integrity": "sha512-2JcKRvOdMD698uGrY0cINLHb6XHnY24L+wlWlw2+TbI2aXlQZ9t8Mc+KssFES+EuNgISbrp7fJR3w/13epfiOw==", + "requires": { + "prop-types": "^15.7.2" + } + }, + "@stripe/stripe-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-2.0.0.tgz", + "integrity": "sha512-SxZnf192En0uAfgbigUIj7oJYaXgGc5AI1aos59YXvO8DPeLI0AtT4oMg/Wuk17wbpquEv73+akyCe7xdEjGlA==" + }, "@swc/helpers": { "version": "0.4.14", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", @@ -6306,6 +6592,11 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -6408,6 +6699,33 @@ "supports-color": "^7.1.0" } }, + "cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "requires": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + } + }, + "cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "requires": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + } + }, "chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -6497,6 +6815,23 @@ "which": "^2.0.1" } }, + "css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + } + }, + "css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==" + }, "cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -6636,6 +6971,39 @@ "esutils": "^2.0.2" } }, + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + } + }, "dottie": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.3.tgz", @@ -6685,6 +7053,11 @@ "tapable": "^2.2.0" } }, + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" + }, "env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -7488,6 +7861,17 @@ "react-is": "^16.7.0" } }, + "htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, "http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", @@ -7980,8 +8364,7 @@ "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, "long": { "version": "5.2.1", @@ -8209,6 +8592,34 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "needle": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz", + "integrity": "sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==", + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -8360,6 +8771,14 @@ "set-blocking": "^2.0.0" } }, + "nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "requires": { + "boolbase": "^1.0.0" + } + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -8520,6 +8939,23 @@ "callsites": "^3.0.0" } }, + "parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "requires": { + "entities": "^4.4.0" + } + }, + "parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "requires": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + } + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -8733,6 +9169,16 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "probe-image-size": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/probe-image-size/-/probe-image-size-7.2.3.tgz", + "integrity": "sha512-HubhG4Rb2UH8YtV4ba0Vp5bQ7L78RTONYu/ujmCu5nBI8wGv24s4E9xSKBi0N1MowRpxk76pFCpJtW0KPzOK0w==", + "requires": { + "lodash.merge": "^4.6.2", + "needle": "^2.5.2", + "stream-parser": "~0.3.1" + } + }, "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -8960,6 +9406,11 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -9121,6 +9572,29 @@ "internal-slot": "^1.0.4" } }, + "stream-parser": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/stream-parser/-/stream-parser-0.3.1.tgz", + "integrity": "sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ==", + "requires": { + "debug": "2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", diff --git a/package.json b/package.json index 728ba54..c1ae880 100644 --- a/package.json +++ b/package.json @@ -5,12 +5,15 @@ "scripts": { "dev": "next dev", "build": "next build", - "start": "next", + "start": "next start", "lint": "next lint" }, "dependencies": { + "@stripe/react-stripe-js": "^2.1.2", + "@stripe/stripe-js": "^2.0.0", "@tinymce/tinymce-react": "^4.3.0", "bcrypt": "^5.1.0", + "cheerio": "^1.0.0-rc.12", "cookie": "^0.5.0", "duckgen": "^1.1.1", "jsonwebtoken": "^9.0.0", @@ -19,6 +22,7 @@ "next": "13.2.4", "pg": "^8.10.0", "pg-hstore": "^2.3.4", + "probe-image-size": "^7.2.3", "react": "18.2.0", "react-cookie": "^4.1.1", "react-credit-cards": "^0.8.3", @@ -36,4 +40,4 @@ "postcss": "^8.4.21", "tailwindcss": "^3.2.7" } -} \ No newline at end of file +} diff --git a/pages/api/charter/join-post-payment.js b/pages/api/charter/join-post-payment.js index 5a4dca3..6ecbf7f 100644 --- a/pages/api/charter/join-post-payment.js +++ b/pages/api/charter/join-post-payment.js @@ -19,6 +19,15 @@ export default async function handler(req, res) { // [ ] Charge the Payment on their Credit Card // [ ] Send Emails to them that they are pending verification + // Create the first announcement of the charter + const announcement = await Announcement.create({ + title: `${req.body.school_name} officially launches an FPA Charter.`, + short: `Today, ${req.body.president_name} launched ${req.body.school_name}'s FPA Charter.`, + content: `Today, ${req.body.president_name} launched ${req.body.school_name}'s FPA Charter. ${req.body.president_name} worked with ${req.body.vice_president_name}, charter vice-president, and ${req.body.treasurer_name}, charter treasurer, to make it happen. To learn more, please contact ${req.body.president_name} or check out the charter's page.`, + userId: president.id, + charterId: _charter.id + }) + res.status(200).json(req); } diff --git a/pages/api/charter/join.js b/pages/api/charter/join.js index ffa29ea..bdae5e2 100644 --- a/pages/api/charter/join.js +++ b/pages/api/charter/join.js @@ -16,14 +16,45 @@ function serialize(obj) { export default async function handler(req, res) { + // Verify School Information + if (req.body.school_name == undefined) return res.status(400).json({ error: "School Name is not defined." }); + if (req.body.school_nces_id == undefined) return res.status(400).json({ error: "NCES ID is not defined." }); + if (req.body.address_line == undefined) return res.status(400).json({ error: "Address Line is not defined." }); + if (req.body.address_town == undefined) return res.status(400).json({ error: "Address Town is not defined." }); + if (req.body.address_area_code == undefined) return res.status(400).json({ error: "Address Area Code is not defined." }); + if (req.body.website == undefined) return res.status(400).json({ error: "Website is not defined." }); + if (req.body.school_type == undefined) return res.status(400).json({ error: "School Type is not defined." }); + + // Verify President Information + if (req.body.president_name == undefined) return res.status(400).json({ error: "President Name is not defined." }); + if (req.body.president_email == undefined) return res.status(400).json({ error: "President Email is not defined." }); + if (req.body.president_dob == undefined) return res.status(400).json({ error: "President Date of Birth is not defined." }); + + // Verify Vice-President Information + if (req.body.vice_president_name == undefined) return res.status(400).json({ error: "Vice President Name is not defined." }); + if (req.body.vice_president_email == undefined) return res.status(400).json({ error: "Vice President Email is not defined." }); + if (req.body.vice_president_dob == undefined) return res.status(400).json({ error: "Vice President Date of Birth is not defined." }); + + // Verify Treasurer Information + if (req.body.treasurer_name == undefined) return res.status(400).json({ error: "Treasurer Name is not defined." }); + if (req.body.treasurer_email == undefined) return res.status(400).json({ error: "Treasurer Email is not defined." }); + if (req.body.treasurer_dob == undefined) return res.status(400).json({ error: "Treasurer Date of Birth is not defined." }); + + // Verify Administrator Information + if (req.body.administrator_name == undefined) return res.status(400).json({ error: "Administrator Name is not defined." }); + if (req.body.administrator_email == undefined) return res.status(400).json({ error: "Administrator Email is not defined." }); + if (req.body.administrator_position == undefined) return res.status(400).json({ error: "Administrator Position is not defined." }); + + // Check the Location const geocodeDataRaw = await fetch(`https://nominatim.openstreetmap.org/search.php?${serialize({ street: req.body.address_line, city: req.body.address_town, - state: req.body.address_state, postalcode: req.body.address_area_code, format: 'jsonv2' - })}`); + })}`); const geocodeDataJson = await geocodeDataRaw.json(); + + // Sort the possible locations (make sure the school is in front) const geocodeDataJsonSorted = geocodeDataJson.sort((a, b) => { const isGoodMatchA = a.type.toLowerCase().startsWith('school'); const isGoodMatchB = b.type.toLowerCase().startsWith('school'); @@ -35,19 +66,21 @@ export default async function handler(req, res) { return a.type.localeCompare(b.type); }); + // If the Address Cant be Found, It is Invalid if (geocodeDataJsonSorted.length == 0) return res.status(400).json({ error: "Invalid Address" }); const { lon: long, lat, display_name } = geocodeDataJsonSorted[0]; - const displayNameSections = display_name.split(', ') - const state = displayNameSections[displayNameSections.length - 3] - const city = displayNameSections[displayNameSections.length - 5] + // Grab the State and City + const state = req.body.state + const city = req.body.address_town + // Create a Charter const _charter = await Charter.create({ name: req.body.school_name, description: `${req.body.school_name} is a highschool located in ${city}, ${state}.`, long, lat, - icon: "", + // icon: "", data: JSON.stringify({ nces_id: req.body.school_nces_id, type: req.body.school_type, @@ -55,65 +88,73 @@ export default async function handler(req, res) { name: req.body.administrator_name, email: req.body.administrator_email, position: req.body.administrator_position - } + }, + website: req.body.website }), - public: false + verified: false }); - const presidentHashed = await bcrypt.hash(req.body.president_name, 10); - const vicePresidentHashed = await bcrypt.hash(req.body.vice_president_name, 10); - const treasurerHashed = await bcrypt.hash(req.body.treasurer_name, 10); + // Create Passwords for the President, Vice-President, and Treasurer + const presidentHashed = await bcrypt.hash(req.body.president_dob, 10); + const vicePresidentHashed = await bcrypt.hash(req.body.vice_president_dob, 10); + const treasurerHashed = await bcrypt.hash(req.body.treasurer_dob, 10); + // Create an Account for the President const president = await User.create({ full_name: req.body.president_name, username: req.body.president_name.toLowerCase().replace(/ /g, '-'), password: presidentHashed, - email: req.body.president_email, - charterId: _charter.id + email: req.body.president_email }) + // Create an Account for the Vice-president const vicePresident = await User.create({ full_name: req.body.vice_president_name, username: req.body.vice_president_name.toLowerCase().replace(/ /g, '-'), password: vicePresidentHashed, - email: req.body.vice_president_email, - charterId: _charter.id + email: req.body.vice_president_email }) + // Create an Account for the Treasurer const treasurer = await User.create({ full_name: req.body.treasurer_name, username: req.body.treasurer_name.toLowerCase().replace(/ /g, '-'), password: treasurerHashed, - email: req.body.treasurer_email, - charterId: _charter.id + email: req.body.treasurer_email }) - const announcement = await Announcement.create({ - title: `${req.body.school_name} officially launches an FPA Charter.`, - short: `Today, ${req.body.president_name} launched ${req.body.school_name}'s FPA Charter.`, - content: `Today, ${req.body.president_name} launched ${req.body.school_name}'s FPA Charter. ${req.body.president_name} worked with ${req.body.vice_president_name}, charter vice-president, and ${req.body.treasurer_name}, charter treasurer, to make it happen. To learn more, please contact ${req.body.president_name} or check out the charter's page.`, - userId: president.id, - charterId: _charter.id - }) + // Add the users to the charter + _charter.addUser(president); + _charter.addUser(vicePresident); + _charter.addUser(treasurer); // TODO: Charge their credit cards // TODO: Send out emails that tell them to reset their passwords - const session = await stripe.checkout.sessions.create({ - line_items: [ - { - price: 'price_1MmMs1HSFWYfEtuCacuyxVzJ', - quantity: 1, - }, - ], - mode: 'payment', - success_url: `http://localhost:3000/success`, - cancel_url: `http://localhost:3000/join`, - automatic_tax: { - enabled: true - }, + const pricePerShirt = 500; + const pricePerSticker = 7860 / 1000; + const shippingCost = 300; + const percentGain = 0.15; + + const stickerCount = 20; + const shirtCount = 8; + + const amount = Math.floor((shippingCost + pricePerShirt * shirtCount + pricePerSticker * stickerCount) * (1 + percentGain)) + + const paymentIntent = await stripe.paymentIntents.create({ + amount: amount, + currency: 'usd', + payment_method_types: ['card'], }); - res.redirect(303, session.url); + res.json({ + pathname: `/join/payment`, + query: { + id: _charter.id, + secret: paymentIntent.client_secret, + website: req.body.website, + amount: amount + } + }); } diff --git a/pages/api/school/banner.js b/pages/api/school/banner.js new file mode 100644 index 0000000..9bf9ebd --- /dev/null +++ b/pages/api/school/banner.js @@ -0,0 +1,61 @@ +// https://nces.ed.gov/surveys/pss/privateschoolsearch/school_list.asp?SchoolID=A0500866 + +import * as util from 'util' +import * as cheerio from 'cheerio'; +import { Charter } from "../../../server/models" + +import probe from 'probe-image-size'; + +export default async function handler(req, res) { + + if (req.query.website == undefined) { + + return res.status(500).json({ error: "No Website Provided." }); + + } + + const request = await fetch(req.query.website) + const html = await request.text() + const $ = cheerio.load(html); + const images = $("img") + + const output = [] + + for (let el of images) { + + console.log(el) + + if (el.attribs.src != undefined) { + + try { + + const url = new URL(el.attribs.src, req.query.website).toString() + let result = await probe(url); + console.log(result); + + output.push({ + url: url, + width: result.width + }) + + } catch (e) { + + + + } + + } + + if (el.attribs['data-image-sizes'] != undefined) { + + const json = JSON.parse(decodeURI(el.attribs['data-image-sizes'])) + + output.push(...json) + + } + + } + + res.send(output.sort(function(a, b){ return b.width - a.width })) + +} diff --git a/pages/api/school/verify.js b/pages/api/school/verify.js new file mode 100644 index 0000000..2b62dea --- /dev/null +++ b/pages/api/school/verify.js @@ -0,0 +1,114 @@ +// https://nces.ed.gov/surveys/pss/privateschoolsearch/school_list.asp?SchoolID=A0500866 + +import * as util from 'util' +import * as cheerio from 'cheerio'; +import { Charter } from "../../../server/models" + +function checkIfGradesValid(input) { + + const parts = input.split('-') + + const grades = { + "PK": 4, + "KG": 5, + "1": 6, + "2": 7, + "3": 8, + "4": 9, + "5": 10, + "6": 11, + "7": 12, + "8": 13, + "9": 14, + "10": 15, + "11": 16, + "12": 17, + } + + // Figure out lowest and highest grade + const min = grades[(parts[0] || "").trim()] || 0 + const max = grades[(parts[1] || "").trim()] || 0 + + // Check if grades are in highschool/ 8th grade + // if (min > 13) return false + if (max < 13) return false + return true + +} + +function beautifyName(name) { + + return name.split(' ').map(i => { + + if (i.length == 0) return '' + if (!i.includes('-')) return i[0].toUpperCase() + i.slice(1).toLowerCase() + + return i.split('-').map(j => { + + if (j.length == 0) return '' + return j[0].toUpperCase() + j.slice(1).toLowerCase() + + }).join('-') + + }).join(' ') + +} + +function calculateSchoolType(isPublic, name) { + + if (!isPublic) return 'private' + if (name.toUpperCase().includes('MAGNET')) return 'magnet' + if (name.toUpperCase().includes('CHARTER')) return 'charter' + return 'public' + +} + +export default async function handler(req, res) { + + if (req.query.name == undefined) { + + return res.status(500).json({ error: "No School Name Provided." }); + + } + + const request = await fetch(`https://nces.ed.gov/globallocator/index.asp?itemname=${req.query.name.replace(/\s/g, '+')}&School=1&PrivSchool=1&College=1`) + const html = await request.text() + const $ = cheerio.load(html); + const schools = $(".Institution > table[border!=0] > tbody > tr") + + const output = [] + schools.map(function(i, v){ + + const root = $(v).find('td:nth-child(2)').first() + const instData = $(v).find('td:nth-child(3)').first() + + const name = root.find('a').first() + const contents = root.contents() + const address = $(contents[2]) + const phone = $(contents[4]).text() + + const ncesAttr = name.attr('href') + const regex = /ID=[a-zA-Z0-9]+'\);/g + const match = ncesAttr.match(regex)[0] + const nces = match.slice(3, match.length - 3) + + const instGrades = instData.find('p').first().text() || instData.text() + const grades = instGrades.slice(8) // remove 'grades: ' + + const isPublic = instData.find('p').length == 0 + + output.push({ + id: nces, + name: beautifyName(name.text()), + address: beautifyName(address.text()), + phone: phone.slice(0, phone.length - 3).trim(), + grades: grades, + valid: checkIfGradesValid(grades), + type: calculateSchoolType(isPublic, name.text()) + }); + + }); + + res.send(output) + +} diff --git a/pages/charter/[id].js b/pages/charter/[id].js index cd5e843..46792a6 100644 --- a/pages/charter/[id].js +++ b/pages/charter/[id].js @@ -15,6 +15,8 @@ export default function CharterPage ({ charter }) { const [ search, setSearch ] = React.useState(""); + const data = JSON.stringify(charter.data) + return { charter.name } • The FPA Charter Program @@ -22,6 +24,7 @@ export default function CharterPage ({ charter }) {

{ charter.name }

{ charter.description }

+

{ data }

{ (charter.users || []).length > 0 ? <>

Members

diff --git a/pages/dashboard/index.js b/pages/dashboard/index.js index 83842ce..e6086f3 100644 --- a/pages/dashboard/index.js +++ b/pages/dashboard/index.js @@ -6,27 +6,39 @@ import Seo from "../../components/seo" import Announcement from "../../components/announcement" import Tabbar from '../../components/tabbar' +import { useEffect, useState } from 'react' export default function IndexPage ({ mainAnnouncement, localAnnouncement, globalAnnouncement }) { + const [ announcements, setAnnouncements ] = useState([ ]) + + useEffect(() => { + + (async () => { + + const req = await fetch('/api/announcement/local') + const data = await req.json() + + setAnnouncements(data) + + })() + + }, []) + return Dashboard • FPA Charter Program
-
{/*
diff --git a/pages/join.js b/pages/join.js deleted file mode 100644 index 11f8f68..0000000 --- a/pages/join.js +++ /dev/null @@ -1,196 +0,0 @@ -import Head from 'next/head' -import Link from 'next/link' - -import Cards from 'react-credit-cards'; - -import Layout from "../components/layout" -import Seo from "../components/seo" - -import Announcement from "../components/announcement" - -import 'react-credit-cards/es/styles-compiled.css'; - -// import { announcements } from "../server/models" - -function patternMatch({ input, template }) { - try { - let j = 0; - let plaintext = ""; - let countj = 0; - while (j < template.length) { - if (countj > input.length - 1) { - template = template.substring(0, j); - break; - } - - if (template[j] == input[j]) { - j++; - countj++; - continue; - } - - if (template[j] == "x") { - template = - template.substring(0, j) + input[countj] + template.substring(j + 1); - plaintext = plaintext + input[countj]; - countj++; - } - j++; - } - - return template; - } catch { - return ""; - } - } - -export default function IndexPage ({ announcements }) { - - return - - Join the Charter Program - -
-
-

Join the Charter Program

-

After filling out this form, we will review your application within three to five days.

-
-
-

School Information

-
- - -
-
-
- - -
-
- - -
-
-

- For the information above, please find the schools official name, school type, and NCES ID. This information should be obtained and verified from the administrator that signs the document. -

-
- - -
-
-
- - -
-
- - -
-
- - -
-
-

- Above is the area for the official address of the school. This is the address listed on websites, communications, and official documents. This will also be where the package is shipped to. -

-
- - -
-
-
- - -
-
- - -
-
-

- In the above section, please enter in the name, email, and position of the administrator that is helping the charter. This administrator should review the application and understand the responsibilities of the charter. -

-
-
-

Personal Information

-
-

Charter President

-
- - -
-
-
- - -
-
- - -
-
-

- The Charter President is the leader of the charter. They schedule meetings, work with the administration, and organize volunteer opportunities for their charter members. -

-
-
-

Charter Vice-President

-
- - -
-
-
- - -
-
- - -
-
-

- The Charter Vice-President is the second-in-command of the charter. They invite new members, find schools to partner with, and work to make their communities better. -

-
-
-

Charter Treasurer

-
- - -
-
-
- - -
-
- - -
-
-

- The Charter Treasurer is the third-in-command of the charter. They manage the finances, take notes during charter meetings, and organize the charter's online presence and marketing. -

-
-
-
- - -

- To start a charter, there is a one time payment of $50.00 USD. This charge is to cover the cost of the charter box that we ship to you once we approve your application. If your application doesn't get approved, you will be given a full refund. -

-

- By clicking submit, you agree to the Terms and Conditions of the FPA Charter System. -

-
-
-
-} diff --git a/pages/join/index.js b/pages/join/index.js new file mode 100644 index 0000000..814e969 --- /dev/null +++ b/pages/join/index.js @@ -0,0 +1,332 @@ +import * as React from 'react' + +import Head from 'next/head' +import Link from 'next/link' + +import { useRouter } from 'next/navigation' + +// import Cards from 'react-credit-cards'; + +import Layout from "../../components/layout" +import Seo from "../../components/seo" + +import Announcement from "../../components/announcement" + +import 'react-credit-cards/es/styles-compiled.css'; +import SchoolSelect from '../../components/schoolSelect'; + +// import { announcements } from "../server/models" + +export default function IndexPage ({ announcements }) { + + const router = useRouter() + + const [ schoolName, setSchoolName ] = React.useState('') + const [ manual, setManual ] = React.useState(false) + const [ schools, setSchools ] = React.useState([]) + const [ selectedIndex, setSelectedIndex ] = React.useState('') + + const [ addressLineOne, setAddressLineOne ] = React.useState('') + const [ addressCity, setAddressCity ] = React.useState('') + const [ addressState, setAddressState ] = React.useState('') + const [ addressZip, setAddressZip ] = React.useState('') + + const [ website, setWebsite ] = React.useState('') + const [ schoolType, setSchoolType ] = React.useState('') + + const [ presidentName, setPresidentName ] = React.useState('') + const [ presidentEmail, setPresidentEmail ] = React.useState('') + const [ presidentDOB, setPresidentDOB ] = React.useState('') + + const [ vicePresidentName, setVicePresidentName ] = React.useState('') + const [ vicePresidentEmail, setVicePresidentEmail ] = React.useState('') + const [ vicePresidentDOB, setVicePresidentDOB ] = React.useState('') + + const [ treasurerName, setTreasurerName ] = React.useState('') + const [ treasurerEmail, setTreasurerEmail ] = React.useState('') + const [ treasurerDOB, setTreasurerDOB ] = React.useState('') + + const [ administratorName, setAdministratorName ] = React.useState('') + const [ administratorEmail, setAdministratorEmail ] = React.useState('') + const [ administratorPosition, setAdministratorPosition ] = React.useState('') + + async function setFullAddress(address) { + + const approxAddy = address.split(',').filter((_, i) => i < 2).join(',') + + const req = await fetch(`https://nominatim.openstreetmap.org/search.php?q=${approxAddy}&format=json&addressdetails=1`) + const data = await req.json() + + const _address = (data[0] || {}).address || { } + + setAddressLineOne(_address.house_number + " " + _address.road) + setAddressCity(_address.town) + setAddressZip(_address.postcode) + setAddressState(_address.country) + + } + + async function setWebsiteFromName(name) { + + const req = await fetch(`https://autocomplete.clearbit.com/v1/companies/suggest?query=${name}`) + const data = await req.json() + + const unrefined = (data[0] || {}).domain || "" + const wwwUnrefined = unrefined.startsWith('www') ? unrefined : 'www.' + unrefined + const refined = wwwUnrefined.startsWith('http') ? wwwUnrefined : "https://" + wwwUnrefined + + setWebsite(unrefined != '' ? refined : '') + + } + + async function verifySchool() { + + const temp = schoolName; + + if (temp != schoolName) return; + + const req = await fetch(`/api/school/verify?name=${schoolName}`) + const data = await req.json() + + setSchools([ ...data + .filter(i => i.valid) + ]) + + } + + async function submitForm() { + + try { + + if (presidentName.trim() == "") return + + const req = await fetch(`/api/charter/join`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + school_name: schoolName, + school_nces_id: selectedIndex, + address_line: addressLineOne, + address_town: addressCity, + address_area_code: addressZip, + address_state: addressState, + website: website, + school_type: schoolType, + + president_name: presidentName, + president_email: presidentEmail, + president_dob: presidentDOB, + + vice_president_name: vicePresidentName, + vice_president_email: vicePresidentEmail, + vice_president_dob: vicePresidentDOB, + + treasurer_name: treasurerName, + treasurer_email: treasurerEmail, + treasurer_dob: treasurerDOB, + + administrator_name: administratorName, + administrator_email: administratorEmail, + administrator_position: administratorPosition + }) + }) + + if (!req.ok) { + + console.log(await req.text()) + // console.log(await req.json()) + + } + + const data = await req.json() + + router.push(data.location) + // redirect() + + } catch (error) { + + // redirect(data.location) + + console.log(error) + + } + + } + + return + + Join the Charter Program + +
{ submitForm(); e.preventDefault(); return false }} onKeyDown={(e) => { if (e.code === 13) { e.preventDefault(); } }}> +
+

Join the Charter Program

+

After filling out this form, we will review your application within three to five days.

+
+
+ { manual ?

School Information

: <> } +
+ +
+ { if (e.code === 13) { verifySchool(); } }} onChange={(e) => { setSchoolName(e.target.value) }} placeholder="Greens Farms Academy" className='border w-full rounded-xl p-2 text-xl' /> + +
+
+ { + !manual ? <> +
+ { + schools.map((i) => SchoolSelect({ data: i, selectedIndex, onClick: ({ name, id, address }) => { setSelectedIndex(id); setWebsiteFromName(name); setFullAddress(address); setSchoolName(name); setManual(true); verifySchool(); }})) + } +
+
+ {/* { + schools.length > 6 ?

{schools.length - 6} more results.

: <> + } */} + { +

Can't find your school? { setManual(true) }}>Enter its details manually.

+ } +
+ : <> + } + { manual ? <> +
+
+ + { setWebsite(e.target.value) }} name="school_website" placeholder="https://gfacademy.org" className='border w-full rounded-xl p-2 text-xl' /> +
+
+ + +
+
+

+ For the information above, please find the schools official name, school type, and website. This information should be obtained and verified from the administrator that signs the document. +

+
+ + { setAddressLineOne(e.target.value) }} placeholder="35 Beachside Ave" className='border w-full rounded-xl p-2 text-xl' /> +
+
+
+ + { setAddressCity(e.target.value) }} placeholder="Westport" className='border w-full rounded-xl p-2 text-xl' /> +
+
+ + { setAddressState(e.target.value) }} placeholder="Connecticut" className='border w-full rounded-xl p-2 text-xl' /> +
+
+ + { setAddressZip(e.target.value) }} placeholder="06880" className='border w-full rounded-xl p-2 text-xl' /> +
+
+

+ Above is the area for the official address of the school. This is the address listed on websites, communications, and official documents. This will also be where the care-package is shipped to. +

+ : <> + } +
+
+

Administration Information

+
+ + { setAdministratorName(e.target.value) }} placeholder="Michael Thompson" className='border w-full rounded-xl p-2 text-xl' /> +
+
+
+ + { setAdministratorEmail(e.target.value) }} type="email" placeholder="thompsonm@gfacademy.org" className='border w-full rounded-xl p-2 text-xl' /> +
+
+ + { setAdministratorPosition(e.target.value) }} placeholder="Head of Upper School" className='border w-full rounded-xl p-2 text-xl' /> +
+
+

+ In the above section, please enter in the name, email, and position of the administrator that is helping the charter. This administrator should review the application and understand the responsibilities of the charter. +

+
+
+

Personal Information

+
+

Charter President

+
+ + { setPresidentName(e.target.value) }} placeholder="William McGonagle" className='border w-full rounded-xl p-2 text-xl' /> +
+
+
+ + { setPresidentEmail(e.target.value) }} type="email" placeholder="william@placeholder.com" className='border w-full rounded-xl p-2 text-xl' /> +
+
+ + { setPresidentDOB(e.target.value) }} type="date" className='border w-full rounded-xl p-2 text-xl' /> +
+
+

+ The Charter President is the leader of the charter. They schedule meetings, work with the administration, and organize volunteer opportunities for their charter members. +

+
+
+

Charter Vice-President

+
+ + { setVicePresidentName(e.target.value) }} placeholder="Neil Chaudhari" className='border w-full rounded-xl p-2 text-xl' /> +
+
+
+ + { setVicePresidentEmail(e.target.value) }} type="email" placeholder="neil@placeholder.com" className='border w-full rounded-xl p-2 text-xl' /> +
+
+ + { setVicePresidentDOB(e.target.value) }} type="date" className='border w-full rounded-xl p-2 text-xl' /> +
+
+

+ The Charter Vice-President is the second-in-command of the charter. They invite new members, find schools to partner with, and work to make their communities better. +

+
+
+

Charter Treasurer

+
+ + { setTreasurerName(e.target.value) }} placeholder="Zoma Tessema" className='border w-full rounded-xl p-2 text-xl' /> +
+
+
+ + { setTreasurerEmail(e.target.value) }} type="email" placeholder="zoma@placeholder.com" className='border w-full rounded-xl p-2 text-xl' /> +
+
+ + { setTreasurerDOB(e.target.value) }} type="date" className='border w-full rounded-xl p-2 text-xl' /> +
+
+

+ The Charter Treasurer is the third-in-command of the charter. They manage the finances, take notes during charter meetings, and organize the charter's online presence and marketing. +

+
+
+
+ { submitForm() }} className="w-full rounded-lg p-4 font-black text-lg bg-green-300 hover:bg-green-400 hover:cursor-pointer" type="submit" value="Continue to Payment & Final Steps" /> + +

+ To start a charter, there is a one time payment of at least $50.00 USD. This charge is to cover the cost of the care package that we ship to your charter once we approve your application. If your application doesn't get approved, you will be given a full refund. +

+

+ By clicking submit, you agree to the Terms and Conditions of the FPA Charter System. +

+
+
+
+} diff --git a/pages/join/payment.js b/pages/join/payment.js new file mode 100644 index 0000000..ef971e0 --- /dev/null +++ b/pages/join/payment.js @@ -0,0 +1,138 @@ +import * as React from 'react' + +import Head from 'next/head' +import Link from 'next/link' + +import {Elements, PaymentElement} from '@stripe/react-stripe-js'; +import {loadStripe} from '@stripe/stripe-js'; + +import Layout from "../../components/layout" +import Seo from "../../components/seo" + +import Announcement from "../../components/announcement" + +import 'react-credit-cards/es/styles-compiled.css'; +import SchoolSelect from '../../components/schoolSelect'; +import PaymentForm from '../../components/paymentForm'; + +import { useSearchParams } from 'next/navigation' +// import { useRouter } from 'next/router' + +// import { announcements } from "../server/models" + +const stripePromise = loadStripe('pk_test_51LKrcgHSFWYfEtuCLLkBpluEBpnCKwLfG5kwEm6IOr0kdNgPj6xyHvWK7FYCeUELQ9aonDMAbPCKxbm8GsHwHAhW00F9lXuUAi'); + +export const dynamic = 'force-dynamic' +// export const revalidate = 0 + +export default function PaymentPage ({ }) { + + const searchParams = useSearchParams() + const website = searchParams.get('website') || "https://gfacademy.org" + const amount = searchParams.get('amount') || 0 + + const options = { + // passing the client secret obtained from the server + clientSecret: searchParams.get('secret') + }; + + const [ charterLogoDisplay, setCharterLogoDisplay ] = React.useState(`https://logo.clearbit.com/${website}`) + const [ charterBannerDisplay, setCharterBannerDisplay ] = React.useState("") + + React.useEffect(() => { + + (async () => { + + const req = await fetch(`http://localhost:3000/api/school/banner?website=${website}`) + const data = await req.json() + + if (data.length == 0) return + setCharterBannerDisplay(data[0].url) + + })(); + + }, []) + + // console.log(JSON.stringify(searchParams)) + + return + + Join the Charter Program + +
{ submitForm(); e.preventDefault(); return false }} onKeyDown={(e) => { if (e.code === 13) { e.preventDefault(); } }}> +
+

Join the Charter Program

+

After filling out this form, we will review your application within three to five days.

+
+
+

Charter Logo

+

Each charter needs a logo to represent themselves with, please upload a new logo if the current one doesn't fit the requirements. Logos provided by Clearbit.

+
+
+
+ + Charter Logo +
+
+
+

This image...

+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+

Charter Banner

+

This is the banner image that is shown on your charters homepage. You can upload a different one if you'd like, but for now, it is an image we took from your schools website.

+
+
+
+ + Charter Banner +
+
+
+
+
+

Charter Payment

+

+ We calculated the cost of starting your charter to be ${(amount / 100).toFixed(2)}. This is due to the location, + number of members, and cost of materials at this time. If you are unable to form a charter + because of economic conditions, please contact us. +

+ + + +
+ +
+ { submitForm() }} className="w-full rounded-lg p-4 font-black text-lg bg-green-300 hover:bg-green-400 hover:cursor-pointer" type="submit" value="Form my Charter!" /> +

+ By clicking submit, you once again agree to the Terms and Conditions of the FPA Charter System. +

+
+
+
+} diff --git a/server/models/charters.js b/server/models/charters.js index 8687afd..09c71ec 100644 --- a/server/models/charters.js +++ b/server/models/charters.js @@ -20,6 +20,7 @@ export default (sequelize, DataTypes) => { icon: DataTypes.TEXT, long: DataTypes.FLOAT, lat: DataTypes.FLOAT, + verified: DataTypes.BOOLEAN }, { sequelize, modelName: 'charters', diff --git a/server/models/index.js b/server/models/index.js index 5e78218..cddfefc 100644 --- a/server/models/index.js +++ b/server/models/index.js @@ -20,6 +20,13 @@ const Announcement = announcements(sequelize, Sequelize.DataTypes); User.belongsTo(Charter); Charter.hasMany(User); +// Charter - User (many to many so that it can be separated into other database) +const User_Profile = sequelize.define('User_Charter', { + role: Sequelize.INTEGER +}); +User.belongsToMany(Charter, { through: 'User_Charter' }); +Charter.belongsToMany(User, { through: 'User_Charter' }); + // User - Announcement User.hasMany(Announcement); Announcement.belongsTo(User); @@ -28,8 +35,10 @@ Announcement.belongsTo(User); Charter.hasMany(Announcement); Announcement.belongsTo(Charter); -export { User, Charter, Announcement }; +export { User, Charter, Announcement, User_Profile }; export const initDB = async () => { await sequelize.sync({ alter: true }); -}; \ No newline at end of file +}; + +initDB(); \ No newline at end of file