-
Notifications
You must be signed in to change notification settings - Fork 145
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Gaby's Happy Thoughts Project w11 #109
base: main
Are you sure you want to change the base?
Changes from all commits
3910217
22a7e43
f3c038e
91ba452
19246ab
3761d1b
c9cec52
dca0719
f5266e1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,15 @@ | ||
<h1 align="center"> | ||
<a href=""> | ||
<img src="/src/assets/happy-thoughts.svg" alt="Project Banner Image"> | ||
</a> | ||
</h1> | ||
|
||
# Happy thoughts Project | ||
|
||
In this week's project, you'll be able to practice your React state skills by fetching and posting data to an API. | ||
|
||
## Getting Started with the Project | ||
|
||
### Dependency Installation & Startup Development Server | ||
|
||
Once cloned, navigate to the project's root directory and this project uses npm (Node Package Manager) to manage its dependencies. | ||
|
||
The command below is a combination of installing dependencies, opening up the project on VS Code and it will run a development server on your terminal. | ||
|
||
```bash | ||
npm i && code . && npm run dev | ||
``` | ||
|
||
### The Problem | ||
|
||
Describe how you approached to problem, and what tools and techniques you used to solve it. How did you plan? What technologies did you use? If you had more time, what would be next? | ||
I started by created the components I believed I needed. My goal was to have the App.jsx as clean as possible and let the components handle the tasks: GET, POST and the returns. I realised quite late that I couldnt insert the new post into the Thoughts list without merging most of the code into one component. Tried many different options but still haven't managed figuring it out. So with more time I would probably create more, but simpler components or just divide it differently. I would also have liked to reach more of the strechgoals. | ||
|
||
### View it live | ||
I managed quite far with the class material but halfway I got stuck and used ChatGPT, Google and previous students code as my help. | ||
|
||
Every project should be deployed somewhere. Be sure to include the link to the deployed project so that the viewer can click around and see what it's all about. | ||
Something I find very challenged is to plan correctly and I feel that I don't know what is right and wrong and doubt my solutions a lot. | ||
|
||
## Instructions | ||
### View it live | ||
|
||
<a href="instructions.md"> | ||
See instructions of this project | ||
</a> | ||
https://gabyshappythoughts.netlify.app/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,16 @@ | ||
import React from "react" | ||
import { Header } from "./components/header/Header"; | ||
import { PostThoughts } from "./components/postThoughts/PostThoughts"; | ||
import { Thoughts } from "./components/thoughts/Thoughts" | ||
import { Footer } from "./components/footer/Footer"; | ||
|
||
export const App = () => { | ||
return <div>Find me in src/app.jsx!</div>; | ||
return ( | ||
<div> | ||
<Header /> | ||
<PostThoughts /> | ||
<Thoughts /> | ||
<Footer /> | ||
</div> | ||
); | ||
}; |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not a component 👀 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const URL = "https://happy-thoughts-ux7hkzgmwa-uc.a.run.app/thoughts" |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice and clean! ✨ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import "./footer.css"; | ||
|
||
export const Footer = () => { | ||
return ( | ||
<div className="footer"> | ||
<h4>Thank you for your sparkle</h4> | ||
<h5>© 2024 Copyright - Developed by Gabriella Iofe</h5> | ||
</div> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
.footer { | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
} | ||
|
||
h4 { | ||
font-family: Courier New, monospace; | ||
font-size: 16px; | ||
margin-bottom: 5px; | ||
|
||
} | ||
|
||
h5 { | ||
font-family: Courier New, monospace; | ||
font-size: 11px; | ||
margin-top: auto; | ||
margin-bottom: 10px; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import "./header.css"; | ||
|
||
export const Header = () => { | ||
return ( | ||
<section className="header"> | ||
<h1>Happy Thoughts Project</h1> | ||
<h2>by Gabriella Iofe</h2> | ||
</section> | ||
) | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
.header { | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
} | ||
|
||
h1 { | ||
font-family: Courier New, monospace; | ||
font-size: 23px; | ||
font-weight: bold; | ||
margin-bottom: 5px; | ||
|
||
} | ||
|
||
h2 { | ||
font-family: Courier New, monospace; | ||
font-size: 15px; | ||
margin-top: auto; | ||
margin-bottom: 30px; | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice error handling, and really good that you remembered the label! However, something more needs to happen when you post a thought:
so that the feed updates with the new thought |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { useState } from "react"; | ||
import { URL } from "../ApiUrl"; | ||
import "./postThoughts.css"; | ||
|
||
|
||
|
||
export const PostThoughts = () => { | ||
const [body, setBody] = useState('') | ||
const [loading, setLoading] = useState(false) | ||
const [errorMessage, setErrorMessage] = useState(''); | ||
|
||
|
||
//This functions POSTs a happy thought to the API | ||
const handleSubmit = async (event) => { | ||
event.preventDefault() | ||
setLoading(true) /* Start loading on submit */ | ||
setErrorMessage(''); | ||
|
||
// Validation: Check if message is between 5 and 140 characters | ||
if (body.length < 5 || body.length > 140) { | ||
setErrorMessage("Message must be between 5 and 140 characters."); | ||
setLoading(false); | ||
return; | ||
} | ||
|
||
try { | ||
const response = await fetch(URL, { | ||
method: "POST", | ||
headers: { | ||
//this is a way to send extra information to our API. | ||
"content-type": "application/json; charset=utf-8" | ||
}, | ||
body: JSON.stringify({message: body})//command to tell API the message (and what i call it in my code "body" should be fetched) | ||
}); | ||
|
||
if (response.ok) { | ||
const recentBody = await response.json(); //Get the new post | ||
setBody((previousBody) => [recentBody, ...previousBody]); // Update the post state | ||
setBody(''); //this clears the input field after its been posted | ||
|
||
} else { | ||
const errorData = await response.json(); | ||
setErrorMessage(errorData.message || "An error occurred. Please try again."); | ||
} | ||
} catch (error) { | ||
setErrorMessage("Network error. Please try again later."); | ||
console.log("error:", error); | ||
} finally { | ||
setLoading(false) /* Stops the loading */ | ||
} | ||
} | ||
|
||
|
||
return ( | ||
<section className="post-thoughts-container"> | ||
<form className="post-thoughts-form" onSubmit={handleSubmit}> | ||
<label> | ||
<h3>What's making you happy right now?</h3> | ||
<textarea | ||
className="textarea" | ||
value={body} | ||
placeholder="Share your happy thought here..." | ||
onChange={(e) => setBody(e.target.value)} | ||
/> | ||
</label> | ||
{errorMessage && <p className="error-message">{errorMessage}</p>} | ||
<button | ||
className="send-button" | ||
type="submit" | ||
disabled={loading} | ||
> | ||
{loading ? "Loading..." : "💖 Send Happy Thought 💖"}{/* Loading? If True show Loading... If False show 💖Send Happy Thought💖*/} | ||
</button> | ||
</form> | ||
</section> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
.post-thoughts-container { | ||
background-color: rgb(239, 236, 237); | ||
border: solid rgb(209, 208, 209) 2px; | ||
padding: 15px; | ||
margin: 0px 15px 25px 15px; | ||
box-shadow: 6px 6px 0px 0px rgb(0, 0, 0); | ||
} | ||
|
||
label { | ||
color: black; | ||
} | ||
|
||
h3 { | ||
font-family:"Segoe UI", Arial, sans-serif; | ||
font-size: 16px; | ||
} | ||
|
||
.textarea { | ||
background-color: white; | ||
height: 50px; | ||
width: 100%; | ||
margin: 10px 0px 10px 0px; | ||
padding: 5px; | ||
font-family: Courier New, monospace; | ||
font-size: 13px; | ||
} | ||
|
||
.error-message {} | ||
|
||
.send-button { | ||
padding: 9px; | ||
border-radius: 30px; | ||
border: none; | ||
background-color: rgb(250, 144, 160); | ||
font-size: 13px; | ||
font-family: "Segoe UI", Arial, sans-serif; | ||
} | ||
|
||
@media only screen and (min-width: 768px) { | ||
.post-thoughts-container { | ||
width: 417px; | ||
margin-left: auto; | ||
margin-right: auto; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
|
||
import { useState,useEffect } from "react" | ||
import { URL } from "../ApiUrl"; | ||
import { formatDistance } from 'date-fns'; | ||
import "./thoughts.css"; | ||
|
||
//A function to fetch and display the message object in a list | ||
export const Thoughts = () => { | ||
const [thoughts, setThoughts] = useState([]) | ||
|
||
useEffect(() => { | ||
const fetchThoughts = async () => { | ||
try { | ||
const response = await fetch(URL) | ||
const data = await response.json() | ||
setThoughts(data) | ||
console.log("Thought is", data) | ||
} catch (error) { | ||
console.log("Error fetching thoughts is:", error) | ||
} | ||
} | ||
fetchThoughts() | ||
}, []); | ||
|
||
|
||
const addLike = async (postId) => { | ||
try { | ||
fetch(`${URL}/${postId}/like`, {method: "POST"}) | ||
|
||
//Update the state with a like | ||
setThoughts((prevThoughts) => | ||
prevThoughts.map((thought) => | ||
thought._id === postId ? { ...thought, hearts: thought.hearts + 1 } : thought | ||
)) | ||
} catch (error) { | ||
console.error("Error liking the post is:", error) | ||
} | ||
} | ||
|
||
|
||
|
||
return ( | ||
<section> | ||
<ul className="thoughts-list-container"> | ||
{thoughts.map((thought) => ( | ||
<li | ||
className="thought-list-item" | ||
key={thought._id} // I tried using both index and thought._id. the second option uses the unique number for each message whereas index creates its own unique numbers. | ||
> | ||
<p className="thought-message">{thought.message}</p> | ||
<div className="time-count-container"> | ||
<div> | ||
<button | ||
aria-label={`Like post with message: ${thought.message}`} | ||
className={`like-button ${thought.hearts === 0 ? 'notLikedClass' : 'likedClass'}`} | ||
onClick={() => addLike(thought._id)} | ||
> | ||
<span className="heart-icon" aria-label="Like icon">💖</span> {/* Target heart icon */} | ||
</button> | ||
<span aria-label="Number of likes"> x {thought.hearts}</span> {/* Display likes outside the button */} | ||
</div> | ||
<div className="time-container"> | ||
<p>{formatDistance(new Date(thought.createdAt), Date.now(), { addSuffix: true })}</p> {/*npm install date-fns --save to */} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⭐ |
||
</div> | ||
</div> | ||
</li> | ||
))} | ||
</ul> | ||
</section> | ||
); | ||
}; | ||
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
|
||
|
||
.thoughts-list-container { | ||
list-style: none; | ||
padding: 0; | ||
margin: 0px 15px 0px 15px; | ||
} | ||
|
||
.thought-list-item { | ||
border: 1px solid black; | ||
margin-bottom: 25px; | ||
padding: 15px; | ||
box-shadow: 6px 6px 0px 0px rgb(0, 0, 0); | ||
} | ||
|
||
.thought-message { | ||
font-family: Courier New, monospace; | ||
margin: 0; | ||
margin-bottom: 5px; | ||
overflow: hidden; | ||
|
||
} | ||
|
||
.time-count-container { | ||
display: flex; | ||
justify-content: space-between; | ||
align-items: flex-end; | ||
|
||
} | ||
|
||
|
||
.like-button { | ||
border-radius: 20px; | ||
padding: 8px 9px 8px 9px; | ||
border: none; | ||
} | ||
|
||
.time-count-container { | ||
font-family: "Segoe UI", Arial, sans-serif; | ||
font-size: 13px; | ||
} | ||
|
||
.time-count-container p { | ||
margin: 0; | ||
padding-bottom: 8px; | ||
} | ||
|
||
|
||
|
||
@media only screen and (min-width: 768px) { | ||
.thoughts-list-container { | ||
width: 450px; | ||
margin-left: auto; | ||
margin-right: auto; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to import React