Skip to content

Commit c0fec83

Browse files
Next: Blog Website Complete
1 parent fdb5f77 commit c0fec83

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+11316
-1757
lines changed

Projects/blog-nextjs/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ yarn-error.log*
2626
.pnpm-debug.log*
2727

2828
# local env files
29+
.env
2930
.env*.local
3031

3132
# vercel
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import { useState, useEffect } from "react";
2+
import Notification from "../ui/notification";
3+
import classes from "./contact-form.module.css";
4+
5+
const sendContactData = async (contactDetails) => {
6+
const response = await fetch("/api/contact", {
7+
method: "POST",
8+
body: JSON.stringify(contactDetails),
9+
headers: {
10+
"Content-Type": "application/json",
11+
},
12+
});
13+
14+
const data = await response.json();
15+
16+
if (!response.ok) {
17+
throw new Error(data.message || "Something went wrong!");
18+
}
19+
};
20+
21+
const ContactForm = (props) => {
22+
const [enteredEmail, setEnteredEmail] = useState("");
23+
const [enteredName, setEnteredName] = useState("");
24+
const [enteredMessage, setEnteredMessage] = useState("");
25+
26+
const [reqStatus, setReqStatus] = useState(); // "pending" | "success" | " error"
27+
const [reqError, setReqError] = useState();
28+
29+
useEffect(() => {
30+
if (reqStatus === "success" || reqStatus === "error") {
31+
const timer = setTimeout(() => {
32+
setReqStatus(null);
33+
setReqError(null);
34+
}, 3000);
35+
36+
return () => clearTimeout(timer);
37+
}
38+
}, [reqStatus]);
39+
40+
const sendMessageHandler = async (event) => {
41+
event.preventDefault();
42+
43+
setReqStatus("pending");
44+
45+
try {
46+
await sendContactData({
47+
email: enteredEmail,
48+
name: enteredName,
49+
message: enteredMessage,
50+
});
51+
52+
setEnteredMessage("");
53+
setEnteredEmail("");
54+
setEnteredName("");
55+
56+
setReqStatus("success");
57+
} catch (error) {
58+
console.log(error);
59+
setReqStatus("error");
60+
}
61+
};
62+
63+
let notification;
64+
65+
if (reqStatus === "pending") {
66+
notification = {
67+
status: "pending",
68+
title: "Sending message...",
69+
message: "Your message is on its way!",
70+
};
71+
}
72+
73+
if (reqStatus === "success") {
74+
notification = {
75+
status: "success",
76+
title: "Success!",
77+
message: "Message sent successfully!",
78+
};
79+
}
80+
81+
if (reqStatus === "error") {
82+
notification = {
83+
status: "error",
84+
title: "Error!",
85+
message: reqError,
86+
};
87+
}
88+
89+
return (
90+
<section className={classes.contact}>
91+
<h1>How can I help you?</h1>
92+
<form className={classes.form} onSubmit={sendMessageHandler}>
93+
<div className={classes.controls}>
94+
<div className={classes.control}>
95+
<label htmlFor="email">Your Email</label>
96+
<input
97+
type="email"
98+
id="email"
99+
required
100+
value={enteredEmail}
101+
onChange={(event) => setEnteredEmail(event.target.value)}
102+
/>
103+
</div>
104+
<div className={classes.control}>
105+
<label htmlFor="name">Your Name</label>
106+
<input
107+
type="text"
108+
id="name"
109+
required
110+
value={enteredName}
111+
onChange={(event) => setEnteredName(event.target.value)}
112+
/>
113+
</div>
114+
</div>
115+
<div className={classes.control}>
116+
<label htmlFor="message">Your Message</label>
117+
<textarea
118+
id="message"
119+
rows="5"
120+
required
121+
value={enteredMessage}
122+
onChange={(event) => setEnteredMessage(event.target.value)}
123+
></textarea>
124+
</div>
125+
126+
<div className={classes.actions}>
127+
<button>Send Message</button>
128+
</div>
129+
</form>
130+
131+
{notification && (
132+
<Notification
133+
status={notification.status}
134+
title={notification.title}
135+
message={notification.message}
136+
/>
137+
)}
138+
</section>
139+
);
140+
};
141+
142+
export default ContactForm;
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
.contact {
2+
margin: var(--size-8) auto;
3+
border-radius: 6px;
4+
background-color: var(--color-grey-100);
5+
width: 90%;
6+
max-width: 50rem;
7+
padding: var(--size-4);
8+
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
9+
font-size: var(--size-6);
10+
}
11+
12+
.contact h1 {
13+
font-size: var(--size-8);
14+
margin: var(--size-4) 0;
15+
text-align: left;
16+
}
17+
18+
.form label {
19+
display: block;
20+
font-family: 'Oswald', sans-serif;
21+
font-weight: bold;
22+
margin: var(--size-2) 0 var(--size-1) 0;
23+
}
24+
25+
.form input,
26+
.form textarea {
27+
font: inherit;
28+
padding: var(--size-1);
29+
border-radius: 4px;
30+
width: 100%;
31+
border: 1px solid var(--color-grey-400);
32+
background-color: var(--color-grey-50);
33+
resize: none;
34+
}
35+
36+
.controls {
37+
display: flex;
38+
column-gap: 1rem;
39+
flex-wrap: wrap;
40+
}
41+
42+
.control {
43+
flex: 1;
44+
min-width: 10rem;
45+
}
46+
47+
.actions {
48+
margin-top: var(--size-4);
49+
text-align: right;
50+
}
51+
52+
.form button {
53+
font: inherit;
54+
cursor: pointer;
55+
background-color: var(--color-primary-700);
56+
border: 1px solid var(--color-primary-700);
57+
padding: var(--size-2) var(--size-4);
58+
border-radius: 4px;
59+
color: var(--color-primary-50);
60+
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
61+
}
62+
63+
.form button:hover {
64+
background-color: var(--color-primary-500);
65+
border-color: var(--color-primary-500);
66+
}
67+
68+
@media (min-width: 768px) {
69+
.contact h1 {
70+
font-size: var(--size-16);
71+
text-align: center;
72+
}
73+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import PostGrid from "../posts/posts-grid";
2+
import classes from "./featured-posts.module.css";
3+
4+
const FeaturedPost = (props) => {
5+
const { posts } = props;
6+
7+
return (
8+
<section className={classes.latest}>
9+
<h2>Featured Posts</h2>
10+
11+
<PostGrid posts={posts} />
12+
</section>
13+
);
14+
};
15+
16+
export default FeaturedPost;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
.latest {
2+
width: 90%;
3+
max-width: 80rem;
4+
margin: var(--size-8) auto;
5+
}
6+
7+
.latest h2 {
8+
font-size: var(--size-8);
9+
color: var(--color-grey-800);
10+
text-align: center;
11+
}
12+
13+
@media (min-width: 768px) {
14+
.latest h2 {
15+
font-size: var(--size-16);
16+
}
17+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import Image from "next/image";
2+
3+
import classes from "./hero.module.css";
4+
5+
const Hero = () => {
6+
return (
7+
<section className={classes.hero}>
8+
<div className={classes.image}>
9+
<Image src="/images/site/max.png" height={300} width={300} alt="Hero" />
10+
</div>
11+
12+
<h1>Hi, I'm Max</h1>
13+
14+
<p>
15+
I blog about Web Development - especially frameworks like React and
16+
Next.js
17+
</p>
18+
</section>
19+
);
20+
};
21+
22+
export default Hero;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
.hero {
2+
text-align: center;
3+
background-image: linear-gradient(
4+
to bottom,
5+
var(--color-grey-900),
6+
var(--color-grey-800)
7+
);
8+
padding: var(--size-8) 0;
9+
}
10+
11+
.image {
12+
width: 300px;
13+
height: 300px;
14+
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.2);
15+
border-radius: 50%;
16+
overflow: hidden;
17+
background-color: var(--color-grey-700);
18+
margin: auto;
19+
}
20+
21+
.image img {
22+
object-fit: cover;
23+
object-position: top;
24+
width: 100%;
25+
height: 100%;
26+
}
27+
28+
.hero h1 {
29+
font-size: var(--size-16);
30+
margin: var(--size-4) 0;
31+
color: var(--color-grey-300);
32+
}
33+
34+
.hero p {
35+
font-size: var(--size-6);
36+
color: var(--color-grey-200);
37+
width: 90%;
38+
max-width: 40rem;
39+
margin: auto;
40+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Fragment } from "react";
2+
import MainNavigation from "./main-navigation";
3+
4+
const Layout = (props) => {
5+
return (
6+
<Fragment>
7+
<MainNavigation />
8+
<main>{props.children}</main>
9+
</Fragment>
10+
);
11+
};
12+
13+
export default Layout;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import classes from "./logo.module.css";
2+
3+
const Logo = (props) => {
4+
return <div className={classes.logo}>Max's Next Blog</div>;
5+
};
6+
7+
export default Logo;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.logo {
2+
text-transform: uppercase;
3+
font-size: var(--size-5);
4+
font-weight: bold;
5+
font-family: 'Oswald', sans-serif;
6+
color: var(--color-grey-50);
7+
}
8+
9+
@media (min-width: 768px) {
10+
.logo {
11+
font-size: var(--size-8);
12+
}
13+
}

0 commit comments

Comments
 (0)