diff --git a/app.js b/app.js index e6187cd..d43a27d 100644 --- a/app.js +++ b/app.js @@ -16,12 +16,14 @@ const session = require("express-session"); const mongoose = require("mongoose"); const MongoStore = require("connect-mongo")(session); const connectDB = require("./config/db"); +const flash = require('connect-flash'); // Load config dotenv.config({ path: "./config/config.env" }); // Passport config require("./config/passport")(passport); +require("./config/passportLocal")(passport); // DB Connected connectDB(); @@ -75,9 +77,15 @@ app.use( app.use(passport.initialize()); app.use(passport.session()); +// Connect Flash +app.use(flash()) + // Set Global variables app.use(function (req, res, next) { res.locals.user = req.user || null; + res.locals.success_msg = req.flash('success_msg'); + res.locals.error_msg = req.flash('error_msg'); + res.locals.error = req.flash('error'); next(); }); @@ -87,6 +95,7 @@ app.use(express.static(path.join(__dirname, "/public"))); // Routes app.use("/", require("./routes/api/index")); app.use("/auth", require("./routes/api/auth")); +app.use("/user", require("./routes/api/user")); app.use("/portfolio", require("./routes/api/portfolio")); app.use("/market", require("./routes/api/market")); app.use("/view", require("./routes/api/view")); diff --git a/config/passportLocal.js b/config/passportLocal.js new file mode 100644 index 0000000..cc0da44 --- /dev/null +++ b/config/passportLocal.js @@ -0,0 +1,40 @@ +const LocalStrategy = require('passport-local').Strategy; +const bcrypt = require('bcryptjs'); + +// Load User model +const User = require('../models/User'); + +module.exports = function (passport) { + passport.use( + new LocalStrategy({ usernameField: 'email' }, (email, password, done) => { + // Match User + User.findOne({ + email: email + }).then(user => { + if (!user) { + return done(null, false, { message: 'That email is not registered' }) + } + + // Match user Password + bcrypt.compare(password, user.password, (err, isMatch) => { + if (err) throw err; + if (isMatch) { + done(null, user); + } else { + return done(null, false, { message: 'That password is incorrect' }) + } + }) + }) + }) + ) + + passport.serializeUser((user, done) => { + done(null, user.id); + }); + + passport.deserializeUser((id, done) => { + User.findById(id, (err, user) => { + done(err, user); + }); + }); +} \ No newline at end of file diff --git a/middleware/auth.js b/middleware/auth.js index c02b88c..bb38a58 100644 --- a/middleware/auth.js +++ b/middleware/auth.js @@ -5,7 +5,8 @@ module.exports = { if (req.isAuthenticated()) { return next(); } else { - res.redirect("/"); + req.flash('error_msg', 'Password or Email does not match'); + res.redirect('/'); } }, ensureGuest: function (req, res, next) { diff --git a/package-lock.json b/package-lock.json index ef450b5..6832b0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -230,6 +230,11 @@ } } }, + "bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" + }, "binary-extensions": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", @@ -722,6 +727,11 @@ "xdg-basedir": "^4.0.0" } }, + "connect-flash": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz", + "integrity": "sha1-2GMPJtlaf4UfmVax6MxnMvO2qjA=" + }, "connect-mongo": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-3.2.0.tgz", @@ -5561,6 +5571,14 @@ "passport-oauth2": "1.x.x" } }, + "passport-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", + "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=", + "requires": { + "passport-strategy": "1.x.x" + } + }, "passport-oauth2": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.5.0.tgz", diff --git a/package.json b/package.json index 422c070..39179b1 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,9 @@ "alphavantage": "^2.1.0", "autoprefixer": "^9.8.6", "axios": "^0.19.2", + "bcryptjs": "^2.4.3", "concurrently": "^5.3.0", + "connect-flash": "^0.1.1", "connect-mongo": "^3.2.0", "cross-env": "^7.0.2", "date-fns": "^2.15.0", @@ -48,6 +50,7 @@ "npm": "^6.14.7", "passport": "^0.4.1", "passport-google-oauth20": "^2.0.0", + "passport-local": "^1.0.0", "postcss-cli": "^7.1.1", "tailwindcss": "^1.6.2", "uuid": "^8.3.0" diff --git a/public/css/style.css b/public/css/style.css index 3f77280..d9bf352 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -1 +1 @@ -/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}[hidden],template{display:none}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}button{background-color:transparent;background-image:none}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}fieldset,ol,ul{margin:0;padding:0}ol,ul{list-style:none}html{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}*,:after,:before{box-sizing:border-box;border:0 solid #e2e8f0}hr{border-top-width:1px}img{border-style:solid}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#a0aec0}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#a0aec0}input::-ms-input-placeholder,textarea::-ms-input-placeholder{color:#a0aec0}input::placeholder,textarea::placeholder{color:#a0aec0}[role=button],button{cursor:pointer}table{border-collapse:collapse}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}button,input,optgroup,select,textarea{padding:0;line-height:inherit;color:inherit}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}.space-y-5>:not(template)~:not(template){--space-y-reverse:0;margin-top:calc(1.25rem*(1 - var(--space-y-reverse)));margin-bottom:calc(1.25rem*var(--space-y-reverse))}.space-y-10>:not(template)~:not(template){--space-y-reverse:0;margin-top:calc(2.5rem*(1 - var(--space-y-reverse)));margin-bottom:calc(2.5rem*var(--space-y-reverse))}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.bg-transparent{background-color:transparent}.bg-white{--bg-opacity:1;background-color:#fff;background-color:rgba(255,255,255,var(--bg-opacity))}.bg-gray-100{--bg-opacity:1;background-color:#f7fafc;background-color:rgba(247,250,252,var(--bg-opacity))}.bg-gray-200{--bg-opacity:1;background-color:#edf2f7;background-color:rgba(237,242,247,var(--bg-opacity))}.bg-gray-300{--bg-opacity:1;background-color:#e2e8f0;background-color:rgba(226,232,240,var(--bg-opacity))}.bg-gray-700{--bg-opacity:1;background-color:#4a5568;background-color:rgba(74,85,104,var(--bg-opacity))}.bg-yellow-500{--bg-opacity:1;background-color:#ecc94b;background-color:rgba(236,201,75,var(--bg-opacity))}.bg-green-200{--bg-opacity:1;background-color:#c6f6d5;background-color:rgba(198,246,213,var(--bg-opacity))}.bg-teal-500{--bg-opacity:1;background-color:#38b2ac;background-color:rgba(56,178,172,var(--bg-opacity))}.bg-blue-400{--bg-opacity:1;background-color:#63b3ed;background-color:rgba(99,179,237,var(--bg-opacity))}.bg-blue-500{--bg-opacity:1;background-color:#4299e1;background-color:rgba(66,153,225,var(--bg-opacity))}.bg-blue-600{--bg-opacity:1;background-color:#3182ce;background-color:rgba(49,130,206,var(--bg-opacity))}.bg-blue-800{--bg-opacity:1;background-color:#2c5282;background-color:rgba(44,82,130,var(--bg-opacity))}.bg-secondary-100{--bg-opacity:1;background-color:#e2e2d5;background-color:rgba(226,226,213,var(--bg-opacity))}.bg-tempc-400{--bg-opacity:1;background-color:#266fea;background-color:rgba(38,111,234,var(--bg-opacity))}.bg-tempc-500{--bg-opacity:1;background-color:#41d7aa;background-color:rgba(65,215,170,var(--bg-opacity))}.hover\:bg-white:hover{--bg-opacity:1;background-color:#fff;background-color:rgba(255,255,255,var(--bg-opacity))}.hover\:bg-gray-200:hover{--bg-opacity:1;background-color:#edf2f7;background-color:rgba(237,242,247,var(--bg-opacity))}.hover\:bg-red-400:hover{--bg-opacity:1;background-color:#fc8181;background-color:rgba(252,129,129,var(--bg-opacity))}.hover\:bg-yellow-600:hover{--bg-opacity:1;background-color:#d69e2e;background-color:rgba(214,158,46,var(--bg-opacity))}.hover\:bg-green-400:hover{--bg-opacity:1;background-color:#68d391;background-color:rgba(104,211,145,var(--bg-opacity))}.hover\:bg-teal-500:hover{--bg-opacity:1;background-color:#38b2ac;background-color:rgba(56,178,172,var(--bg-opacity))}.hover\:bg-blue-400:hover{--bg-opacity:1;background-color:#63b3ed;background-color:rgba(99,179,237,var(--bg-opacity))}.hover\:bg-blue-500:hover{--bg-opacity:1;background-color:#4299e1;background-color:rgba(66,153,225,var(--bg-opacity))}.hover\:bg-blue-700:hover{--bg-opacity:1;background-color:#2b6cb0;background-color:rgba(43,108,176,var(--bg-opacity))}.hover\:bg-indigo-700:hover{--bg-opacity:1;background-color:#4c51bf;background-color:rgba(76,81,191,var(--bg-opacity))}.focus\:bg-gray-200:focus{--bg-opacity:1;background-color:#edf2f7;background-color:rgba(237,242,247,var(--bg-opacity))}.hover\:bg-opacity-50:hover{--bg-opacity:0.5}.border-gray-100{--border-opacity:1;border-color:#f7fafc;border-color:rgba(247,250,252,var(--border-opacity))}.border-gray-200{--border-opacity:1;border-color:#edf2f7;border-color:rgba(237,242,247,var(--border-opacity))}.border-green-400{--border-opacity:1;border-color:#68d391;border-color:rgba(104,211,145,var(--border-opacity))}.border-teal-500{--border-opacity:1;border-color:#38b2ac;border-color:rgba(56,178,172,var(--border-opacity))}.border-blue-500{--border-opacity:1;border-color:#4299e1;border-color:rgba(66,153,225,var(--border-opacity))}.border-blue-600{--border-opacity:1;border-color:#3182ce;border-color:rgba(49,130,206,var(--border-opacity))}.border-purple-700{--border-opacity:1;border-color:#6b46c1;border-color:rgba(107,70,193,var(--border-opacity))}.hover\:border-green-400:hover{--border-opacity:1;border-color:#68d391;border-color:rgba(104,211,145,var(--border-opacity))}.hover\:border-blue-600:hover{--border-opacity:1;border-color:#3182ce;border-color:rgba(49,130,206,var(--border-opacity))}.rounded-sm{border-radius:.125rem}.rounded{border-radius:.25rem}.rounded-lg{border-radius:.5rem}.rounded-full{border-radius:9999px}.rounded-b-lg{border-bottom-right-radius:.5rem;border-bottom-left-radius:.5rem}.border-4{border-width:4px}.border-8{border-width:8px}.border{border-width:1px}.border-b-2{border-bottom-width:2px}.border-l-8{border-left-width:8px}.border-b{border-bottom-width:1px}.cursor-pointer{cursor:pointer}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.flex-grow{flex-grow:1}.flex-shrink-0{flex-shrink:0}.font-medium{font-weight:500}.font-semibold{font-weight:600}.font-bold{font-weight:700}.font-extrabold{font-weight:800}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-8{height:2rem}.h-10{height:2.5rem}.h-16{height:4rem}.h-20{height:5rem}.h-64{height:16rem}.h-auto{height:auto}.h-full{height:100%}.text-xs{font-size:.75rem}.text-sm{font-size:.875rem}.text-base{font-size:1rem}.text-lg{font-size:1.125rem}.text-xl{font-size:1.25rem}.text-2xl{font-size:1.5rem}.text-3xl{font-size:1.875rem}.text-4xl{font-size:2.25rem}.text-5xl{font-size:3rem}.text-6xl{font-size:4rem}.leading-none{line-height:1}.leading-tight{line-height:1.25}.leading-normal{line-height:1.5}.m-4{margin:1rem}.m-8{margin:2rem}.-m-4{margin:-1rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.my-4{margin-top:1rem;margin-bottom:1rem}.my-8{margin-top:2rem;margin-bottom:2rem}.mx-auto{margin-left:auto;margin-right:auto}.-mx-8{margin-left:-2rem;margin-right:-2rem}.ml-0{margin-left:0}.mr-1{margin-right:.25rem}.mb-1{margin-bottom:.25rem}.mt-2{margin-top:.5rem}.mr-2{margin-right:.5rem}.mb-2{margin-bottom:.5rem}.ml-2{margin-left:.5rem}.mt-3{margin-top:.75rem}.mb-3{margin-bottom:.75rem}.ml-3{margin-left:.75rem}.mt-4{margin-top:1rem}.mb-4{margin-bottom:1rem}.ml-4{margin-left:1rem}.mt-6{margin-top:1.5rem}.mr-6{margin-right:1.5rem}.mt-8{margin-top:2rem}.mb-8{margin-bottom:2rem}.mb-10{margin-bottom:2.5rem}.mt-12{margin-top:3rem}.max-w-xs{max-width:20rem}.max-w-sm{max-width:24rem}.max-w-4xl{max-width:56rem}.max-w-6xl{max-width:72rem}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.min-w-0{min-width:0}.object-cover{-o-object-fit:cover;object-fit:cover}.focus\:outline-none:focus,.outline-none{outline:0}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.p-0{padding:0}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.px-4{padding-left:1rem;padding-right:1rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-10{padding-top:2.5rem;padding-bottom:2.5rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.py-16{padding-top:4rem;padding-bottom:4rem}.pt-2{padding-top:.5rem}.pb-2{padding-bottom:.5rem}.pl-2{padding-left:.5rem}.pr-4{padding-right:1rem}.pb-4{padding-bottom:1rem}.pt-6{padding-top:1.5rem}.pb-8{padding-bottom:2rem}.pt-10{padding-top:2.5rem}.pr-10{padding-right:2.5rem}.pl-10{padding-left:2.5rem}.pt-20{padding-top:5rem}.pt-32{padding-top:8rem}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{top:0;right:0;bottom:0;left:0}.top-0{top:0}.right-0{right:0}.bottom-0{bottom:0}.left-0{left:0}.shadow{box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)}.shadow-md{box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06)}.shadow-lg{box-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -2px rgba(0,0,0,.05)}.shadow-2xl{box-shadow:0 25px 50px -12px rgba(0,0,0,.25)}.hover\:shadow-inner:hover{box-shadow:inset 0 2px 4px 0 rgba(0,0,0,.06)}.focus\:shadow-outline:focus{box-shadow:0 0 0 3px rgba(66,153,225,.5)}.fill-current{fill:currentColor}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.text-black{--text-opacity:1;color:#000;color:rgba(0,0,0,var(--text-opacity))}.text-white{--text-opacity:1;color:#fff;color:rgba(255,255,255,var(--text-opacity))}.text-gray-500{--text-opacity:1;color:#a0aec0;color:rgba(160,174,192,var(--text-opacity))}.text-gray-600{--text-opacity:1;color:#718096;color:rgba(113,128,150,var(--text-opacity))}.text-gray-700{--text-opacity:1;color:#4a5568;color:rgba(74,85,104,var(--text-opacity))}.text-gray-800{--text-opacity:1;color:#2d3748;color:rgba(45,55,72,var(--text-opacity))}.text-gray-900{--text-opacity:1;color:#1a202c;color:rgba(26,32,44,var(--text-opacity))}.text-green-900{--text-opacity:1;color:#22543d;color:rgba(34,84,61,var(--text-opacity))}.text-teal-500{--text-opacity:1;color:#38b2ac;color:rgba(56,178,172,var(--text-opacity))}.text-secondary-200{--text-opacity:1;color:#888883;color:rgba(136,136,131,var(--text-opacity))}.text-tempc-200{--text-opacity:1;color:#e93434;color:rgba(233,52,52,var(--text-opacity))}.text-tempc-400{--text-opacity:1;color:#266fea;color:rgba(38,111,234,var(--text-opacity))}.text-tempc-500{--text-opacity:1;color:#41d7aa;color:rgba(65,215,170,var(--text-opacity))}.hover\:text-black:hover{--text-opacity:1;color:#000;color:rgba(0,0,0,var(--text-opacity))}.hover\:text-white:hover{--text-opacity:1;color:#fff;color:rgba(255,255,255,var(--text-opacity))}.hover\:text-gray-900:hover{--text-opacity:1;color:#1a202c;color:rgba(26,32,44,var(--text-opacity))}.focus\:text-black:focus{--text-opacity:1;color:#000;color:rgba(0,0,0,var(--text-opacity))}.focus\:text-gray-900:focus{--text-opacity:1;color:#1a202c;color:rgba(26,32,44,var(--text-opacity))}.uppercase{text-transform:uppercase}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.tracking-widest{letter-spacing:.1em}.whitespace-no-wrap{white-space:nowrap}.break-words{overflow-wrap:break-word}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-10{width:2.5rem}.w-16{width:4rem}.w-20{width:5rem}.w-24{width:6rem}.w-32{width:8rem}.w-auto{width:auto}.w-2\/3{width:66.666667%}.w-2\/5{width:40%}.w-full{width:100%}.w-screen{width:100vw}.gap-4{grid-gap:1rem;gap:1rem}.gap-8{grid-gap:2rem;gap:2rem}.gap-10{grid-gap:2.5rem;gap:2.5rem}.grid-flow-col{grid-auto-flow:column}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.transform{--transform-translate-x:0;--transform-translate-y:0;--transform-rotate:0;--transform-skew-x:0;--transform-skew-y:0;--transform-scale-x:1;--transform-scale-y:1;transform:translateX(var(--transform-translate-x)) translateY(var(--transform-translate-y)) rotate(var(--transform-rotate)) skewX(var(--transform-skew-x)) skewY(var(--transform-skew-y)) scaleX(var(--transform-scale-x)) scaleY(var(--transform-scale-y))}.hover\:scale-125:hover{--transform-scale-x:1.25;--transform-scale-y:1.25}.transition{transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.duration-500{transition-duration:.5s}@-webkit-keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@-webkit-keyframes ping{0%{transform:scale(1);opacity:1}75%,to{transform:scale(2);opacity:0}}@keyframes ping{0%{transform:scale(1);opacity:1}75%,to{transform:scale(2);opacity:0}}@-webkit-keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}@-webkit-keyframes bounce{0%,to{transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:translateY(0);-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1)}}@keyframes bounce{0%,to{transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:translateY(0);-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1)}}body,html{margin:0;padding:0;width:100%;height:100%;overflow-x:hidden;box-sizing:border-box;background-color:#e2e8f0}.chartjs-container{position:relative;margin:auto;height:75vh;width:75vw}.symbolicon{font-size:72px;background:-webkit-linear-gradient(#eee,#333);background-clip:text;-webkit-background-clip:text;-webkit-text-fill-color:transparent}@media (min-width:640px){.sm\:container{width:100%;max-width:640px}@media (min-width:768px){.sm\:container{max-width:768px}}@media (min-width:1024px){.sm\:container{max-width:1024px}}@media (min-width:1280px){.sm\:container{max-width:1280px}}.sm\:h-20{height:5rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:px-8{padding-left:2rem;padding-right:2rem}}@media (min-width:768px){.md\:container{width:100%}@media (min-width:640px){.md\:container{max-width:640px}}@media (min-width:768px){.md\:container{max-width:768px}}@media (min-width:1024px){.md\:container{max-width:1024px}}@media (min-width:1280px){.md\:container{max-width:1280px}}.md\:block{display:block}.md\:inline-block{display:inline-block}.md\:flex{display:flex}.md\:hidden{display:none}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:justify-center{justify-content:center}.md\:h-screen{height:100vh}.md\:text-sm{font-size:.875rem}.md\:text-xl{font-size:1.25rem}.md\:text-2xl{font-size:1.5rem}.md\:text-3xl{font-size:1.875rem}.md\:mx-auto{margin-left:auto;margin-right:auto}.md\:ml-56{margin-left:14rem}.md\:ml-64{margin-left:16rem}.md\:ml-auto{margin-left:auto}.md\:px-2{padding-left:.5rem;padding-right:.5rem}.md\:px-8{padding-left:2rem;padding-right:2rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:py-12{padding-top:3rem;padding-bottom:3rem}.md\:pb-0{padding-bottom:0}.md\:fixed{position:fixed}.md\:top-0{top:0}.md\:left-0{left:0}.md\:break-all{word-break:break-all}.md\:w-56{width:14rem}.md\:w-1\/2{width:50%}.md\:w-4\/5{width:80%}.md\:z-50{z-index:50}.md\:gap-4{grid-gap:1rem;gap:1rem}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:col-span-4{grid-column:span 4/span 4}.md\:grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.md\:grid-rows-3{grid-template-rows:repeat(3,minmax(0,1fr))}}@media (min-width:1024px){.lg\:container{width:100%}@media (min-width:640px){.lg\:container{max-width:640px}}@media (min-width:768px){.lg\:container{max-width:768px}}@media (min-width:1024px){.lg\:container{max-width:1024px}}@media (min-width:1280px){.lg\:container{max-width:1280px}}.lg\:block{display:block}.lg\:grid{display:grid}.lg\:items-start{align-items:flex-start}.lg\:items-center{align-items:center}.lg\:justify-center{justify-content:center}.lg\:text-sm{font-size:.875rem}.lg\:text-base{font-size:1rem}.lg\:text-lg{font-size:1.125rem}.lg\:text-3xl{font-size:1.875rem}.lg\:text-4xl{font-size:2.25rem}.lg\:text-6xl{font-size:4rem}.lg\:ml-64{margin-left:16rem}.lg\:p-6{padding:1.5rem}.lg\:text-center{text-align:center}.lg\:w-64{width:16rem}.lg\:w-1\/4{width:25%}.lg\:w-3\/5{width:60%}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.lg\:grid-rows-3{grid-template-rows:repeat(3,minmax(0,1fr))}.lg\:row-span-2{grid-row:span 2/span 2}}@media (min-width:1280px){.xl\:container{width:100%}@media (min-width:640px){.xl\:container{max-width:640px}}@media (min-width:768px){.xl\:container{max-width:768px}}@media (min-width:1024px){.xl\:container{max-width:1024px}}@media (min-width:1280px){.xl\:container{max-width:1280px}}.xl\:text-2xl{font-size:1.5rem}} \ No newline at end of file +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}[hidden],template{display:none}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}button{background-color:transparent;background-image:none}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}fieldset,ol,ul{margin:0;padding:0}ol,ul{list-style:none}html{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}*,:after,:before{box-sizing:border-box;border:0 solid #e2e8f0}hr{border-top-width:1px}img{border-style:solid}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#a0aec0}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#a0aec0}input::-ms-input-placeholder,textarea::-ms-input-placeholder{color:#a0aec0}input::placeholder,textarea::placeholder{color:#a0aec0}[role=button],button{cursor:pointer}table{border-collapse:collapse}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}button,input,optgroup,select,textarea{padding:0;line-height:inherit;color:inherit}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}body,html{margin:0;padding:0;width:100%;height:100%;overflow-x:hidden;box-sizing:border-box;background-color:#e2e8f0}.chartjs-container{position:relative;margin:auto;height:75vh;width:75vw}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}.symbolicon{font-size:72px;background:-webkit-linear-gradient(#eee,#333);background-clip:text;-webkit-background-clip:text;-webkit-text-fill-color:transparent}.min-w{min-width:400px}@media (max-width:400px){.min-w{min-width:300px}}.space-y-5>:not(template)~:not(template){--space-y-reverse:0;margin-top:calc(1.25rem*(1 - var(--space-y-reverse)));margin-bottom:calc(1.25rem*var(--space-y-reverse))}.space-y-10>:not(template)~:not(template){--space-y-reverse:0;margin-top:calc(2.5rem*(1 - var(--space-y-reverse)));margin-bottom:calc(2.5rem*var(--space-y-reverse))}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.bg-transparent{background-color:transparent}.bg-white{--bg-opacity:1;background-color:#fff;background-color:rgba(255,255,255,var(--bg-opacity))}.bg-gray-100{--bg-opacity:1;background-color:#f7fafc;background-color:rgba(247,250,252,var(--bg-opacity))}.bg-gray-200{--bg-opacity:1;background-color:#edf2f7;background-color:rgba(237,242,247,var(--bg-opacity))}.bg-gray-300{--bg-opacity:1;background-color:#e2e8f0;background-color:rgba(226,232,240,var(--bg-opacity))}.bg-gray-700{--bg-opacity:1;background-color:#4a5568;background-color:rgba(74,85,104,var(--bg-opacity))}.bg-gray-900{--bg-opacity:1;background-color:#1a202c;background-color:rgba(26,32,44,var(--bg-opacity))}.bg-red-200{--bg-opacity:1;background-color:#fed7d7;background-color:rgba(254,215,215,var(--bg-opacity))}.bg-red-900{--bg-opacity:1;background-color:#742a2a;background-color:rgba(116,42,42,var(--bg-opacity))}.bg-yellow-200{--bg-opacity:1;background-color:#fefcbf;background-color:rgba(254,252,191,var(--bg-opacity))}.bg-yellow-500{--bg-opacity:1;background-color:#ecc94b;background-color:rgba(236,201,75,var(--bg-opacity))}.bg-green-200{--bg-opacity:1;background-color:#c6f6d5;background-color:rgba(198,246,213,var(--bg-opacity))}.bg-teal-500{--bg-opacity:1;background-color:#38b2ac;background-color:rgba(56,178,172,var(--bg-opacity))}.bg-blue-400{--bg-opacity:1;background-color:#63b3ed;background-color:rgba(99,179,237,var(--bg-opacity))}.bg-blue-500{--bg-opacity:1;background-color:#4299e1;background-color:rgba(66,153,225,var(--bg-opacity))}.bg-blue-600{--bg-opacity:1;background-color:#3182ce;background-color:rgba(49,130,206,var(--bg-opacity))}.bg-blue-800{--bg-opacity:1;background-color:#2c5282;background-color:rgba(44,82,130,var(--bg-opacity))}.bg-secondary-100{--bg-opacity:1;background-color:#e2e2d5;background-color:rgba(226,226,213,var(--bg-opacity))}.bg-tempc-400{--bg-opacity:1;background-color:#266fea;background-color:rgba(38,111,234,var(--bg-opacity))}.bg-tempc-500{--bg-opacity:1;background-color:#41d7aa;background-color:rgba(65,215,170,var(--bg-opacity))}.hover\:bg-white:hover{--bg-opacity:1;background-color:#fff;background-color:rgba(255,255,255,var(--bg-opacity))}.hover\:bg-gray-200:hover{--bg-opacity:1;background-color:#edf2f7;background-color:rgba(237,242,247,var(--bg-opacity))}.hover\:bg-red-400:hover{--bg-opacity:1;background-color:#fc8181;background-color:rgba(252,129,129,var(--bg-opacity))}.hover\:bg-yellow-600:hover{--bg-opacity:1;background-color:#d69e2e;background-color:rgba(214,158,46,var(--bg-opacity))}.hover\:bg-green-400:hover{--bg-opacity:1;background-color:#68d391;background-color:rgba(104,211,145,var(--bg-opacity))}.hover\:bg-teal-500:hover{--bg-opacity:1;background-color:#38b2ac;background-color:rgba(56,178,172,var(--bg-opacity))}.hover\:bg-blue-400:hover{--bg-opacity:1;background-color:#63b3ed;background-color:rgba(99,179,237,var(--bg-opacity))}.hover\:bg-blue-500:hover{--bg-opacity:1;background-color:#4299e1;background-color:rgba(66,153,225,var(--bg-opacity))}.hover\:bg-blue-700:hover{--bg-opacity:1;background-color:#2b6cb0;background-color:rgba(43,108,176,var(--bg-opacity))}.hover\:bg-indigo-700:hover{--bg-opacity:1;background-color:#4c51bf;background-color:rgba(76,81,191,var(--bg-opacity))}.focus\:bg-gray-200:focus{--bg-opacity:1;background-color:#edf2f7;background-color:rgba(237,242,247,var(--bg-opacity))}.hover\:bg-opacity-50:hover{--bg-opacity:0.5}.border-gray-100{--border-opacity:1;border-color:#f7fafc;border-color:rgba(247,250,252,var(--border-opacity))}.border-gray-200{--border-opacity:1;border-color:#edf2f7;border-color:rgba(237,242,247,var(--border-opacity))}.border-gray-400{--border-opacity:1;border-color:#cbd5e0;border-color:rgba(203,213,224,var(--border-opacity))}.border-red-600{--border-opacity:1;border-color:#e53e3e;border-color:rgba(229,62,62,var(--border-opacity))}.border-yellow-600{--border-opacity:1;border-color:#d69e2e;border-color:rgba(214,158,46,var(--border-opacity))}.border-green-400{--border-opacity:1;border-color:#68d391;border-color:rgba(104,211,145,var(--border-opacity))}.border-green-600{--border-opacity:1;border-color:#38a169;border-color:rgba(56,161,105,var(--border-opacity))}.border-teal-500{--border-opacity:1;border-color:#38b2ac;border-color:rgba(56,178,172,var(--border-opacity))}.border-blue-500{--border-opacity:1;border-color:#4299e1;border-color:rgba(66,153,225,var(--border-opacity))}.border-blue-600{--border-opacity:1;border-color:#3182ce;border-color:rgba(49,130,206,var(--border-opacity))}.border-purple-700{--border-opacity:1;border-color:#6b46c1;border-color:rgba(107,70,193,var(--border-opacity))}.hover\:border-green-400:hover{--border-opacity:1;border-color:#68d391;border-color:rgba(104,211,145,var(--border-opacity))}.hover\:border-blue-600:hover{--border-opacity:1;border-color:#3182ce;border-color:rgba(49,130,206,var(--border-opacity))}.rounded-sm{border-radius:.125rem}.rounded{border-radius:.25rem}.rounded-lg{border-radius:.5rem}.rounded-full{border-radius:9999px}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.rounded-b-lg{border-bottom-right-radius:.5rem;border-bottom-left-radius:.5rem}.border-4{border-width:4px}.border-8{border-width:8px}.border{border-width:1px}.border-b-2{border-bottom-width:2px}.border-l-8{border-left-width:8px}.border-b{border-bottom-width:1px}.cursor-pointer{cursor:pointer}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.flex-auto{flex:1 1 auto}.flex-grow{flex-grow:1}.flex-shrink-0{flex-shrink:0}.font-normal{font-weight:400}.font-medium{font-weight:500}.font-semibold{font-weight:600}.font-bold{font-weight:700}.font-extrabold{font-weight:800}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-8{height:2rem}.h-10{height:2.5rem}.h-16{height:4rem}.h-20{height:5rem}.h-64{height:16rem}.h-auto{height:auto}.h-full{height:100%}.text-xs{font-size:.75rem}.text-sm{font-size:.875rem}.text-base{font-size:1rem}.text-lg{font-size:1.125rem}.text-xl{font-size:1.25rem}.text-2xl{font-size:1.5rem}.text-3xl{font-size:1.875rem}.text-4xl{font-size:2.25rem}.text-5xl{font-size:3rem}.text-6xl{font-size:4rem}.leading-none{line-height:1}.leading-tight{line-height:1.25}.leading-normal{line-height:1.5}.m-4{margin:1rem}.m-8{margin:2rem}.-m-4{margin:-1rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.my-4{margin-top:1rem;margin-bottom:1rem}.my-6{margin-top:1.5rem;margin-bottom:1.5rem}.my-8{margin-top:2rem;margin-bottom:2rem}.mx-auto{margin-left:auto;margin-right:auto}.-mx-8{margin-left:-2rem;margin-right:-2rem}.mb-0{margin-bottom:0}.ml-0{margin-left:0}.mt-1{margin-top:.25rem}.mr-1{margin-right:.25rem}.mb-1{margin-bottom:.25rem}.ml-1{margin-left:.25rem}.mt-2{margin-top:.5rem}.mr-2{margin-right:.5rem}.mb-2{margin-bottom:.5rem}.ml-2{margin-left:.5rem}.mt-3{margin-top:.75rem}.mb-3{margin-bottom:.75rem}.ml-3{margin-left:.75rem}.mt-4{margin-top:1rem}.mb-4{margin-bottom:1rem}.ml-4{margin-left:1rem}.mt-6{margin-top:1.5rem}.mr-6{margin-right:1.5rem}.mb-6{margin-bottom:1.5rem}.mt-8{margin-top:2rem}.mb-8{margin-bottom:2rem}.mb-10{margin-bottom:2.5rem}.mt-12{margin-top:3rem}.max-w-xs{max-width:20rem}.max-w-sm{max-width:24rem}.max-w-4xl{max-width:56rem}.max-w-6xl{max-width:72rem}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.min-w-0{min-width:0}.object-cover{-o-object-fit:cover;object-fit:cover}.focus\:outline-none:focus,.outline-none{outline:0}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.p-0{padding:0}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.px-4{padding-left:1rem;padding-right:1rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.px-8{padding-left:2rem;padding-right:2rem}.py-10{padding-top:2.5rem;padding-bottom:2.5rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.py-16{padding-top:4rem;padding-bottom:4rem}.pt-0{padding-top:0}.pt-2{padding-top:.5rem}.pb-2{padding-bottom:.5rem}.pl-2{padding-left:.5rem}.pr-4{padding-right:1rem}.pb-4{padding-bottom:1rem}.pt-6{padding-top:1.5rem}.pb-8{padding-bottom:2rem}.pt-10{padding-top:2.5rem}.pr-10{padding-right:2.5rem}.pb-10{padding-bottom:2.5rem}.pl-10{padding-left:2.5rem}.pt-20{padding-top:5rem}.pt-32{padding-top:8rem}.placeholder-gray-400::-moz-placeholder{--placeholder-opacity:1;color:#cbd5e0;color:rgba(203,213,224,var(--placeholder-opacity))}.placeholder-gray-400:-ms-input-placeholder{--placeholder-opacity:1;color:#cbd5e0;color:rgba(203,213,224,var(--placeholder-opacity))}.placeholder-gray-400::-ms-input-placeholder{--placeholder-opacity:1;color:#cbd5e0;color:rgba(203,213,224,var(--placeholder-opacity))}.placeholder-gray-400::placeholder{--placeholder-opacity:1;color:#cbd5e0;color:rgba(203,213,224,var(--placeholder-opacity))}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{top:0;right:0;bottom:0;left:0}.top-0{top:0}.right-0{right:0}.bottom-0{bottom:0}.left-0{left:0}.shadow{box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)}.shadow-md{box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06)}.shadow-lg{box-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -2px rgba(0,0,0,.05)}.shadow-2xl{box-shadow:0 25px 50px -12px rgba(0,0,0,.25)}.hover\:shadow-md:hover{box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06)}.hover\:shadow-lg:hover{box-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -2px rgba(0,0,0,.05)}.hover\:shadow-inner:hover{box-shadow:inset 0 2px 4px 0 rgba(0,0,0,.06)}.focus\:shadow-outline:focus{box-shadow:0 0 0 3px rgba(66,153,225,.5)}.fill-current{fill:currentColor}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.text-black{--text-opacity:1;color:#000;color:rgba(0,0,0,var(--text-opacity))}.text-white{--text-opacity:1;color:#fff;color:rgba(255,255,255,var(--text-opacity))}.text-gray-500{--text-opacity:1;color:#a0aec0;color:rgba(160,174,192,var(--text-opacity))}.text-gray-600{--text-opacity:1;color:#718096;color:rgba(113,128,150,var(--text-opacity))}.text-gray-700{--text-opacity:1;color:#4a5568;color:rgba(74,85,104,var(--text-opacity))}.text-gray-800{--text-opacity:1;color:#2d3748;color:rgba(45,55,72,var(--text-opacity))}.text-gray-900{--text-opacity:1;color:#1a202c;color:rgba(26,32,44,var(--text-opacity))}.text-red-600{--text-opacity:1;color:#e53e3e;color:rgba(229,62,62,var(--text-opacity))}.text-red-700{--text-opacity:1;color:#c53030;color:rgba(197,48,48,var(--text-opacity))}.text-yellow-700{--text-opacity:1;color:#b7791f;color:rgba(183,121,31,var(--text-opacity))}.text-green-700{--text-opacity:1;color:#2f855a;color:rgba(47,133,90,var(--text-opacity))}.text-green-900{--text-opacity:1;color:#22543d;color:rgba(34,84,61,var(--text-opacity))}.text-teal-500{--text-opacity:1;color:#38b2ac;color:rgba(56,178,172,var(--text-opacity))}.text-blue-200{--text-opacity:1;color:#bee3f8;color:rgba(190,227,248,var(--text-opacity))}.text-blue-700{--text-opacity:1;color:#2b6cb0;color:rgba(43,108,176,var(--text-opacity))}.text-secondary-200{--text-opacity:1;color:#888883;color:rgba(136,136,131,var(--text-opacity))}.text-tempc-200{--text-opacity:1;color:#e93434;color:rgba(233,52,52,var(--text-opacity))}.text-tempc-400{--text-opacity:1;color:#266fea;color:rgba(38,111,234,var(--text-opacity))}.text-tempc-500{--text-opacity:1;color:#41d7aa;color:rgba(65,215,170,var(--text-opacity))}.hover\:text-black:hover{--text-opacity:1;color:#000;color:rgba(0,0,0,var(--text-opacity))}.hover\:text-white:hover{--text-opacity:1;color:#fff;color:rgba(255,255,255,var(--text-opacity))}.hover\:text-gray-900:hover{--text-opacity:1;color:#1a202c;color:rgba(26,32,44,var(--text-opacity))}.hover\:text-red-500:hover{--text-opacity:1;color:#f56565;color:rgba(245,101,101,var(--text-opacity))}.hover\:text-yellow-500:hover{--text-opacity:1;color:#ecc94b;color:rgba(236,201,75,var(--text-opacity))}.hover\:text-green-500:hover{--text-opacity:1;color:#48bb78;color:rgba(72,187,120,var(--text-opacity))}.focus\:text-black:focus{--text-opacity:1;color:#000;color:rgba(0,0,0,var(--text-opacity))}.focus\:text-gray-900:focus{--text-opacity:1;color:#1a202c;color:rgba(26,32,44,var(--text-opacity))}.uppercase{text-transform:uppercase}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.tracking-widest{letter-spacing:.1em}.whitespace-no-wrap{white-space:nowrap}.break-words{overflow-wrap:break-word}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-10{width:2.5rem}.w-16{width:4rem}.w-20{width:5rem}.w-24{width:6rem}.w-32{width:8rem}.w-auto{width:auto}.w-1\/2{width:50%}.w-2\/3{width:66.666667%}.w-2\/5{width:40%}.w-full{width:100%}.w-screen{width:100vw}.gap-4{grid-gap:1rem;gap:1rem}.gap-8{grid-gap:2rem;gap:2rem}.gap-10{grid-gap:2.5rem;gap:2.5rem}.grid-flow-col{grid-auto-flow:column}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.transform{--transform-translate-x:0;--transform-translate-y:0;--transform-rotate:0;--transform-skew-x:0;--transform-skew-y:0;--transform-scale-x:1;--transform-scale-y:1;transform:translateX(var(--transform-translate-x)) translateY(var(--transform-translate-y)) rotate(var(--transform-rotate)) skewX(var(--transform-skew-x)) skewY(var(--transform-skew-y)) scaleX(var(--transform-scale-x)) scaleY(var(--transform-scale-y))}.hover\:scale-125:hover{--transform-scale-x:1.25;--transform-scale-y:1.25}.transition{transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.duration-500{transition-duration:.5s}@-webkit-keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@-webkit-keyframes ping{0%{transform:scale(1);opacity:1}75%,to{transform:scale(2);opacity:0}}@keyframes ping{0%{transform:scale(1);opacity:1}75%,to{transform:scale(2);opacity:0}}@-webkit-keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}@-webkit-keyframes bounce{0%,to{transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:translateY(0);-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1)}}@keyframes bounce{0%,to{transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:translateY(0);-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1)}}@media (min-width:640px){.sm\:container{width:100%;max-width:640px}@media (min-width:768px){.sm\:container{max-width:768px}}@media (min-width:1024px){.sm\:container{max-width:1024px}}@media (min-width:1280px){.sm\:container{max-width:1280px}}.sm\:h-20{height:5rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:px-8{padding-left:2rem;padding-right:2rem}}@media (min-width:768px){.md\:container{width:100%}@media (min-width:640px){.md\:container{max-width:640px}}@media (min-width:768px){.md\:container{max-width:768px}}@media (min-width:1024px){.md\:container{max-width:1024px}}@media (min-width:1280px){.md\:container{max-width:1280px}}.md\:block{display:block}.md\:inline-block{display:inline-block}.md\:flex{display:flex}.md\:hidden{display:none}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:justify-center{justify-content:center}.md\:h-screen{height:100vh}.md\:text-sm{font-size:.875rem}.md\:text-xl{font-size:1.25rem}.md\:text-2xl{font-size:1.5rem}.md\:text-3xl{font-size:1.875rem}.md\:mx-auto{margin-left:auto;margin-right:auto}.md\:ml-56{margin-left:14rem}.md\:ml-64{margin-left:16rem}.md\:ml-auto{margin-left:auto}.md\:px-2{padding-left:.5rem;padding-right:.5rem}.md\:px-8{padding-left:2rem;padding-right:2rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:py-12{padding-top:3rem;padding-bottom:3rem}.md\:pb-0{padding-bottom:0}.md\:fixed{position:fixed}.md\:top-0{top:0}.md\:left-0{left:0}.md\:break-all{word-break:break-all}.md\:w-56{width:14rem}.md\:w-1\/2{width:50%}.md\:w-4\/5{width:80%}.md\:z-50{z-index:50}.md\:gap-4{grid-gap:1rem;gap:1rem}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:col-span-4{grid-column:span 4/span 4}.md\:grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.md\:grid-rows-3{grid-template-rows:repeat(3,minmax(0,1fr))}}@media (min-width:1024px){.lg\:container{width:100%}@media (min-width:640px){.lg\:container{max-width:640px}}@media (min-width:768px){.lg\:container{max-width:768px}}@media (min-width:1024px){.lg\:container{max-width:1024px}}@media (min-width:1280px){.lg\:container{max-width:1280px}}.lg\:block{display:block}.lg\:grid{display:grid}.lg\:items-start{align-items:flex-start}.lg\:items-center{align-items:center}.lg\:justify-center{justify-content:center}.lg\:text-sm{font-size:.875rem}.lg\:text-base{font-size:1rem}.lg\:text-lg{font-size:1.125rem}.lg\:text-3xl{font-size:1.875rem}.lg\:text-4xl{font-size:2.25rem}.lg\:text-5xl{font-size:3rem}.lg\:text-6xl{font-size:4rem}.lg\:ml-64{margin-left:16rem}.lg\:p-6{padding:1.5rem}.lg\:px-10{padding-left:2.5rem;padding-right:2.5rem}.lg\:text-center{text-align:center}.lg\:w-64{width:16rem}.lg\:w-1\/4{width:25%}.lg\:w-3\/5{width:60%}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.lg\:grid-rows-3{grid-template-rows:repeat(3,minmax(0,1fr))}.lg\:row-span-2{grid-row:span 2/span 2}}@media (min-width:1280px){.xl\:container{width:100%}@media (min-width:640px){.xl\:container{max-width:640px}}@media (min-width:768px){.xl\:container{max-width:768px}}@media (min-width:1024px){.xl\:container{max-width:1024px}}@media (min-width:1280px){.xl\:container{max-width:1280px}}.xl\:text-2xl{font-size:1.5rem}} \ No newline at end of file diff --git a/public/css/tailwind.css b/public/css/tailwind.css index f441c4a..985abac 100644 --- a/public/css/tailwind.css +++ b/public/css/tailwind.css @@ -1,7 +1,4 @@ @tailwind base; -@tailwind components; -@tailwind utilities; - /* Custom CSS */ /* Main CSS */ @@ -23,10 +20,24 @@ body { width: 75vw; } +@tailwind components; + .symbolicon { font-size: 72px; background: -webkit-linear-gradient(#eee, #333); background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent; -} \ No newline at end of file +} + +.min-w{ + min-width: 400px; +} + +@media (max-width:400px){ + .min-w{ + min-width: 300px; + } +} + +@tailwind utilities; \ No newline at end of file diff --git a/public/images/google.png b/public/images/google.png new file mode 100644 index 0000000..9074676 Binary files /dev/null and b/public/images/google.png differ diff --git a/routes/api/auth.js b/routes/api/auth.js index 971fdbd..f5a41a7 100644 --- a/routes/api/auth.js +++ b/routes/api/auth.js @@ -21,6 +21,14 @@ router.get( } ); +router.post('/signin', (req, res, next) => { + passport.authenticate('local', { + successRedirect: '/portfolio', + failureRedirect: '/', + failureFlash: true + })(req, res, next) +}) + // @desc Logout user // @route /auth/logout router.get("/logout", (req, res) => { diff --git a/routes/api/user.js b/routes/api/user.js new file mode 100644 index 0000000..4efffba --- /dev/null +++ b/routes/api/user.js @@ -0,0 +1,78 @@ +const express = require("express"); +const router = express.Router(); +const passport = require("passport") +const bcrypt = require("bcryptjs"); +const {v4: uuidv4} = require("uuid"); +const { ensureGuest } = require("../../middleware/auth"); + +// Load User Model +const User = require("../../models/User"); + +// @desc Sign Up Page +// @route GET /user/signup +// @access Public +router.get('/signup', ensureGuest, (req, res) => { + res.status(200).render('signup', { layout: 'layouts/login' }) +}) + +// @desc Submit Sign Up Form +// @route GET /user/signup +router.post('/signup', (req, res) => { + const { firstName, lastName, password1, password2, email } = req.body; + let errors = []; + + if (!firstName || !lastName || !password1 || !password2 || !email) { + errors.push({ msg: 'Please enter all fields' }); + } + if (password1 !== password2) { + errors.push({ msg: 'Passwords do not match' }) + } + if (password1.length < 6) { + errors.push({ msg: 'Password must be longer than 6 characters' }) + } + + if (errors.length > 0) { + res.render('signup', { layout: 'layouts/login', errors, firstName, lastName, password1, password2 }) + } else { + User.findOne({ email: email }).then((user) => { + if (user) { + errors.push({ msg: 'Email already exists' }) + res.render('signup', { layout: 'layouts/login', errors, firstName, lastName, password1, password2 }) + } else { + const newUser = new User({ + googleId: uuidv4(), + displayName: `${firstName} ${lastName}`, + firstName, + lastName, + email, + image: 'https://t3.ftcdn.net/jpg/00/64/67/52/240_F_64675209_7ve2XQANuzuHjMZXP3aIYIpsDKEbF5dD.jpg', + password: password1, + balance: 10000, + }) + + bcrypt.genSalt(10, (err, salt) => { + bcrypt.hash(newUser.password, salt, (err, hash) => { + if (err) throw err; + newUser.password = hash; + newUser.save().then(user => { + req.flash('success_msg', 'You are now registered and can log in') + res.status(200).redirect('/') + }).catch((err) => console.log(err)) + }) + }) + } + }) + } +}) + +// @desc Submit Sign In Form +// @route GET /user/signin +router.post('/signin', (req, res, next) => { + passport.authenticate('local', { + successRedirect: '/portfolio', + failureRedirect: '/', + failureFlash: true, + })(req, res, next) +}) + +module.exports = router; \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js index 0b764c4..1b75d52 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -11,11 +11,7 @@ module.exports = { 100: '#E2E2D5', 200: '#888883', }, - - // colors according to design 400->blue 200->red 500->green - - tempc: { 100: '#365088', 200: '#E93434', diff --git a/views/layouts/login.ejs b/views/layouts/login.ejs index 1fbc4e3..3430f21 100644 --- a/views/layouts/login.ejs +++ b/views/layouts/login.ejs @@ -4,15 +4,17 @@ - + + TradeByte - + <%- body %> - + \ No newline at end of file diff --git a/views/login.ejs b/views/login.ejs index 56199c3..e7f92af 100644 --- a/views/login.ejs +++ b/views/login.ejs @@ -1,7 +1,43 @@ -

