-
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
Happy Thoughts App - Jenny A #103
base: main
Are you sure you want to change the base?
Changes from 6 commits
73951f1
205f56a
81bcf97
3583dbe
bdd3cd5
2906b61
fa6793a
b4e3c24
31e67f0
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,12 @@ | ||
<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. | ||
A project to be able to practice React state skills by fetching and posting data to an API. | ||
|
||
## Getting Started with the Project | ||
## The Problem | ||
|
||
### 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? | ||
During this project, I faced several challenges including structuring the components effectively, managing state between components, and implementing the like functionality. A significant part of the problem-solving involved handling data flow from the API, particularly with the POST requests for new thoughts and likes. Understanding how to implement and display timestamps required exploring different solutions, ultimately leading to using the date-fns library for more elegant time formatting. The project also involved careful consideration of user experience elements, such as error handling for the thought submission form and ensuring proper alignment of UI elements like the heart button and like counter. | ||
|
||
### View it live | ||
|
||
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. | ||
|
||
## Instructions | ||
|
||
<a href="instructions.md"> | ||
See instructions of this project | ||
</a> | ||
https://happythoughtsapp-byjenny.netlify.app/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,43 @@ | ||
// App.jsx - Main component that orchestrates the entire application | ||
import './index.css'; | ||
import { ListThoughts } from "./components/ListThoughts"; | ||
import { SubmitThought } from './components/SubmitThought'; | ||
import { useState, useEffect } from 'react'; | ||
|
||
export const App = () => { | ||
return <div>Find me in src/app.jsx!</div>; | ||
}; | ||
// State to store all thoughts from the API | ||
const [thoughts, setThoughts] = useState([]); | ||
|
||
// Handler function to add new thoughts to the beginning of the list | ||
const handleNewThought = (newThought) => { | ||
setThoughts(prevThoughts => [newThought, ...prevThoughts]); | ||
}; | ||
|
||
// Fetch initial thoughts when component mounts | ||
useEffect(() => { | ||
fetch('https://happy-thoughts-ux7hkzgmwa-uc.a.run.app/thoughts') | ||
.then((res) => res.json()) | ||
.then((json) => { | ||
setThoughts(json); | ||
}); | ||
}, []); | ||
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. Some error handling would be nice 👀 |
||
|
||
return ( | ||
<div className="app"> | ||
<div className="main-title-container"> | ||
<h1>Project Happy Thoughts</h1> | ||
<p>Made by Jenny Andersén</p> | ||
</div> | ||
|
||
{/* Form component for submitting new thoughts */} | ||
<div className="submit-thought-card"> | ||
<SubmitThought onSubmit={handleNewThought} /> | ||
</div> | ||
|
||
{/* List component that displays all thoughts */} | ||
<div className="main-container"> | ||
<ListThoughts thoughts={thoughts} /> | ||
</div> | ||
</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. ⭐ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// CreatedAt.jsx | ||
|
||
import { formatDistanceToNow } from 'date-fns'; | ||
|
||
export const CreatedAt = ({ createdAt }) => { | ||
return ( | ||
<span className="timestamp"> | ||
{/* Converts timestamp to "X time ago" format */} | ||
{formatDistanceToNow(new Date(createdAt), { addSuffix: true })} | ||
</span> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// LikeButton.jsx | ||
|
||
import { useState } from 'react'; | ||
|
||
export const LikeButton = ({ thoughtId, initialHearts }) => { | ||
// State to track if like action is in progress | ||
const [isLiking, setIsLiking] = useState(false); | ||
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. What's the purpose of this? 👀 |
||
// State to track number of likes | ||
const [hearts, setHearts] = useState(initialHearts); | ||
|
||
// Handler for when like button is clicked | ||
const handleLike = () => { | ||
if (isLiking) return; // Prevent multiple clicks | ||
|
||
setIsLiking(true); | ||
// Send POST request to like the thought | ||
fetch(`https://happy-thoughts-ux7hkzgmwa-uc.a.run.app/thoughts/${thoughtId}/like`, { | ||
method: 'POST' | ||
}) | ||
.then(res => res.json()) | ||
.then(updatedThought => { | ||
setHearts(updatedThought.hearts); // Update like count | ||
setIsLiking(false); // Enable button again | ||
}); | ||
}; | ||
|
||
return ( | ||
<div className="like-container"> | ||
<button | ||
onClick={handleLike} | ||
className={`heart-button ${isLiking ? 'liking' : ''}`} | ||
disabled={isLiking} | ||
> | ||
❤️ | ||
</button> | ||
<span className="heart-count"> | ||
x {hearts} | ||
</span> | ||
</div> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// ListThoughts.jsx | ||
import { ThoughtCard } from './ThoughtCard'; | ||
|
||
export const ListThoughts = ({ thoughts }) => { | ||
return ( | ||
<div className="thoughts-list"> | ||
{/* Map through thoughts array and create a card for each thought */} | ||
{thoughts.map((thought) => ( | ||
<ThoughtCard key={thought._id} thought={thought} /> | ||
))} | ||
</div> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
// SubmitThought.jsx | ||
|
||
// components/SubmitThought.jsx | ||
import { useState } from 'react'; | ||
|
||
export const SubmitThought = ({ onSubmit }) => { | ||
// State for the input field value | ||
const [message, setMessage] = useState(''); | ||
// State for error messages | ||
const [error, setError] = useState(''); | ||
|
||
// Handler for form submission | ||
const handleSubmit = (event) => { | ||
event.preventDefault(); | ||
|
||
// Clear any previous error messages | ||
setError(''); | ||
|
||
// Validate message length | ||
if (message.length === 0) { | ||
setError('Please write a message'); | ||
return; | ||
} | ||
if (message.length < 5) { | ||
setError('Message must be at least 5 characters long'); | ||
return; | ||
} | ||
if (message.length > 140) { | ||
setError('Message cannot be longer than 140 characters'); | ||
return; | ||
} | ||
|
||
// Send POST request to create new thought | ||
fetch('https://happy-thoughts-ux7hkzgmwa-uc.a.run.app/thoughts', { | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json' | ||
}, | ||
body: JSON.stringify({ message: message }) | ||
}) | ||
.then(res => res.json()) | ||
.then(newThought => { | ||
// Check if the API returned an error | ||
if (newThought.error) { | ||
setError(newThought.error); | ||
return; | ||
} | ||
setMessage(''); // Clears the input | ||
onSubmit(newThought); // Updates the list | ||
}) | ||
.catch(() => { | ||
setError('Something went wrong. Please try again.'); | ||
}); | ||
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 ⭐ |
||
}; | ||
|
||
return ( | ||
<form onSubmit={handleSubmit} className="submit-form"> | ||
<h2>What's making you happy right now?</h2> | ||
<textarea | ||
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. Don't forget labels! And be mindful when working with textareas, it's good to remove the resize option so that the user don't break the styling |
||
value={message} | ||
onChange={(e) => { | ||
setMessage(e.target.value); | ||
// Clear error when user starts typing | ||
setError(''); | ||
}} | ||
className="thought-input" | ||
placeholder="Write your happy message here..." | ||
/> | ||
{/* Show error message if it exists */} | ||
{error && <p className="error-message">{error}</p>} | ||
<button type="submit" className="submit-button"> | ||
❤️ Send Happy Thought ❤️ | ||
</button> | ||
</form> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// components/ThoughtCard.jsx | ||
|
||
import React from 'react'; | ||
import { LikeButton } from './LikeButton'; | ||
import { CreatedAt } from './CreatedAt'; | ||
|
||
export const ThoughtCard = ({ thought }) => { | ||
return ( | ||
<div className="thought-card"> | ||
<p>{thought.message}</p> | ||
<div className="thought-footer"> | ||
{/* Like button component with thought ID and initial like count */} | ||
<LikeButton thoughtId={thought._id} initialHearts={thought.hearts} /> | ||
{/* Timestamp showing when thought was created */} | ||
<CreatedAt createdAt={thought.createdAt} /> | ||
</div> | ||
</div> | ||
); | ||
}; |
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.
Imports are usually grouped in this order:
// React and core libraries
// Third-party libraries
// Custom hooks and utilities
// Components
// Assets (icons, images, and styles)