Completed contact page??

This commit is contained in:
Esther 2020-07-01 12:10:30 +01:00
parent f638cf4d8b
commit ffe3b01e32
No known key found for this signature in database
GPG Key ID: 162A307C5EBD40EA
11 changed files with 229 additions and 42 deletions

2
app.js
View File

@ -5,6 +5,7 @@ var cookieParser = require('cookie-parser');
var mLogger = require('morgan'); var mLogger = require('morgan');
var sassMiddleware = require('node-sass-middleware'); var sassMiddleware = require('node-sass-middleware');
var logger = require('./config/winston'); var logger = require('./config/winston');
const helmet = require("helmet");
var indexRouter = require('./routes/index'); var indexRouter = require('./routes/index');
var aboutRouter = require('./routes/about'); var aboutRouter = require('./routes/about');
@ -25,6 +26,7 @@ if (process.env.NODE_ENV === 'production') {
app.use(mLogger('dev')); app.use(mLogger('dev'));
} }
app.use(helmet());
app.use(express.json()); app.use(express.json());
app.use(express.urlencoded({ extended: false })); app.use(express.urlencoded({ extended: false }));
app.use(cookieParser()); app.use(cookieParser());

116
package-lock.json generated
View File

@ -250,6 +250,11 @@
} }
} }
}, },
"bowser": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/bowser/-/bowser-2.9.0.tgz",
"integrity": "sha512-2ld76tuLBNFekRgmJfT2+3j5MIrP6bFict8WAIT3beq+srz1gcKNAdNKMqHqauQt63NmAa88HfP1/Ypa9Er3HA=="
},
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -278,6 +283,11 @@
"map-obj": "^1.0.0" "map-obj": "^1.0.0"
} }
}, },
"camelize": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz",
"integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs="
},
"caseless": { "caseless": {
"version": "0.12.0", "version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
@ -428,6 +438,11 @@
"safe-buffer": "5.1.2" "safe-buffer": "5.1.2"
} }
}, },
"content-security-policy-builder": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/content-security-policy-builder/-/content-security-policy-builder-2.1.0.tgz",
"integrity": "sha512-/MtLWhJVvJNkA9dVLAp6fg9LxD2gfI6R2Fi1hPmfjYXSahJJzcfvoeDOxSyp4NvxMuwWv3WMssE9o31DoULHrQ=="
},
"content-type": { "content-type": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
@ -482,6 +497,11 @@
"assert-plus": "^1.0.0" "assert-plus": "^1.0.0"
} }
}, },
"dasherize": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dasherize/-/dasherize-2.0.0.tgz",
"integrity": "sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg="
},
"debug": { "debug": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
@ -520,6 +540,11 @@
"resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz",
"integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk="
}, },
"dont-sniff-mimetype": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/dont-sniff-mimetype/-/dont-sniff-mimetype-1.1.0.tgz",
"integrity": "sha512-ZjI4zqTaxveH2/tTlzS1wFp+7ncxNZaIEWYg3lzZRHkKf5zPT/MnEG6WL0BhHMJUabkh8GeU5NL5j+rEUCb7Ug=="
},
"ecc-jsbn": { "ecc-jsbn": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@ -634,6 +659,11 @@
} }
} }
}, },
"express-rate-limit": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.1.3.tgz",
"integrity": "sha512-TINcxve5510pXj4n9/1AMupkj3iWxl3JuZaWhCdYDlZeoCPqweGZrxbrlqTCFb1CT5wli7s8e2SH/Qz2c9GorA=="
},
"extend": { "extend": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@ -659,6 +689,11 @@
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz",
"integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA=="
}, },
"feature-policy": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/feature-policy/-/feature-policy-0.3.0.tgz",
"integrity": "sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ=="
},
"fecha": { "fecha": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.0.tgz", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.0.tgz",
@ -877,11 +912,77 @@
"resolved": "https://registry.npmjs.org/hcaptcha/-/hcaptcha-0.0.2.tgz", "resolved": "https://registry.npmjs.org/hcaptcha/-/hcaptcha-0.0.2.tgz",
"integrity": "sha512-wWOncj/sY+q8s7tV12tjn3cFNoQhSu3l/7nTJi4QkFKALQi9XnduoXrV/KFzLg5lnB+5560zSAoi9YdYPDw6Eg==" "integrity": "sha512-wWOncj/sY+q8s7tV12tjn3cFNoQhSu3l/7nTJi4QkFKALQi9XnduoXrV/KFzLg5lnB+5560zSAoi9YdYPDw6Eg=="
}, },
"helmet": {
"version": "3.23.3",
"resolved": "https://registry.npmjs.org/helmet/-/helmet-3.23.3.tgz",
"integrity": "sha512-U3MeYdzPJQhtvqAVBPntVgAvNSOJyagwZwyKsFdyRa8TV3pOKVFljalPOCxbw5Wwf2kncGhmP0qHjyazIdNdSA==",
"requires": {
"depd": "2.0.0",
"dont-sniff-mimetype": "1.1.0",
"feature-policy": "0.3.0",
"helmet-crossdomain": "0.4.0",
"helmet-csp": "2.10.0",
"hide-powered-by": "1.1.0",
"hpkp": "2.0.0",
"hsts": "2.2.0",
"nocache": "2.1.0",
"referrer-policy": "1.2.0",
"x-xss-protection": "1.3.0"
},
"dependencies": {
"depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
}
}
},
"helmet-crossdomain": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/helmet-crossdomain/-/helmet-crossdomain-0.4.0.tgz",
"integrity": "sha512-AB4DTykRw3HCOxovD1nPR16hllrVImeFp5VBV9/twj66lJ2nU75DP8FPL0/Jp4jj79JhTfG+pFI2MD02kWJ+fA=="
},
"helmet-csp": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.10.0.tgz",
"integrity": "sha512-Rz953ZNEFk8sT2XvewXkYN0Ho4GEZdjAZy4stjiEQV3eN7GDxg1QKmYggH7otDyIA7uGA6XnUMVSgeJwbR5X+w==",
"requires": {
"bowser": "2.9.0",
"camelize": "1.0.0",
"content-security-policy-builder": "2.1.0",
"dasherize": "2.0.0"
}
},
"hide-powered-by": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/hide-powered-by/-/hide-powered-by-1.1.0.tgz",
"integrity": "sha512-Io1zA2yOA1YJslkr+AJlWSf2yWFkKjvkcL9Ni1XSUqnGLr/qRQe2UI3Cn/J9MsJht7yEVCe0SscY1HgVMujbgg=="
},
"hosted-git-info": { "hosted-git-info": {
"version": "2.8.8", "version": "2.8.8",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg=="
}, },
"hpkp": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/hpkp/-/hpkp-2.0.0.tgz",
"integrity": "sha1-EOFCJk52IVpdMMROxD3mTe5tFnI="
},
"hsts": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/hsts/-/hsts-2.2.0.tgz",
"integrity": "sha512-ToaTnQ2TbJkochoVcdXYm4HOCliNozlviNsg+X2XQLQvZNI/kCHR9rZxVYpJB3UPcHz80PgxRyWQ7PdU1r+VBQ==",
"requires": {
"depd": "2.0.0"
},
"dependencies": {
"depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
}
}
},
"http-errors": { "http-errors": {
"version": "1.7.3", "version": "1.7.3",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz",
@ -1262,6 +1363,11 @@
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
}, },
"nocache": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz",
"integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q=="
},
"node-gyp": { "node-gyp": {
"version": "3.8.0", "version": "3.8.0",
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz",
@ -1735,6 +1841,11 @@
"strip-indent": "^1.0.1" "strip-indent": "^1.0.1"
} }
}, },
"referrer-policy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.2.0.tgz",
"integrity": "sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA=="
},
"repeating": { "repeating": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
@ -2289,6 +2400,11 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
}, },
"x-xss-protection": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/x-xss-protection/-/x-xss-protection-1.3.0.tgz",
"integrity": "sha512-kpyBI9TlVipZO4diReZMAHWtS0MMa/7Kgx8hwG/EuZLiA6sg4Ah/4TRdASHhRRN3boobzcYgFRUFSgHRge6Qhg=="
},
"y18n": { "y18n": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",

View File

@ -12,7 +12,9 @@
"cookie-parser": "1.4.4", "cookie-parser": "1.4.4",
"debug": "4.1.1", "debug": "4.1.1",
"express": "4.17.1", "express": "4.17.1",
"express-rate-limit": "^5.1.3",
"hcaptcha": "0.0.2", "hcaptcha": "0.0.2",
"helmet": "^3.23.3",
"http-errors": "1.7.3", "http-errors": "1.7.3",
"morgan": "1.10.0", "morgan": "1.10.0",
"node-sass-middleware": "0.11.0", "node-sass-middleware": "0.11.0",

View File

@ -25,9 +25,12 @@ h2 {
font-weight: bold; font-weight: bold;
font-family: "Apercu Mono", monospace; } font-family: "Apercu Mono", monospace; }
p { p, label, input, textarea {
font-family: "Apercu Mono", monospace; } font-family: "Apercu Mono", monospace; }
label {
font-size: 3vh; }
a:hover { a:hover {
color: #CDE7B0; } color: #CDE7B0; }
@ -147,6 +150,27 @@ footer {
.start { .start {
margin-top: 3vh; } margin-top: 3vh; }
#contact-message {
margin-top: 2vh; }
#contact-message p {
color: #CDE7B0; }
#contact-message p span {
text-decoration: underline #CDE7B0; }
.h-captcha {
margin-bottom: 0.5vh; }
.btn-primary {
font-family: "Apercu Mono";
background-color: #CC7A98;
color: #002234;
border-color: #002234; }
.btn-primary:hover {
background-color: #002234;
color: #CC7A98;
border-color: #CC7A98; }
.ham { .ham {
width: 30px; width: 30px;
height: 20px; height: 20px;

View File

@ -29,13 +29,15 @@ h2
font-weight: bold font-weight: bold
font-family: "Apercu Mono", monospace font-family: "Apercu Mono", monospace
p p, label, input, textarea
font-family: "Apercu Mono", monospace font-family: "Apercu Mono", monospace
label
font-size: 3vh
a:hover a:hover
color: $green color: $green
article article
min-height: 100% min-height: 100%
display: grid display: grid
@ -166,6 +168,27 @@ footer
.start .start
margin-top: 3vh margin-top: 3vh
#contact-message
margin-top: 2vh
p
color: $green
span
text-decoration: underline $green
.h-captcha
margin-bottom: 0.5vh
.btn-primary
font-family: "Apercu Mono"
background-color: $pink
color: $black
border-color: $black
.btn-primary:hover
background-color: $black
color: $pink
border-color: $pink
// Nav stuff // Nav stuff
.ham .ham

View File

@ -4,8 +4,8 @@ var router = express.Router();
/* GET home page. */ /* GET home page. */
router.get('/', function(req, res, next) { router.get('/', function(req, res, next) {
const ghost_key = process.env.GHOST_KEY const GHOST_KEY = process.env.GHOST_KEY
const base_url = `https://blog.pastel.codes/ghost/api/v3/content/posts/?key=${ghost_key}` const base_url = `https://blog.pastel.codes/ghost/api/v3/content/posts/?key=${GHOST_KEY}`
axios.all([ axios.all([
axios.get(`${base_url}&limit=3`), axios.get(`${base_url}&limit=3`),
@ -22,7 +22,6 @@ router.get('/', function(req, res, next) {
.catch(error => { .catch(error => {
console.log(error); console.log(error);
}); });
// https://blog.pastel.codes/ghost/api/v3/content/posts/?key=
}); });
module.exports = router; module.exports = router;

View File

@ -1,19 +1,29 @@
var express = require('express'); var express = require('express');
var rate_limit = require("express-rate-limit")
const {verify} = require('hcaptcha'); const {verify} = require('hcaptcha');
const nodemailer = require('nodemailer') const nodemailer = require('nodemailer')
var router = express.Router(); var router = express.Router();
const contact_rate_limit = rate_limit({
windowMs: 10 * 60 * 1000, // 10 minutes
max: 1, // limit each IP to 10 requests per windowMs
message: "Too many contact requests, try again later.",
handler: function(req, res /*, next*/) {
res.render('error', {title:"Error", message: "Too many contact requests, try again later.", error: {status: null}})
},
});
// POST route from contact form // POST route from contact form
router.post('/', (req, res) => { router.post('/', contact_rate_limit,(req, res) => {
const TO_GMAIL_USER = process.env.TO_GMAIL_USER const TO_GMAIL_USER = process.env.TO_GMAIL_USER
const FROM_GMAIL_USER = process.env.FROM_GMAIL_USER const FROM_GMAIL_USER = process.env.FROM_GMAIL_USER
const GMAIL_PASS = process.env.GMAIL_PASS const GMAIL_PASS = process.env.GMAIL_PASS
const HCAPTCHA_KEY = process.env.HCAPTCHA_KEY const HCAPTCHA_KEY = process.env.HCAPTCHA_KEY
const token = req.body["g-recaptcha-response"]; const token = req.body["g-recaptcha-response"];
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
verify(HCAPTCHA_KEY, token) verify(HCAPTCHA_KEY, token)
.then((data) => { .then((data) => {
console.log(data)
if (data.success === true) { if (data.success === true) {
// Instantiate the SMTP server // Instantiate the SMTP server
const smtpTrans = nodemailer.createTransport({ const smtpTrans = nodemailer.createTransport({
@ -31,7 +41,7 @@ router.post('/', (req, res) => {
from: 'Pastel.codes Contact Notifications', // This is ignored by Gmail from: 'Pastel.codes Contact Notifications', // This is ignored by Gmail
to: TO_GMAIL_USER, to: TO_GMAIL_USER,
subject: 'New message from contact form at pastel.codes', subject: 'New message from contact form at pastel.codes',
text: `${req.body.name} (${req.body.email}) says: ${req.body.message}` text: `${req.body.firstname} ${req.body.lastname} (${req.body.email})\nsays: ${req.body.message}\n\nip: ${ip}`
} }
// maybe send conformation email? // maybe send conformation email?
@ -40,26 +50,26 @@ router.post('/', (req, res) => {
smtpTrans.sendMail(mailOpts, (error, response) => { smtpTrans.sendMail(mailOpts, (error, response) => {
if (error) { if (error) {
console.log(error) console.log(error)
res.render('error', {message: "Email did not send"}) // Show a page indicating failure res.render('error', { message: "Email did not send" }) // Show a page indicating failure
} }
else { else {
res.render('contact-success') // Show a page indicating success res.render('contact', { message: "I will get back to you soon!", success: "Make sure the email is from ", email: TO_GMAIL_USER }) // Show a page indicating success
} }
}) })
} else { } else {
// rerender with same info in the text box and show error message // rerender with same info in the text box and show error message
res.render('contact', { title: 'Contact', description: "Contact me!", message: "Captcha failed, try again" }); res.render('contact', { message: "Captcha failed, try again" });
} }
}) })
.catch(error => { .catch(error => {
console.log(error); console.log(error);
res.render('contact', {title: 'Contact', description: "Contact me!", message: "Something wrong happened, try again later"}); res.render('contact', { message: "Something wrong happened, try again later" });
}); });
}) })
/* GET home page. */ /* GET home page. */
router.get('/', function(req, res, next) { router.get('/', function(req, res, next) {
res.render('contact', { title: 'Contact', description: "Contact me!", message: null }); res.render('contact', { title: 'Contact', description: "Contact me!"});
}); });
module.exports = router; module.exports = router;

View File

@ -1,10 +1,10 @@
extends layout extends layout
block nav-links block nav-links
li.nav-item.active li.nav-item
a.nav-link(href='/') a.nav-link(href='/')
span Home span Home
li.nav-item li.nav-item.active
a.nav-link(href='#') a.nav-link(href='#')
span About span About
li.nav-item li.nav-item
@ -28,7 +28,8 @@ block content
h1 h1
span Hello. span Hello.
p Im Esther, a 19 year old student in 2nd year of university, who studies computer science & artificial intelligence. p Im Esther, a 19 year old student in 2nd year of university, who studies computer science & artificial intelligence.
p In my free time, I create small projects to learn new skills and for them to function well for others to use; additionally, I do some graphic design / art as a hobby but has helped me create catching designs. p In my free time, I create small projects to learn new skills to be helpful for others, recently i have been focusing on node.js.
p Graphic design is also very fun and i enjoy making small projects to post on instagram, it helps with programming too which is neat
.row .row
.col.start .col.start
h1 h1

View File

@ -1,4 +0,0 @@
extends layout
block content
p fuck

View File

@ -1,4 +0,0 @@
extends layout
block content
p yay

View File

@ -4,7 +4,7 @@ block head
script(src='https://hcaptcha.com/1/api.js' async='' defer='defer') script(src='https://hcaptcha.com/1/api.js' async='' defer='defer')
block nav-links block nav-links
li.nav-item.active li.nav-item
a.nav-link(href='/') a.nav-link(href='/')
span Home span Home
li.nav-item li.nav-item
@ -19,24 +19,42 @@ block nav-links
li.nav-item li.nav-item
a.nav-link(href='https://blog.pastel.codes') a.nav-link(href='https://blog.pastel.codes')
span Blog span Blog
li.nav-item li.nav-item.active
a.nav-link(href='#') a.nav-link(href='#')
span Contact span Contact
block content block content
.container .container
.row
.col
h1
span Contact.
.row
.col
form#contact-form(action='/contact' method='post' role='form') form#contact-form(action='/contact' method='post' role='form')
.form-group .form-group
label(for='name') Name label Name
input#name(name='name' class="form-control" type='text' placeholder='Your name' required='') .form-row
.col
input#fname(name='firstname' class="form-control" type='text' placeholder='First name' required="true" pattern="^[\\w'\\-,.][^0-9_!¡?÷?¿/\\\\+=@#$%ˆ&*(){}|~<>;:[\\]]{2,}$")
.col
input#lname(name='lastname' class="form-control" type='text' placeholder='Last name' required="true" pattern="^[\\w'\\-,.][^0-9_!¡?÷?¿/\\\\+=@#$%ˆ&*(){}|~<>;:[\\]]{2,}$")
.form-group .form-group
label(for='email') Email label(for='email') Email
input#email(name='email' class="form-control" type='text' placeholder='Your email' required='') input#email(name='email' class="form-control" type='text' placeholder='Email (example@email.com)' required='true' pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$")
.form-group .form-group
label(for='message') Message label(for='message') Message
textarea#message(name='message' class="form-control" placeholder='Enter your message here' rows='3' required='') textarea#message(name='message' class="form-control" placeholder='Enter your message here' rows='3' required='true')
.form-group
.h-captcha(data-sitekey='49abba50-1813-4ab3-acbf-2a8bfff1f7c3') .h-captcha(data-sitekey='49abba50-1813-4ab3-acbf-2a8bfff1f7c3')
button(type='submit' class="btn btn-primary") Submit button(type='submit' class="btn btn-primary") Submit
.row
.col
if message if message
#contact-error #contact-message
p=message p=message
if success
p=success
span#email=email