Skip to content
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

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 4 additions & 27 deletions README.md
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/
4 changes: 3 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
<!-- index.html -->

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Happy Thought - Project - Week 7</title>
<title>Happy Thought - Project</title>
</head>
<body>
<div id="root"></div>
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
"date-fns": "^4.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
Expand Down
44 changes: 42 additions & 2 deletions src/App.jsx
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';
Comment on lines +2 to +5
Copy link
Contributor

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)


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);
});
}, []);
Copy link
Contributor

Choose a reason for hiding this comment

The 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>
);
};
12 changes: 12 additions & 0 deletions src/components/CreatedAt.jsx
Copy link
Contributor

Choose a reason for hiding this comment

The 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>
);
};
41 changes: 41 additions & 0 deletions src/components/LikeButton.jsx
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);
Copy link
Contributor

Choose a reason for hiding this comment

The 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>
);
};
13 changes: 13 additions & 0 deletions src/components/ListThoughts.jsx
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>
);
};
76 changes: 76 additions & 0 deletions src/components/SubmitThought.jsx
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.');
});
Copy link
Contributor

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The 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>
);
};
19 changes: 19 additions & 0 deletions src/components/ThoughtCard.jsx
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>
);
};
Loading