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

my-happy.thoughts-webby #108

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
31 changes: 8 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,20 @@
<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.
We had to build an API to collect Happy Thoughts, making it look similar to X, amd make it as closer as possible to the original design

## Getting Started with the Project

### Dependency Installation & Startup Development Server
### The Problem

Once cloned, navigate to the project's root directory and this project uses npm (Node Package Manager) to manage its dependencies.
First of all, we had to plan how the app should work, thinking how many components, the reason for each one, and the name, I decided to create 3 different components one for the message, another for the items, and a 3rd one for the list of comments that would be displayed.

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.
I had several problems starting with the planing, the logic was not very clear for me (and still), I got a lot og help from chatgpt

```bash
npm i && code . && npm run dev
```
amother problem for me was the styling, but I managed to make it look almost exacly the same.

### The Problem
If I had more time I would try to make the Advanced stretch goals

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?

### 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>
Netlify link:
https://my-happy-thoughts-webby.netlify.app/
Empty file added api.js
Empty file.
2 changes: 1 addition & 1 deletion instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ To achieve this, we've built an API with three endpoints. Note that all of the t

## Fetch recent thoughts

`GET https://happy-thoughts-ux7hkzgmwa-uc.a.run.app/thoughts`
`GET `https://happy-thoughts-ux7hkzgmwa-uc.a.run.app/thoughts

This will return the latest 20 thoughts from the API, looking something like this:

Expand Down
121 changes: 119 additions & 2 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,120 @@
import React, { useEffect, useState } from "react";
Copy link
Contributor

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

import "./index.css";
import "./components/styleForm.css";

// API URL for fetching and posting thoughts
const API_URL = "https://happy-thoughts-ux7hkzgmwa-uc.a.run.app/thoughts";

export const App = () => {
return <div>Find me in src/app.jsx!</div>;
};
const [thoughts, setThoughts] = useState([]); // Stores the list of thoughts
const [newThought, setNewThought] = useState(""); // Stores new thought input
const [error, setError] = useState(""); // Error message for input validation
const [likedThoughts, setLikedThoughts] = useState({}); // Tracks liked states

// Fetch thoughts from API on component mount
useEffect(() => {
fetch(API_URL)
.then((response) => response.json())
.then((data) => setThoughts(data))
.catch((error) => console.error("Error fetching data:", error));
}, []);

// Handle form submission for a new thought
const handleFormSubmit = (event) => {
event.preventDefault();

// Validate input length
if (newThought.length < 5 || newThought.length > 140) {
setError("Message must be between 5 and 140 characters.");
return;
}

setError(""); // Clear previous errors

// Post new thought to the API
fetch(API_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: newThought }),
})
.then((response) => response.json())
.then((data) => {
setThoughts([data, ...thoughts]); // Add new thought to the list
setNewThought(""); // Clear input field
})
.catch((error) => {
console.error("Error posting thought:", error);
setError("Failed to post the thought. Please try again.");
});
};

// Handle like button click for a thought
const handleLikeClick = (thoughtId) => {
const likeUrl = `${API_URL}/${thoughtId}/like`;

fetch(likeUrl, { method: "POST" })
.then((response) => response.json())
.then((updatedThought) => {
// Update thought in the list
setThoughts((prevThoughts) =>
prevThoughts.map((thought) =>
thought._id === updatedThought._id ? updatedThought : thought
)
);

// Toggle liked state for the thought
setLikedThoughts((prevLiked) => ({
...prevLiked,
[thoughtId]: !prevLiked[thoughtId],
}));
})
.catch((error) => console.error("Error liking thought:", error));
};

