Added in a cookie popup and proper newsletter functionality
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 21s

Signed-off-by: rodude123 <rodude123@gmail.com>
This commit is contained in:
2023-12-06 00:38:33 +00:00
parent e6522fb05e
commit 52614e5835
37 changed files with 2634 additions and 154 deletions
+843 -9
View File
@@ -8,11 +8,15 @@ use DOMDocument;
use PDO;
use Psr\Http\Message\UploadedFileInterface;
use DonatelloZa\RakePlus\RakePlus;
use function DI\string;
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
use function api\utils\dbConn;
use function api\utils\getEmailPassword;
use const api\utils\feedGenerator\ATOM;
use const api\utils\feedGenerator\RSS2;
require_once __DIR__ . "/../utils/config.php";
require_once __DIR__ . "/../utils/imgUtils.php";
require_once __DIR__ . "/../utils/feedGenerator/FeedWriter.php";
@@ -254,10 +258,10 @@ class blogData
* @param string $dateCreated - Date the blog post was created
* @param bool $featured - Whether the blog post is featured or not
* @param string $categories - Categories of the blog post
* @param UploadedFileInterface $headerImg - Header image of the blog post
* @param UploadedFileInterface|null $headerImg - Header image of the blog post
* @return int|string - ID of the blog post or error message
*/
public function createPost(string $title, string $abstract, string $body, string $bodyText, string $dateCreated, bool $featured, string $categories, UploadedFileInterface $headerImg): int|string
public function createPost(string $title, string $abstract, string $body, string $bodyText, string $dateCreated, bool $featured, string $categories, UploadedFileInterface|null $headerImg): int|string
{
$conn = dbConn();
$folderID = uniqid();
@@ -289,6 +293,13 @@ class blogData
$keywords = implode(", ", RakePlus::create($bodyText)->keywords());
$latest = $this->getLatestBlogPost();
$prevTitle = $latest["title"];
$prevAbstract = $latest["abstract"];
$prevHeaderImage = $latest["headerImg"];
$headerImage = $targetFile["imgLocation"];
$stmt = $conn->prepare("INSERT INTO blog (title, dateCreated, dateModified, featured, headerImg, abstract, body, bodyText, categories, keywords, folderID)
VALUES (:title, :dateCreated, :dateModified, :featured, :headerImg, :abstract, :body, :bodyText, :categories, :keywords, :folderID);");
$stmt->bindParam(":title", $title);
@@ -296,7 +307,7 @@ class blogData
$stmt->bindParam(":dateModified", $dateCreated);
$isFeatured = $featured ? 1 : 0;
$stmt->bindParam(":featured", $isFeatured);
$stmt->bindParam(":headerImg", $targetFile["imgLocation"]);
$stmt->bindParam(":headerImg", $headerImage);
$stmt->bindParam(":abstract", $abstract);
$stmt->bindParam(":body", $newBody);
$stmt->bindParam(":bodyText", $bodyText);
@@ -304,12 +315,267 @@ class blogData
$stmt->bindParam(":keywords", $keywords);
$stmt->bindParam(":folderID", $folderID);
if ($stmt->execute())
if (!$stmt->execute())
{
return intval($conn->lastInsertId());
return "Error, couldn't create post";
}
return "Error, couldn't create post";
$stmtEmails = $conn->prepare("SELECT email FROM newsletter;");
$stmtEmails->execute();
$emails = $stmtEmails->fetchAll(PDO::FETCH_ASSOC);
$emailBody = <<<EOD
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Rohit Pai's blog</title>
<style>
@font-face {
font-family: 'Noto Sans';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/notosans/v34/o-0NIpQlx3QUlC5A4PNjXhFVZNyB.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Share Tech Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/sharetechmono/v15/J7aHnp1uDWRBEqV98dVQztYldFcLowEF.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
*{
box-sizing: border-box;
}
body, html {
margin: 0;
padding: 0;
}
html {
scroll-behavior: smooth;
}
body {
font-family: Noto Sans KR, sans-serif;
font-style: normal;
font-weight: 500;
font-size: 22px;
line-height: 1.625rem;
min-height: 100%;
}
main, header, footer {
max-width: 768px;
margin: 0 auto;
}
a {
text-decoration: none;
text-transform: lowercase;
}
a:visited {
color: inherit;
}
h1, h2 {
font-family: Share Tech Mono, monospace;
font-style: normal;
font-weight: normal;
line-height: 2.5625rem;
text-transform: lowercase;
}
h1, nav {
font-size: 40px;
}
h2 {
font-size: 28px;
}
h3 {
font-size: 22px;
}
header {
background: hsla(80, 60%, 50%, 1);
color: #FFFFFF;
border-bottom: 5px solid hsla(0, 0%, 75%, 1);
}
header div.title, header div.byLine {
padding: 0 3em 1em;
}
header div.title h1 {
margin: 0;
padding: 1em 0 0;
font-size: 42px;
}
header div.byLine {
border-top: 5px solid hsla(80, 60%, 30%, 1);
}
a.btn {
background-color: hsla(80, 60%, 50%, 1);
text-decoration: none;
display: inline-flex;
padding: 1em 2em;
border-radius: 0.625em;
border: 0.3215em solid hsla(80, 60%, 50%, 1);
color: #FFFFFF;
text-align: center;
align-items: center;
max-height: 4em;
}
div.postContainer, div.container {
padding: 1em 2em 0;
margin: 0 auto 1em;
max-width: 768px;
}
div.postContainer ~ div.postContainer {
border-top: 5px solid hsla(0, 0%, 75%, 1);
}
div.postContainer > *, div.container > * {
margin: 0 auto;
max-width: 768px;
}
div.postContainer div.post h2, div.container div.content h2 {
margin: 0;
padding: 0;
}
div.postContainer div.image, div.container div.image {
width: 50%;
max-width: 100%;
height: auto;
}
div.postContainer div.image img {
-webkit-border-radius: 0.5em;
-moz-border-radius: 0.5em;
border-radius: 0.5em;
border: 4px solid hsla(0, 0%, 75%, 1);
}
div.container div.image img {
-webkit-border-radius: 10em;
-moz-border-radius: 10em;
border-radius: 10em;
border: 4px solid hsla(0, 0%, 75%, 1);
}
div.postContainer div.image img, div.container div.image img {
min-width: 100%;
width: 100%;
}
footer {
background-color: hsla(80, 60%, 50%, 1);
margin-top: auto;
padding: 3em;
display: block;
color: #FFFFFF;
}
footer .nav {
width: 75%;
float: left;
}
footer .nav ul {
list-style: none;
margin: 0;
padding: 0;
width: 100%;
}
footer .nav ul li {
display: inline-block;
margin: 0 1em;
}
footer .nav ul a {
color: #FFFFFF;
}
footer .date {
width: 25%;
float: right;
}
</style>
</head>
<body>
<header>
<div class="title">
<h1>Hey, I've got a new post!</h1>
</div>
<div class="byLine">
<p>Hello I'm Rohit an avid full-stack developer and home lab enthusiast. I love anything and everything
to do with full stack development, home labs, coffee and generally anything to do with tech.</p>
</div>
</header>
<main>
<div class="postContainer">
<h2>latest post</h2>
<div class="image">
<img src="$headerImage" alt="header image of the latest post">
</div>
<div class="post">
<h3>$title</h3>
<p>$abstract</p>
<a href="https://rohitpai.co.uk/blog/post/$title" class="btn">See Post</a>
</div>
</div>
<div class="postContainer">
<h2>in case you missed the previous post</h2>
<div class="image">
<img src="$prevHeaderImage" alt="header image of the previous post">
</div>
<div class="post">
<h3>$prevTitle</h3>
<p>$prevAbstract</p>
<a href="https://rohitpai.co.uk/blog/post/$prevTitle" class="btn">See Post</a>
</div>
</div>
</main>
<footer>
<div class="nav">
<ul>
<li><a href="https://rohitpai.co.uk/blog">&lt;https://rohitpai.co.uk/blog&gt;</a></li>
<li><a href="https://rohitpai.co.uk/blog/unsubscribe">&lt;Unsubscribe&gt;</a></li>
</ul>
</div>
<div class="date">2023</div>
</footer>
</body>
</html>
EOD;
foreach ($emails as $email)
{
$this->sendMail($email["email"], $emailBody, "Hey, Rohit's blog has a new post!");
}
return intval($conn->lastInsertId());
}
/**
@@ -616,12 +882,12 @@ class blogData
foreach ($posts as $post)
{
$items[] = array(
"id" => string($post["ID"]),
"id" => strval($post["ID"]),
"url" => "https://rohitpai.co.uk/blog/post/" . rawurlencode($post["title"]) . "#disqus_thread",
"title" => $post["title"],
"date_published" => date($post["dateCreated"]),
"date_modified" => date($post["dateModified"]),
// "description" => $post["abstract"],
"description" => $post["abstract"],
"banner_image" => "https://rohitpai.co.uk/" . rawurlencode($post["headerImg"]),
"content_html" => $post["body"]
);
@@ -655,4 +921,572 @@ class blogData
return $feed;
}
/**
* Add an email to the newsletter and send welcome email
* @param string $email - Email to add to the newsletter
* @return string|array - Success or error message
*/
public function addNewsletterEmail(string $email): string|array
{
$conn = dbConn();
$stmtCheckEmail = $conn->prepare("SELECT * FROM newsletter WHERE email = :email;");
$stmtCheckEmail->bindParam(":email", $email);
$stmtCheckEmail->execute();
$result = $stmtCheckEmail->fetch(PDO::FETCH_ASSOC);
if ($result)
{
return "Email already exists";
}
$stmt = $conn->prepare("INSERT INTO newsletter (email) VALUES (:email);");
$stmt->bindParam(":email", $email);
$stmt->execute();
$body = <<<EOD
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Rohit Pai's blog</title>
<style>
@font-face {
font-family: 'Noto Sans';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/notosans/v34/o-0NIpQlx3QUlC5A4PNjXhFVZNyB.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Share Tech Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/sharetechmono/v15/J7aHnp1uDWRBEqV98dVQztYldFcLowEF.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
*{
box-sizing: border-box;
}
body, html {
margin: 0;
padding: 0;
}
html {
scroll-behavior: smooth;
}
body {
font-family: Noto Sans KR, sans-serif;
font-style: normal;
font-weight: 500;
font-size: 22px;
line-height: 1.625rem;
min-height: 100%;
}
main, header, footer {
max-width: 768px;
margin: 0 auto;
}
a {
text-decoration: none;
text-transform: lowercase;
}
a:visited {
color: inherit;
}
h1, h2 {
font-family: Share Tech Mono, monospace;
font-style: normal;
font-weight: normal;
line-height: 2.5625rem;
text-transform: lowercase;
}
h1, nav {
font-size: 40px;
}
h2 {
font-size: 28px;
}
h3 {
font-size: 22px;
}
header {
background: hsla(80, 60%, 50%, 1);
color: #FFFFFF;
border-bottom: 5px solid hsla(0, 0%, 75%, 1);
}
header div.title, header div.byLine {
padding: 0 3em 1em;
}
header div.title h1 {
margin: 0;
padding: 1em 0 0;
font-size: 42px;
}
header div.byLine {
border-top: 5px solid hsla(80, 60%, 30%, 1);
}
a.btn {
background-color: hsla(80, 60%, 50%, 1);
text-decoration: none;
display: inline-flex;
padding: 1em 2em;
border-radius: 0.625em;
border: 0.3215em solid hsla(80, 60%, 50%, 1);
color: #FFFFFF;
text-align: center;
align-items: center;
max-height: 4em;
}
div.postContainer, div.container {
padding: 1em 2em 0;
margin: 0 auto 1em;
max-width: 768px;
}
div.postContainer ~ div.postContainer {
border-top: 5px solid hsla(0, 0%, 75%, 1);
}
div.postContainer > *, div.container > * {
margin: 0 auto;
max-width: 768px;
}
div.postContainer div.post h2, div.container div.content h2 {
margin: 0;
padding: 0;
}
div.postContainer div.image, div.container div.image {
width: 50%;
max-width: 100%;
height: auto;
}
div.postContainer div.image img {
-webkit-border-radius: 0.5em;
-moz-border-radius: 0.5em;
border-radius: 0.5em;
border: 4px solid hsla(0, 0%, 75%, 1);
}
div.container div.image img {
-webkit-border-radius: 50em;
-moz-border-radius: 50em;
border-radius: 50em;
border: 4px solid hsla(0, 0%, 75%, 1);
}
div.postContainer div.image img, div.container div.image img {
min-width: 100%;
width: 100%;
}
footer {
background-color: hsla(80, 60%, 50%, 1);
margin-top: auto;
padding: 3em;
display: block;
color: #FFFFFF;
}
footer .nav {
width: 75%;
float: left;
}
footer .nav ul {
list-style: none;
margin: 0;
padding: 0;
width: 100%;
}
footer .nav ul li {
display: inline-block;
margin: 0 1em;
}
footer .nav ul a {
color: #FFFFFF;
}
footer .date {
width: 25%;
float: right;
}
</style>
</head>
<body>
<header>
<div class="title">
<h1>hello from rohit</h1>
</div>
<div class="byLine">
<p>Hello I'm Rohit an avid full-stack developer and home lab enthusiast. I love anything and everything
to do with full stack development, home labs, coffee and generally anything to do with tech.</p>
</div>
</header>
<main>
<div class="container">
<h2>hey there, i'm rohit!</h2>
<div class="image"><img src="https://rohitpai.co.uk/imgs/profile.jpg" alt=""></div>
<div class="content">
<h3>What to Expect</h3>
<p>You'll get an email from me everytime I make a new post to my blog. Sometimes, you may get a special
email on occasion, where I talk about something interesting that's not worth a full on post but I
Still want to tell you about it.</p>
<p>Don't worry, I won't spam you with emails. Well, thanks for signing up to my newsletter, hopefully
you'll hear from me soon!</p>
<p>P.S. Please consider adding this email address rohit@rohitpai.co.uk to your emails
contact list so that my emails won't get sent to span, thanks.</p>
</div>
</div>
</main>
<footer>
<div class="nav">
<ul>
<li><a href="https://rohitpai.co.uk/blog">&lt;https://rohitpai.co.uk/blog&gt;</a></li>
<li><a href="https://rohitpai.co.uk/blog/unsubscribe">&lt;Unsubscribe&gt;</a></li>
</ul>
</div>
<div class="date">2023</div>
</footer>
</body>
</html>
EOD;
return $this->sendMail($email, $body, "Hello from Rohit!");
}
/**
* Send an email to the given email address
* @param string $email - Email address to send the email to
* @param string $body - Body of the email
* @param string $subject - Subject of the email
* @return string|string[]
*/
public function sendMail(string $email, string $body, string $subject): string|array
{
$mail = new PHPMailer(true);
try
{
$mail->isSMTP();
$mail->Host = "smtp.hostinger.com";
$mail->SMTPAuth = true;
$mail->Username = "rohit@rohitpai.co.uk";
$mail->Password = getEmailPassword();
$mail->SMTPSecure = "tls";
$mail->Port = 587;
$mail->setFrom("rohit@rohitpai.co.uk", "Rohit Pai");
$mail->addAddress($email);
$mail->isHTML();
$mail->Subject = $subject;
$mail->Body = $body;
$mail->send();
return "success";
}
catch (Exception $e)
{
return array("errorMessage" => "Error, couldn't send email because of " . $e);
}
}
/**
* @param string $email - Email to delete from the newsletter
* @return string - Success or error message
*/
public function deleteNewsletterEmail(string $email): string
{
$conn = dbConn();
$stmtCheckEmail = $conn->prepare("SELECT * FROM newsletter WHERE email = :email;");
$stmtCheckEmail->bindParam(":email", $email);
$stmtCheckEmail->execute();
$result = $stmtCheckEmail->fetch(PDO::FETCH_ASSOC);
if (!$result)
{
return "email not found";
}
$stmt = $conn->prepare("DELETE FROM newsletter WHERE email = :email;");
$stmt->bindParam(":email", $email);
$stmt->execute();;
return "success";
}
/**
* @param string $subject - Subject of the newsletter
* @param string $message - Message content
* @return array|string - Success or error message
*/
public function sendNewsletter(string $subject, string $message): array|string
{
$conn = dbConn();
$stmtEmails = $conn->prepare("SELECT email FROM newsletter;");
$stmtEmails->execute();
$emails = $stmtEmails->fetchAll(PDO::FETCH_ASSOC);
$msg = "";
$body = <<<EOD
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Rohit Pai's blog</title>
<style>
@font-face {
font-family: 'Noto Sans';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/notosans/v34/o-0NIpQlx3QUlC5A4PNjXhFVZNyB.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Share Tech Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/sharetechmono/v15/J7aHnp1uDWRBEqV98dVQztYldFcLowEF.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
*{
box-sizing: border-box;
}
body, html {
margin: 0;
padding: 0;
}
html {
scroll-behavior: smooth;
}
body {
font-family: Noto Sans KR, sans-serif;
font-style: normal;
font-weight: 500;
font-size: 22px;
line-height: 1.625rem;
min-height: 100%;
}
main, header, footer {
max-width: 768px;
margin: 0 auto;
}
a {
text-decoration: none;
text-transform: lowercase;
}
a:visited {
color: inherit;
}
h1, h2 {
font-family: Share Tech Mono, monospace;
font-style: normal;
font-weight: normal;
line-height: 2.5625rem;
text-transform: lowercase;
}
h1, nav {
font-size: 40px;
}
h2 {
font-size: 28px;
}
h3 {
font-size: 22px;
}
header {
background: hsla(80, 60%, 50%, 1);
color: #FFFFFF;
border-bottom: 5px solid hsla(0, 0%, 75%, 1);
}
header div.title, header div.byLine {
padding: 0 3em 1em;
}
header div.title h1 {
margin: 0;
padding: 1em 0 0;
font-size: 42px;
}
header div.byLine {
border-top: 5px solid hsla(80, 60%, 30%, 1);
}
a.btn {
background-color: hsla(80, 60%, 50%, 1);
text-decoration: none;
display: inline-flex;
padding: 1em 2em;
border-radius: 0.625em;
border: 0.3215em solid hsla(80, 60%, 50%, 1);
color: #FFFFFF;
text-align: center;
align-items: center;
max-height: 4em;
}
div.postContainer, div.container {
padding: 1em 2em 0;
margin: 0 auto 1em;
max-width: 768px;
}
div.postContainer ~ div.postContainer {
border-top: 5px solid hsla(0, 0%, 75%, 1);
}
div.postContainer > *, div.container > * {
margin: 0 auto;
max-width: 768px;
}
div.postContainer div.post h2, div.container div.content h2 {
margin: 0;
padding: 0;
}
div.postContainer div.image, div.container div.image {
width: 50%;
max-width: 100%;
height: auto;
}
div.postContainer div.image img {
-webkit-border-radius: 0.5em;
-moz-border-radius: 0.5em;
border-radius: 0.5em;
border: 4px solid hsla(0, 0%, 75%, 1);
}
div.container div.image img {
-webkit-border-radius: 50em;
-moz-border-radius: 50em;
border-radius: 50em;
border: 4px solid hsla(0, 0%, 75%, 1);
}
div.postContainer div.image img, div.container div.image img {
min-width: 100%;
width: 100%;
}
footer {
background-color: hsla(80, 60%, 50%, 1);
margin-top: auto;
padding: 3em;
display: block;
color: #FFFFFF;
}
footer .nav {
width: 75%;
float: left;
}
footer .nav ul {
list-style: none;
margin: 0;
padding: 0;
width: 100%;
}
footer .nav ul li {
display: inline-block;
margin: 0 1em;
}
footer .nav ul a {
color: #FFFFFF;
}
footer .date {
width: 25%;
float: right;
}
</style>
</head>
<body>
<header>
<div class="title">
<h1>a surprise hello from rohit</h1>
</div>
<div class="byLine">
<p>Hello I'm Rohit an avid full-stack developer and home lab enthusiast. I love anything and everything
to do with full stack development, home labs, coffee and generally anything to do with tech.</p>
</div>
</header>
<main>
<div class="container">
<h2>$subject</h2>
<div class="content">
$message
</div>
</div>
</main>
<footer>
<div class="nav">
<ul>
<li><a href="https://rohitpai.co.uk/blog">&lt;https://rohitpai.co.uk/blog&gt;</a></li>
<li><a href="https://rohitpai.co.uk/blog/unsubscribe">&lt;Unsubscribe&gt;</a></li>
</ul>
</div>
<div class="date">2023</div>
</footer>
</body>
</html>
EOD;
foreach ($emails as $email)
{
$msg = $this->sendMail($email["email"], $body, $subject);
if (is_array($msg))
{
return $msg;
}
}
return $msg;
}
}
+82 -4
View File
@@ -269,12 +269,38 @@ class blogRoutes implements routesInterface
return $response;
});
$app->delete("/blog/newsletter/{email}", function (Request $request, Response $response, $args)
{
if ($args["email"] == null)
{
$response->getBody()->write(json_encode(array("error" => "Please provide an email")));
return $response->withStatus(400);
}
$message = $this->blogData->deleteNewsletterEmail($args["email"]);
if ($message === "email not found")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Error, email not found")));
return $response->withStatus(404);
}
if ($message === "error")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Error, something went wrong")));
return $response->withStatus(500);
}
return $response;
});
$app->post("/blog/post", function (Request $request, Response $response)
{
$data = $request->getParsedBody();
$files = $request->getUploadedFiles();
$headerImg = $files["headerImg"];
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["abstract"]) || empty($data["dateCreated"]) || empty($data["categories"]))
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["bodyText"]) || empty($data["abstract"]) || empty($data["dateCreated"]) || empty($data["categories"]))
{
// uh oh sent some empty data
$response->getBody()->write(json_encode(array("error" => "Error, empty data sent")));
@@ -288,6 +314,11 @@ class blogRoutes implements routesInterface
return $response->withStatus(400);
}
if (array_key_exists("headerImg", $files))
{
$headerImg = $files["headerImg"];
}
if (empty($files["headerImg"]))
{
$headerImg = null;
@@ -339,19 +370,66 @@ class blogRoutes implements routesInterface
if (empty($files))
{
// uh oh sent some empty data
$response->getBody()->write(json_encode(array("error" => array("message" => "Error, empty data sent"))));
$response->getBody()->write(json_encode(array("error" => "Error, empty data sent")));
return $response->withStatus(400);
}
$message = $this->blogData->uploadHeaderImage($args["id"], $files["headerImg"]);
if (!is_array($message))
{
$response->getBody()->write(json_encode(array("error" => array("message" => $message))));
$response->getBody()->write(json_encode(array("error" => $message)));
return $response->withStatus(500);
}
$response->getBody()->write(json_encode($message));
return $response->withStatus(201);
});
$app->post("/blog/newsletter", function (Request $request, Response $response)
{
$data = $request->getParsedBody();
if (empty($data["subject"]) || empty($data["message"]))
{
// uh oh sent some empty data
$response->getBody()->write(json_encode(array("error" => "Error, empty data sent")));
return $response->withStatus(400);
}
$message = $this->blogData->sendNewsletter(strtolower($data["subject"]), $data["message"]);
if (is_array($message))
{
$response->getBody()->write(json_encode(array("error" => "Error, something went wrong")));
return $response->withStatus(500);
}
$response->getBody()->write(json_encode(array("message" => "Message sent")));
return $response->withStatus(201);
});
$app->post("/blog/newsletter/{email}", function (Request $request, Response $response, $args)
{
if ($args["email"] == null)
{
$response->getBody()->write(json_encode(array("error" => "Please provide an email")));
return $response->withStatus(400);
}
$message = $this->blogData->addNewsletterEmail($args["email"]);
if ($message === "Email already exists")
{
$response->getBody()->write(json_encode(array("message" => "exists")));
return $response->withStatus(409);
}
if (is_array($message) || !$message || $message === "error")
{
$response->getBody()->write(json_encode(array("message" => "Something went wrong")));
return $response->withStatus(500);
}
$response->getBody()->write(json_encode(array("message" => "Thanks for signing up!")));
return $response->withStatus(201);
});
}
}
+1
View File
@@ -5,6 +5,7 @@ namespace api\project;
use api\utils\imgUtils;
use PDO;
use Psr\Http\Message\UploadedFileInterface;
use function api\utils\dbConn;
require_once __DIR__ . "/../utils/config.php";
require_once __DIR__ . "/../utils/imgUtils.php";
+1
View File
@@ -3,6 +3,7 @@
namespace api\timeline;
use PDO;
use function api\utils\dbConn;
require_once __DIR__ . "/../utils/config.php";
+2
View File
@@ -4,6 +4,8 @@ namespace api\user;
use Firebase\JWT\JWT;
use PDO;
use function api\utils\dbConn;
use function api\utils\getSecretKey;
require_once __DIR__ . "/../utils/config.php";
+23 -3
View File
@@ -13,6 +13,7 @@ use Slim\Exception\HttpInternalServerErrorException;
use Slim\Exception\HttpMethodNotAllowedException;
use Slim\Exception\HttpNotFoundException;
use Slim\Psr7\Response;
use Throwable;
use Tuupola\Middleware\JwtAuthentication;
use Tuupola\Middleware\JwtAuthentication\RequestMethodRule;
use Tuupola\Middleware\JwtAuthentication\RequestPathRule;
@@ -84,8 +85,8 @@ class middleware
$app->add(new JwtAuthentication([
"rules" => [
new RequestPathRule([
"path" => ["/api/projectData", "/api/timelineData/[a-z]*", "/api/projectImage/[0-9]*", "/api/logout"],
"ignore" => ["/api/contact", "/api/userData/login", "/api/userData/changePassword"]
"path" => ["/api/projectData", "/api/timelineData/[a-z]*", "/api/projectImage/[0-9]*", "/api/logout", "/api/blog/[a-z]*"],
"ignore" => ["/api/contact", "/api/userData/login", "/api/userData/changePassword", "/api/blog/newsletter/\S*", "/api/blog/newsletter/unsubscribe/\S*"]
]),
new RequestMethodRule([
"ignore" => ["OPTIONS", "GET"]
@@ -133,8 +134,27 @@ class middleware
return $response;
}
});
$app->addErrorMiddleware(true, true, true);
$errorMiddleware = $app->addErrorMiddleware(true, true, true);
$errorHandler = $errorMiddleware->getDefaultErrorHandler();
$errorMiddleware->setDefaultErrorHandler(function (ServerRequestInterface $request, Throwable $exception,
bool $displayErrorDetails,
bool $logErrors,
bool $logErrorDetails
) use ($app, $errorHandler)
{
$statusCode = $exception->getCode() ?: 500;
// Create a JSON response with the error message
$response = $app->getResponseFactory()->createResponse($statusCode);
$response->getBody()->write(json_encode(['error' => $exception->getMessage()]));
return $response;
});
}
}
+1 -1
View File
@@ -151,7 +151,7 @@ div.otherPosts a, div.feeds a {
padding: 0.5em 1em;
}
div.newsletter form input[type="submit"] {
div.newsletter div.form input[type="submit"] {
margin-top: 1em;
padding: 0.5em 1em;
}
+2 -1
View File
@@ -12,11 +12,12 @@ section.catPosts .largePost {
section.categories {
display: flex;
flex-direction: row;
justify-content: center;
justify-content: flex-start;
align-items: center;
flex-wrap: wrap;
width: 100%;
margin-bottom: 5em;
row-gap: 1em;
}
section.categories .btnContainer {
+1 -1
View File
@@ -108,7 +108,7 @@ section.largePost .outerContent .postContent a {
align-self: flex-end;
}
#main .error {
#main .errorFof {
display: table;
width: 100%;
height: 100vh;
+38
View File
@@ -23,6 +23,44 @@
font-weight: bold;
}
.modal-container {
display: block;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.4); /* Background color for the entire screen */
box-sizing: border-box; /* Include padding and border in the element's total width and height */
}
.modal-container.hidden {
display: none;
}
.modal {
position: fixed;
right: 0;
bottom: 0;
width: 40%;
max-width: 550px;
height: auto;
max-height: 70vh;
overflow: auto;
margin: 1.25em;
padding: 1.25em;
box-sizing: border-box; /* Include padding and border in the element's total width and height */
}
.modal-content {
background-color: #DDDDDD;
margin: auto;
padding: 20px;
border: 5px solid var(--primaryHover);
width: 100%;
box-shadow: 0 6px 4px 0 var(--mutedBlack);
}
/**** Media Queries *****/
@media screen and (max-width: 90em) {
+16
View File
@@ -72,6 +72,22 @@
</main>
<div class="modal-container" id="cookiePopup">
<div class="modal">
<div class="modal-content">
<h2><i class="fas fa-cookie-bite"></i> Hey I use cookies btw</h2>
<!-- cookie info text and 2 links disagree and agree, clicking disagree should redirect to google.co.uk -->
<p>Just to let you know, I use cookies to give you the best experience on my blog. By clicking agree
I'll assume that you are happy with it. <a href="/blog/policy/cookie" class="link">Read more</a>
</p>
<div class="flexRow">
<button class="btn btnPrimary" id="cookieAccept">agree</button>
<a href="https://google.co.uk" class="btn btnPrimary">disagree</a>
</div>
</div>
</div>
</div>
<footer class="flexRow">
<div class="nav">
<ul>
+66 -9
View File
@@ -33,6 +33,11 @@ function goToURL(url)
// Get the current URL and split it into an array
let urlArray = url.split('/');
if (localStorage.getItem('cookiePopup') === 'accepted')
{
document.querySelector('#cookiePopup').classList.add('hidden');
}
if (url === '/blog/' || url === '/blog')
{
loadHomeContent();
@@ -84,7 +89,6 @@ function goToURL(url)
}
show404();
}
document.querySelector('#searchBtn').addEventListener('click', _ =>
@@ -107,6 +111,48 @@ document.querySelector('#searchField').addEventListener('keyup', e =>
}
});
document.querySelector('#cookieAccept').addEventListener('click', _ =>
{
document.querySelector('#cookiePopup').classList.add('hidden');
localStorage.setItem('cookiePopup', 'accepted');
});
/**
* Submits the newsletter form
*/
function submitNewsletter()
{
fetch(`/api/blog/newsletter/${document.querySelector('#email').value}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
}).then(res => res.json().then(json =>
{
document.querySelector('#newsletterMessage').classList.remove('hidden');
if (json.message.includes('exists'))
{
document.querySelector('#newsletterMessage').classList.add('error');
document.querySelector('#newsletterMessage').classList.remove('success');
document.querySelector('#newsletterMessage div').innerHTML = 'You\'ve already signed up you silly goose!';
return;
}
if (!res.ok)
{
document.querySelector('#newsletterMessage').classList.add('error');
document.querySelector('#newsletterMessage').classList.remove('success');
document.querySelector('#newsletterMessage div').innerHTML = json.error;
return;
}
document.querySelector('#newsletterMessage div').innerHTML = json.message;
document.querySelector('#newsletterMessage').classList.add('success');
}));
}
/**
* Creates a formatted date
* @param {string} dateString - the date string
@@ -358,21 +404,26 @@ async function createSideContent()
<a href="/blog/post/${featuredPost.title}" class="btn btnPrimary boxShadowIn boxShadowOut">See Post</a>
</div>
<div class="newsletter">
<h3>Sign up to the newsletter</h3>
<form action="newsletter.html">
<h3>Sign up to the newsletter to never miss a new post!</h3>
<div id="newsletterForm" class="form">
<div class="formControl">
<label for="email">Email</label>
<input type="email" id="email" name="email" placeholder="Email" required>
</div>
<input type="submit" value="Sign Up">
</form>
<div class="success hidden" id="newsletterMessage">
<button class="close" type="button" onclick="this.parentElement.classList.toggle('hidden')">&times;</button>
<div></div>
</div>
<input type="submit" value="Sign Up" onclick="submitNewsletter()">
</div>
</div>
<div class="feeds">
<h2>feeds</h2>
<div class="icons">
<a href="https://rohitpai.co.uk/api/blog/feed/rss" class="btn btnPrimary" title="RSS"><i class="fa-solid fa-rss"></i></a>
<a href="https://rohitpai.co.uk/blog/feed/atom" class="btn btnPrimary" title="Atom"><img class="atom" src="/blog/imgs/atomFeed.svg" alt="Atom"></a>
<a href="https://rohitpai.co.uk/blog/feed/json" class="btn btnPrimary" title="JSON"><img class="json" src="/blog/imgs/jsonFeed.svg" alt="JSON"></a>
<a href="https://rohitpai.co.uk/api/blog/feed/atom" class="btn btnPrimary" title="Atom"><img class="atom" src="/blog/imgs/atomFeed.svg" alt="Atom"></a>
<a href="https://rohitpai.co.uk/api/blog/feed/json" class="btn btnPrimary" title="JSON"><img class="json" src="/blog/imgs/jsonFeed.svg" alt="JSON"></a>
</div>
</div>
<div class="categories">
@@ -384,6 +435,12 @@ async function createSideContent()
return sideContent;
}
/**
* Create the meta tags
* @param nameOrProperty - the name or property
* @param attribute - the attribute
* @param value - the value
*/
function createMetaTag(nameOrProperty, attribute, value)
{
let existingTag = document.querySelector(`meta[name="${nameOrProperty}"], meta[property="${nameOrProperty}"]`);
@@ -760,7 +817,7 @@ function loadCookiePolicy()
document.querySelector('#main').innerHTML = `
<div class="policy">
<h3>Cookies Policy</h3>
<p>I only use functional cookies for the blog which includes PHP Session ID, disqus and maybe share this. I think that these are functional cookies, if you don't, you're welcome to exit the site or tell me by emailing me through the email address below, or the contact form on the contact page.</p>
<p>I only use functional cookies for the blog which includes PHP Session ID, disqus. a cookie to disable the cookie popup, and maybe share this. I think that these are functional cookies, if you don't, you're welcome to exit the site or tell me by emailing me through the email address below, or the contact form on the contact section of my main website.</p>
<br>
<a href="mailto:rohit@rohitpai.co.uk" class="link">rohit@rohitpai.co.uk</a>
<br>
@@ -775,7 +832,7 @@ function loadCookiePolicy()
function show404()
{
document.querySelector('#main').innerHTML = `
<div class="error">
<div class="errorFof">
<div class="fof">
<h1>Blog post, Category or page not found</h1>
<a href="/blog/" class="btn btnPrimary">See all blog posts</a>
+116 -36
View File
@@ -15,9 +15,9 @@
--grey: hsla(0, 0%, 39%, 1);
--notAvailableDefault: hsla(0, 0%, 39%, 1);
--notAvailableHover: hsla(0, 0%, 32%, 1);
--mutedGrey: hsla(0, 0%, 78%, 1);
--mutedGrey: hsla(0, 0%, 75%, 1);
--mutedBlack: hsla(0, 0%, 0%, 0.25);
--mutedGreen: hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) + 20%), 0.5);
--mutedGreen: hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) + 20%), 1);
--navBack: hsla(0, 0%, 30%, 1);
/* Font Sizes */
@@ -63,7 +63,7 @@ h2 {
line-height: 2.1875rem;
}
a.btn, button.btn, form input[type="submit"] {
a.btn, button.btn, form input[type="submit"], div.form input[type="submit"] {
text-decoration: none;
display: inline-flex;
padding: 1em 2em;
@@ -75,11 +75,15 @@ a.btn, button.btn, form input[type="submit"] {
max-height: 4em;
}
form input[type="submit"] {
button.btn {
padding: 1.2em 2.2em;
}
form input[type="submit"], div.form input[type="submit"] {
padding: 1.1em 2em;
}
a.btn:hover, button.btn:hover form input[type="submit"]:hover {
a.btn:hover, button.btn:hover, form input[type="submit"]:hover, div.form input[type="submit"]:hover {
border: 0.3215em solid var(--primaryHover);
}
@@ -87,7 +91,7 @@ a.btn:hover::before, a.btn:hover::after {
visibility: hidden;
}
a.btnPrimary, button.btnPrimary, form input[type="submit"] {
a.btnPrimary, button.btnPrimary, form input[type="submit"], div.form input[type="submit"] {
background-color: var(--primaryDefault);
cursor: pointer;
}
@@ -108,12 +112,12 @@ a.btnPrimary[disabled]:hover, button.btnPrimary[disabled]:hover {
border: 0.3215em solid var(--notAvailableHover);
}
a.btnPrimary:hover, button.btnPrimary:hover, form input[type="submit"]:hover {
a.btnPrimary:hover, button.btnPrimary:hover, form input[type="submit"]:hover, div.form input[type="submit"]:hover {
background: var(--primaryHover);
border: 0.3215em solid var(--primaryHover);
}
a.btn:active, button.btn:active, form input[type="submit"]:active {
a.btn:active, button.btn:active, form input[type="submit"]:active, div.form input[type="submit"]:active {
padding: 0.8rem 1.8rem;
}
@@ -129,37 +133,46 @@ a.btn:active, button.btn:active, form input[type="submit"]:active {
text-shadow: 0 6px 4px var(--mutedBlack);
}
form .formControl input:not([type="submit"]).invalid:invalid, form .formControl textarea.invalid:invalid {
form .formControl input:not([type="submit"]).invalid:invalid, form .formControl textarea.invalid:invalid,
div.form .formControl input:not([type="submit"]).invalid:invalid, form .formControl textarea.invalid:invalid {
border: 0.3125em solid var(--errorDefault);
}
form .formControl input:not([type="submit"]).invalid:invalid:focus, form .formControl textarea.invalid:invalid:focus {
form .formControl input:not([type="submit"]).invalid:invalid:focus, form .formControl textarea.invalid:invalid:focus,
div.form .formControl input:not([type="submit"]).invalid:invalid:focus, div.form .formControl textarea.invalid:invalid:focus {
border: 0.3125em solid var(--errorHover);
box-shadow: 0 4px 2px 0 var(--mutedBlack);
}
form .formControl input:not([type="submit"]) {
form .formControl input:not([type="submit"]),
div.form .formControl input:not([type="submit"]) {
height: 3em;
}
form .formControl {
form .formControl,
div.form .formControl {
width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
}
form .formControl.passwordControl {
form .formControl.passwordControl,
div.form .formControl.passwordControl {
display: block;
}
form input[type="submit"] {
form input[type="submit"],
div.form input[type="submit"] {
align-self: flex-start;
}
form .formControl input:not([type="submit"]), form .formControl textarea,
form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar,
form .formControl .ck.ck-editor__main .ck-content, div.menu input:not([type="submit"]) {
form .formControl .ck.ck-editor__main .ck-content, div.menu input:not([type="submit"]),
div.form .formControl input:not([type="submit"]), form .formControl textarea,
div.form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar,
div.form .formControl .ck.ck-editor__main .ck-content, div.menu input:not([type="submit"]) {
width: 100%;
border: 0.3125em solid var(--primaryDefault);
background: none;
@@ -170,30 +183,37 @@ form .formControl .ck.ck-editor__main .ck-content, div.menu input:not([type="sub
padding: 0 0.5em;
}
form .formControl textarea {
form .formControl textarea,
div.form .formControl textarea {
padding: 0.5em;
}
form .formControl input:not([type="submit"]).invalid:invalid, form .formControl textarea.invalid:invalid {
form .formControl input:not([type="submit"]).invalid:invalid, form .formControl textarea.invalid:invalid,
div.form .formControl input:not([type="submit"]).invalid:invalid, form .formControl textarea.invalid:invalid {
border: 0.3125em solid var(--errorDefault);
}
form .formControl input:not([type="submit"]).invalid:invalid:focus, form .formControl textarea.invalid:invalid:focus {
form .formControl input:not([type="submit"]).invalid:invalid:focus, form .formControl textarea.invalid:invalid:focus,
div.form .formControl input:not([type="submit"]).invalid:invalid:focus, form .formControl textarea.invalid:invalid:focus {
border: 0.3125em solid var(--errorHover);
box-shadow: 0 4px 2px 0 var(--mutedBlack);
}
form .formControl input:not([type="submit"]):focus, form .formControl textarea:focus,
form .formControl input:not([type="submit"]):hover, form .formControl textarea:hover,
div.menu input:not([type="submit"]):focus, div.menu input:not([type="submit"]):hover {
div.menu input:not([type="submit"]):focus, div.menu input:not([type="submit"]):hover,
div.form .formControl input:not([type="submit"]):focus, form .formControl textarea:focus,
div.form .formControl input:not([type="submit"]):hover, form .formControl textarea:hover {
border: 0.3125em solid var(--primaryHover);
}
form .formControl input:not([type="submit"]) {
form .formControl input:not([type="submit"]),
div.form .formControl input:not([type="submit"]) {
height: 3em;
}
form .formControl i.fa-eye, form .formControl i.fa-eye-slash {
form .formControl i.fa-eye, form .formControl i.fa-eye-slash,
div.form .formControl i.fa-eye, form .formControl i.fa-eye-slash {
margin-left: -40px;
cursor: pointer;
color: var(--primaryDefault);
@@ -204,7 +224,8 @@ form .formControl input:not([type="submit"]):focus + i.fa-eye-slash {
color: var(--primaryHover);
}
form .formControl .checkContainer {
form .formControl .checkContainer,
div.form .formControl .checkContainer {
display: block;
position: relative;
margin-bottom: 1.25em;
@@ -215,7 +236,8 @@ form .formControl .checkContainer {
user-select: none;
}
form .formControl .checkContainer input {
form .formControl .checkContainer input,
div.form .formControl .checkContainer input {
position: absolute;
opacity: 0;
cursor: pointer;
@@ -223,7 +245,8 @@ form .formControl .checkContainer input {
width: 0;
}
form .formControl .checkContainer .checkmark {
form .formControl .checkContainer .checkmark,
div.form .formControl .checkContainer .checkmark {
position: absolute;
top: 1.25em;
left: 0;
@@ -232,29 +255,35 @@ form .formControl .checkContainer .checkmark {
background-color: var(--mutedGrey);
}
form .formControl .checkContainer:hover input ~ .checkmark {
form .formControl .checkContainer:hover input ~ .checkmark,
div.form .formControl .checkContainer:hover input ~ .checkmark {
background-color: var(--grey);
}
form .formControl .checkContainer input:checked ~ .checkmark {
form .formControl .checkContainer input:checked ~ .checkmark,
div.form .formControl .checkContainer input:checked ~ .checkmark {
background-color: var(--primaryDefault);
}
form .formControl .checkContainer input:checked:hover ~ .checkmark {
form .formControl .checkContainer input:checked:hover ~ .checkmark,
div.form .formControl .checkContainer input:checked:hover ~ .checkmark {
background-color: var(--primaryHover);
}
form .formControl .checkContainer .checkmark:after {
form .formControl .checkContainer .checkmark:after,
div.form .formControl .checkContainer .checkmark:after {
content: "";
position: absolute;
display: none;
}
form .formControl .checkContainer input:checked ~ .checkmark:after {
form .formControl .checkContainer input:checked ~ .checkmark:after,
div.form .formControl .checkContainer input:checked ~ .checkmark:after {
display: block;
}
form .formControl .checkContainer .checkmark:after {
form .formControl .checkContainer .checkmark:after,
div.form .formControl .checkContainer .checkmark:after {
left: 9px;
top: 5px;
width: 5px;
@@ -324,10 +353,61 @@ a.link:hover::after {
visibility: visible;
}
/*.link span {*/
/* visibility: hidden;*/
/*}*/
div.error, div.success {
color: #FFFFFF;
padding: 0.5em 0.8em;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
align-self: flex-start;
flex-direction: row-reverse;
position: relative;
height: 75px;
visibility: visible;
overflow: hidden;
-webkit-transition: all 0.5s ease-in-out;
-moz-transition: all 0.5s ease-in-out;
-ms-transition: all 0.5s ease-in-out;
-o-transition: all 0.5s ease-in-out;
transition: all 0.5s ease-in-out;
opacity: 1;
margin-top: 1em;
}
/*.link:hover span {*/
/* visibility: visible;*/
/*}*/
div.error {
background: var(--errorDefault);
}
div.success {
background-color: var(--primaryHover);
}
div.error button, div.success button {
border: none;
background: none;
outline: none;
cursor: pointer;
color: #FFFFFF;
font-size: 1.25rem;
margin-top: -5px;
position: absolute;
transform: translate(0, 0);
transform-origin: 0 0;
right: 10px;
top: 10px;
}
div.error.hidden, div.success.hidden {
opacity: 0;
visibility: hidden;
height: 0;
margin: 0;
padding: 0;
}
div.error button:hover, div.success button:hover {
text-shadow: -1px 2px var(--mutedBlack);
}
+4
View File
@@ -278,4 +278,8 @@ section#editPost table td, th {
section#editPost form {
margin-bottom: 2em;
}
section#newsletter form {
margin: 0 5em;
}
-57
View File
@@ -66,64 +66,7 @@ div#login input[type=submit]{
margin: 0;
}
div.error, div.success {
color: #FFFFFF;
padding: 0.5em 0.8em;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
align-self: flex-start;
flex-direction: row-reverse;
position: relative;
height: 75px;
visibility: visible;
overflow: hidden;
-webkit-transition: all 0.5s ease-in-out;
-moz-transition: all 0.5s ease-in-out;
-ms-transition: all 0.5s ease-in-out;
-o-transition: all 0.5s ease-in-out;
transition: all 0.5s ease-in-out;
opacity: 1;
margin-top: 1em;
}
div.error {
background: var(--errorDefault);
}
div.success {
background-color: var(--primaryHover);
}
div.error button, div.success button {
border: none;
background: none;
outline: none;
cursor: pointer;
color: #FFFFFF;
font-size: 1.25rem;
margin-top: -5px;
position: absolute;
transform: translate(0, 0);
transform-origin: 0 0;
right: 10px;
top: 10px;
}
div.error.hidden, div.success.hidden {
opacity: 0;
visibility: hidden;
height: 0;
margin: 0;
padding: 0;
}
div.error button:hover, div.success button:hover {
text-shadow: -1px 2px var(--mutedBlack);
}
div.btnContainer {
width: 100%;
+1 -1
View File
@@ -61,7 +61,7 @@ nav.sideNav ul li.dropdown ul {
nav.sideNav ul li.dropdown ul.active {
transition: max-height ease-in 400ms;
max-height: 15rem;
max-height: 20rem;
}
nav.sideNav ul li.dropdown ul li {
+33 -2
View File
@@ -7,7 +7,6 @@
<script src="https://kit.fontawesome.com/ed3c25598e.js" crossorigin="anonymous"></script>
<script src="js/CKEditor/ckeditor.js"></script>
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<nav class="sideNav">
@@ -40,6 +39,11 @@
Edit Blog Post
</a>
</li>
<li>
<a href="#" id="goToNewsletter" class="link">
Send Newsletter
</a>
</li>
</ul>
</li>
<li><a href="#" id="logout">Logout</a></li>
@@ -135,7 +139,7 @@
</div>
</div>
</div>
</div>
</div>
</section>
@@ -288,6 +292,33 @@
</form>
</section>
<section id="newsletter">
<h2>newsletter</h2>
<form action="" id="sendNewsletterForm" method="POST">
<div class="formControl">
<label for="newsletterSubject">Subject</label>
<input type="text" id="newsletterSubject" name="newsletterSubject" required>
</div>
<div class="formControl">
<label for="CKEditorNewsletter">Message</label>
<div id="CKEditorNewsletter">
</div>
</div>
<div class="error hidden" id="newsletterError">
<button class="close" type="button">&times;</button>
<div></div>
</div>
<div class="success hidden" id="newsletterSuccess">
<button class="close" type="button">&times;</button>
<div></div>
</div>
<input type="submit" class="btn btnPrimary boxShadowIn boxShadowOut" value="Send newsletter">
</form>
</section>
</main>
<script src="js/editor.js"></script>
+43 -3
View File
@@ -70,7 +70,7 @@ document.addEventListener('DOMContentLoaded', () =>
}));
// CKEditor stuff
createEditors("CKEditorAddPost", "CKEditorEditPost");
createEditors('CKEditorAddPost', 'CKEditorEditPost', 'CKEditorNewsletter');
});
@@ -378,10 +378,43 @@ document.querySelector("#editPostForm").addEventListener("submit", e =>
window.location.href = "./";
return;
}
showErrorMessage(json.error.message, "editPost");
showErrorMessage(json.error, 'editPost');
}));
});
document.querySelector('#sendNewsletterForm').addEventListener('submit', e =>
{
e.preventDefault();
let data = new FormData();
data.append('subject', document.querySelector('#newsletterSubject').value);
data.append('message', editors['CKEditorNewsletter'].getData());
fetch('/api/blog/newsletter', {
method: 'POST',
body: data,
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('token'),
},
}).then(res => res.json().then(json =>
{
if (res.ok)
{
document.querySelector('#sendNewsletterForm').reset();
editors['CKEditorNewsletter'].setData('');
showSuccessMessage('Newsletter sent successfully', 'newsletter');
return;
}
if (res.status === 401)
{
window.location.href = './';
return;
}
showErrorMessage(json.error, 'newsletter');
}));
});
document.querySelector("#goToCV").addEventListener("click", () =>
@@ -422,6 +455,13 @@ document.querySelector("#goToEditPost").addEventListener("click", () =>
document.querySelector("#blog").classList.add("active");
});
document.querySelector('#goToNewsletter').addEventListener('click', () =>
{
textareaLoaded = false;
addActiveClass('goToNewsletter');
goToPage('newsletter');
});
document.querySelector("#logout").addEventListener("click", () =>
{
fetch("/api/user/logout").then(res =>
+7 -1
View File
@@ -63,7 +63,13 @@ document.querySelector("#login form").addEventListener("submit", e =>
showErrorMessage("Please type in a username and password.", "login");
return;
}
showErrorMessage("Invalid username or password.", "login");
if (res.status === 401)
{
showErrorMessage('Invalid username or password.', 'login');
return;
}
showErrorMessage(json.error, 'login');
}));
return;
}