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 Emelie Kedert #91

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
20 changes: 4 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,15 @@

# 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.
The goal of this project was to build a positive social feed for sharing "happy thoughts" by utilizing React’s state management capabilities to fetch, display, and post data to an API. I started by analyzing the structure and requirements, breaking it down into three key areas: fetching recent thoughts, posting new ones, and implementing a "like" feature. Planning involved deciding on the component structure and defining the necessary state for each component to ensure smooth interactions and an intuitive user experience.

## Getting Started with the Project
For the build, I used React to manage component state and effects, enabling efficient data fetching and updating. useEffect was used to load initial data from the API upon component mounting, and useState to handle form submissions and like counts. For an improved user experience, I implemented an optimistic UI update, where new thoughts appear instantly in the feed before the API has confirmed the post. This approach provided fast feedback for users, especially for mobile responsiveness and handling slow network connections.

### 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?
If I had more time, I would focus on refining accessibility by improving screen reader support, adding animations for new thoughts, and implementing a loading spinner to indicate the fetching process. Adding validation feedback and character counting would further enhance the app's interactivity and user experience.

### 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.
https://happy-thoughts-project-technigo.netlify.app/

## Instructions

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
13 changes: 13 additions & 0 deletions src/AddPost/AddPost.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// AddPost.jsx

import { PostForm } from "./AddPostComponents/PostForm";

export const AddPost = ({ addNewThought, url }) => {
return (
<div className="post-card-add-post">
<h1>What's making you happy right now?</h1>

<PostForm addNewThought={addNewThought} url={url} />
</div>
);
};
57 changes: 57 additions & 0 deletions src/AddPost/AddPostComponents/PostForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// PostForm.jsx

import { useState } from "react";

export const PostForm = ({ addNewThought, url }) => {
// Hooks
const [newThought, setNewThought] = useState("");
const [submitting, setSubmitting] = useState(false);

// Handle submit on form and POST to API
const handleSubmit = async (event) => {
event.preventDefault(); // Prevent site to update
setSubmitting(true); // For loading message on the button

try {
const response = await fetch(url, { // Use URL from App.jsx
method: "POST", // POST to API
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ message: newThought }),
});

if (response.ok) {
const result = await response.json();
addNewThought(result); // Send new thought to App.jsx
setNewThought(""); // Clean input/text field
} else {
console.error("Post failed:", response.status);
}
} catch (error) {
console.error("Error during post:", error);
} finally {
setSubmitting(false); // No loading message on button anymore
}
};

return (
<form onSubmit={handleSubmit}>
<textarea
type="text"
name="thoughts"
placeholder="Type your happy thought..."
value={newThought} // Bind input/textfield to newthought-state
onChange={(e) => setNewThought(e.target.value)}
className="custom-textarea"
/>
<button
className="add-post-btn"
type="submit"
disabled={submitting}>
{/* If submitting, show Submitting, otherwise show Send button */}
{submitting ? <p>Submitting...</p> :
<>❤️<p>Send happy thought</p>❤️</>}</button>
</form>
);
};
139 changes: 139 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/* index.css */

:root {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}

main {
font-family: "Menlo";
}

h1 {
font-size: 16px;
font-weight: 200;
}

p {
font-size: 20px;
}

.post-card-add-post,
.post-card-post {
max-width: 450px;
width: 100%;
height: auto;
padding: 15px;
margin: 20px 0;
border: solid black;
box-shadow: 5px 5px #000;
word-wrap: break-word;
}

.post-card-add-post {
background-color: rgb(242, 240, 240);
}

.add-post-container {
display: flex;
justify-content: center;
}

.post-container {
display: flex;
flex-direction: column;
align-items: center;
}

.custom-textarea {
padding: 10px;
font-family: inherit;
font-size: 16px;
height: 70px;
width: 100%;
box-sizing: border-box;
resize: none;
overflow-wrap: break-word;
white-space: pre-wrap;
border-radius: 0;
}

.post-btn-and-date {
display: flex;
flex-direction: row;
justify-content: space-between;
}

.post-btn-container {
display: flex;
align-items: center;
color: rgb(100, 100, 100);
}

.post-btn-container p {
font-size: 16px;
}