return (
<div className="app">
<h1>Happy Thoughts</h1>

{/* Thought submission form */}
<form className="thought-form" onSubmit={handleFormSubmit}>
<span className="thought-prompt">What's making you happy right now?</span>

<textarea
value={newThought}
onChange={(e) => setNewThought(e.target.value)}
placeholder="Write your happy thought here..."
rows="4"
maxLength="140"
/>
Comment on lines +82 to +88
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remember labels when working with form elements

<button type="submit" aria-label="Send your happy thought">
Send Happy Thought
</button>
{error && <p className="error-message">{error}</p>}
</form>

{/* Display list of thoughts */}
<ul>
{thoughts.map((thought) => (
<li className="thought-item" key={thought._id}>
<p className="thought-message">{thought.message}</p>
<div className="thought-footer">
<div className="thought-hearts">
<button
className={`heart-button ${thought.hearts > 0 ? "liked" : ""}`}
onClick={() => handleLikeClick(thought._id)}
aria-label={`Like the thought: "${thought.message}". Currently has ${thought.hearts} likes`}
>
❤️
</button>
<span>x {thought.hearts}</span>
</div>
<small className="thought-date">
{new Date(thought.createdAt).toLocaleString()}
</small>
</div>
</li>
))}
</ul>
</div>
);
};
50 changes: 50 additions & 0 deletions src/components/ThoughtForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { useState } from "react";

const API_URL = "https://example-api.com/thoughts"; // Replace with your API URL

const ThoughtForm = () => {
const [newThought, setNewThought] = useState("");
const [error, setError] = useState(""); // Added for error handling

const handleSubmit = (event) => {
event.preventDefault();

fetch(API_URL, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ message: newThought })
})
.then(response => response.json())
.then(data => {
console.log("Posted:", data);
setNewThought(""); // Clear input field
})
.catch(error => {
console.error("Error posting thought:", error);
setError("Failed to post your thought. Please try again."); // Set error message
});
};

return (
<form onSubmit={handleSubmit} className="thoughtForm"> {/* Add class for styling */}
<h3>What's making you happy right now?</h3> {/* Add your text here */}
<textarea
value={newThought}
onChange={(e) => setNewThought(e.target.value)}
placeholder="Write a happy thought..."
rows="4"
maxLength="140"
className="thoughtInput" // Optional: Add class for styling
/>
<button type="submit" aria-label="Send Happy Thought">
Send Happy Thought
</button>
{error && <p className="error-message">{error}</p>}
</form>
);
};

export default ThoughtForm;

37 changes: 37 additions & 0 deletions src/components/ThoughtItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// ThoughtItem.jsx
import React from 'react';
import { formatDistanceToNow } from 'date-fns';
import './styleForm.css';

const ThoughtItem = ({ thought, onLike }) => {
// Format time difference using date-fns
const formatTimeDifference = (timestamp) => {
const messageTime = new Date(timestamp);
return formatDistanceToNow(messageTime, { addSuffix: true });
};

return (
<div className="messageContainer"> {/* Uses your existing messageContainer class */}
<div className="messageBox"> {/* Existing messageBox class */}
<p className="messageText">{thought.message}</p> {/* Uses existing messageText class */}
</div>
<div className="likeContainer"> {/* Existing likeContainer class */}
<button className="heartsButton" onClick={() => onLike(thought._id)}>
<img
width="20"
height="20"
src="https://img.icons8.com/emoji/48/heart-suit.png"
alt="heart"
/>
</button>
<div className="infoText"> {/* Existing infoText class */}
<p className="likeCount">x {thought.hearts}</p> {/* Existing likeCount class */}
<p className="messageTime">{formatTimeDifference(thought.createdAt)}</p> {/* Existing messageTime class */}
</div>
</div>
</div>
);
};

export default ThoughtItem;

16 changes: 16 additions & 0 deletions src/components/ThoughtList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// ThoughtList.jsx
import React from "react";
import ThoughtItem from "./ThoughtItem";
import './styleForm.css'; // Add styles for your form

const ThoughtList = ({ thoughts, onLike }) => {
return (
<div>
{thoughts.map((thought) => (
<ThoughtItem key={thought._id} thought={thought} onLike={onLike} />
))}
</div>
);
};

export default ThoughtList;
Loading