**Login/Landing Page**

- -
- - - Login With Google - +
+

TradeByte

+
+
+ + <%- include('./partials/_messages') %> +
+
+
+
Or sign in with credentials
+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+
\ No newline at end of file diff --git a/views/partials/_messages.ejs b/views/partials/_messages.ejs new file mode 100644 index 0000000..e260cb9 --- /dev/null +++ b/views/partials/_messages.ejs @@ -0,0 +1,103 @@ +<% if(typeof errors != "undefined") { %> +<% errors.forEach(function(error) { %> +
+
+
+ + + + + +
+

<%= error.msg %>

+
+
+ + + + +
+
+<% }); %> +<% } %> + +<% if(success_msg != "") { %> +
+
+
+ + + + + +
+

<%= success_msg %>

+
+
+ + + + +
+
+<% } %> + +<% if(error_msg != "") { %> +
+
+
+ + + + + +
+

<%= error_msg %>

+
+
+ + + + +
+
+<% } %> + +<% if(error != "") { %> +
+
+
+ + + + + +
+

<%= error %>

+
+
+ + + + +
+
+<% } %> \ No newline at end of file diff --git a/views/signup.ejs b/views/signup.ejs new file mode 100644 index 0000000..7434d8b --- /dev/null +++ b/views/signup.ejs @@ -0,0 +1,52 @@ +
+

TradeByte

+
+
+

Sign Up for  TradeByte

+ <%- include('./partials/_messages') %> +
+
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ +

Go back to Login

+
+
+
+
+
\ No newline at end of file