.add-post-btn,
.post-btn {
margin-right: 15px;
border: none;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
font-size: 20px;
}

.add-post-btn {
margin-top: 15px;
border-radius: 25px;
height: 50px;
padding: 0 20px;
background-color: rgb(255, 173, 173);
}

.add-post-btn p {
font-family: "Menlo";
font-size: 16px;
margin: 0 10px;
}

.post-btn {
height: 50px;
width: 50px;
border-radius: 50%;
background-color: rgb(234, 234, 234);
}

/* If heart button is liked change color*/
.post-btn.liked {
background-color: rgb(255, 173, 173);
}

.post-date {
color: rgb(100, 100, 100);
}

.post-date p {
font-size: 16px;
}

/* Mobile screens */
@media (max-width: 677px) {

.post-card-add-post,
.post-card-post {
width: 85%;
max-width: 100%;
}
}
68 changes: 66 additions & 2 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,67 @@
// App.jsx

import { useEffect, useState } from "react";
import { AddPost } from "./AddPost/AddPost";
import { Post } from "./Post/Post";
import "./App.css";

export const App = () => {
return <div>Find me in src/app.jsx!</div>;
};
// Hooks
const [recentThoughts, setRecentThoughts] = useState([]);
const [loading, setLoading] = useState(true);
// URL for GET and POST thoughts to API
const URL_THOUGHTS = "https://project-happy-thoughts-api-ek.onrender.com/thoughts";
// OLD API
// const URL_THOUGHTS = "https://happy-thoughts-ux7hkzgmwa-uc.a.run.app/thoughts";

// Function to fetch recent thoughts
const fetchRecentThoughts = async () => {
setLoading(true); // Show Loading message
try {
const response = await fetch(URL_THOUGHTS);
if (response.ok) {
const data = await response.json();
setRecentThoughts(data);
} else {
console.error("Failed to fetch thoughts");
}
} catch (error) {
console.error("Error fetching data:", error);
} finally {
setLoading(false); // Do not show loading message
}
};

// useEffect hook and call for fetchRecentThought function. ending with empty array to only make the API call once.
useEffect(() => {
fetchRecentThoughts();
}, []);

// Function that adds a new thought
const addNewThought = (newThought) => {
setRecentThoughts([newThought, ...recentThoughts]); // Update UI in the web browser before API, to make it more user firendly
};

return (
// Send addNewThought function as a prop to AddPost
<main>
<div className="add-post-container">
<AddPost addNewThought={addNewThought} url={URL_THOUGHTS} />
</div>

{/* If thoughts are loading, show loading message */}
{loading ?
<div className="post-container">
<p>Loading...</p>
</div>
: (
// otherwise show recent thoughts
<div className="post-container">
{recentThoughts.map(recentThought => (
<Post key={recentThought._id} recentThought={recentThought} />
))}
</div>
)}
</main>
);
};
22 changes: 22 additions & 0 deletions src/Post/Post.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Post.jsx

import { PostMessage } from "./PostComponents/PostMessage";
import { PostLikes } from "./PostComponents/PostLikes";
import { PostDate } from "./PostComponents/PostDate";

export const Post = ({ recentThought }) => {
return (
<article className="post-card-post">
{/* Recent thought message from the API is sent as a prop to PostMessage */}
<PostMessage recentThoughtMessage={recentThought.message} />

<div className="post-btn-and-date">
{/* Number of hearts in the API on each thought, and the ID of each thought is sent as a prop to PostLikes */}
<PostLikes recentThoughtLikes={recentThought.hearts} thoughtId={recentThought._id} />

{/* Recent thought created date from the API is sent as a prop to PostDate */}
<PostDate recentThoughtDate={recentThought.createdAt} />
</div>
</article>
);
};
16 changes: 16 additions & 0 deletions src/Post/PostComponents/PostDate.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// PostDate.jsx

// Downloaded in terminal and then imported here to make dates look pretty
import { formatDistanceToNow } from "date-fns";

export const PostDate = ({ recentThoughtDate }) => {
const date = new Date(recentThoughtDate);
const timeAgo = formatDistanceToNow(date, { addSuffix: true });

return (
<div className="post-date">
<p>{timeAgo}</p>
</div>
);
};

Loading