diff --git a/README.md b/README.md index 6fc6d095..7911c82d 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,40 @@ -

- - Project Banner Image - -

+# Happy Thoughts App +This project is a React-based application that allows users to share their happy thoughts with others. Users can submit short messages (thoughts), and view a list of all submitted thoughts along with their like counts. The app also includes features such as validation for thought submission, a character counter, and a fun heart animation when submitting thoughts. -# Happy thoughts Project +Key Features: +Users can enter their thoughts in a form and submit them to the app. +Thoughts are limited to a maximum of 140 characters and must be at least 5 characters long. +On successful submission, the thought is added to the list of thoughts, and the input is cleared. -In this week's project, you'll be able to practice your React state skills by fetching and posting data to an API. +Like Thoughts: Each thought has a like button that allows users to "like" a thought by clicking on a heart icon. The number of likes (hearts) is updated both locally and on the backend. +Users can "unlike" a thought by clicking the heart again. -## Getting Started with the Project +Local storage is used to persist liked thoughts across page reloads. -### Dependency Installation & Startup Development Server +Heart Animation: A heart animation appears when a user submits a new thought, providing a delightful visual confirmation. -Once cloned, navigate to the project's root directory and this project uses npm (Node Package Manager) to manage its dependencies. +Character Count: As users type their thought, a character counter is displayed to show how many characters remain before reaching the 140-character limit. -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. +Validation messages appear if the user attempts to submit a thought that doesn't meet the requirements. -```bash -npm i && code . && npm run dev -``` +Time Ago: +Each thought is tagged with the time it was submitted (e.g., "3 minutes ago", "2 days ago"). +The time difference updates every minute to reflect the most accurate "time ago" format. -### The Problem +Responsive Design: The app is responsive and works well on mobile and desktop screens, ensuring a smooth experience across different devices. -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? +# Tech Stack +React: Used for creating interactive UI components, managing state, and handling events. -### View it live +CSS: Custom styling is used to design a clean and user-friendly interface. -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. +Local Storage: Local storage is utilized to remember liked thoughts, even after page refreshes. -## Instructions +PropTypes: Ensures the correctness of props passed to React components. - - See instructions of this project - +API Integration: API calls are made to store and fetch thoughts from a backend server (mocked with functions postThought and likeThought). + +Hooks: The app utilizes custom hooks (useState, useEffect, useFetchThoughts) for managing state and side effects. + +## view it live +https://post-happy-thoughts.netlify.app/ \ No newline at end of file diff --git a/index.html b/index.html index 21cce4e0..3ad29457 100644 --- a/index.html +++ b/index.html @@ -1,13 +1,15 @@ - - - - - Happy Thought - Project - Week 7 - - -
- - - + + + + + Happy Thought - Project - Week 7 + + + +
+ + + + \ No newline at end of file diff --git a/package.json b/package.json index 74245b0c..89698cc4 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,10 @@ "preview": "vite preview" }, "dependencies": { + "prop-types": "^15.8.1", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-fireworks": "^1.0.4" }, "devDependencies": { "@types/react": "^18.2.15", diff --git a/pull_request_template.md b/pull_request_template.md index d92c89b5..7a44a1eb 100644 --- a/pull_request_template.md +++ b/pull_request_template.md @@ -1,6 +1,5 @@ ## Netlify link -Add your Netlify link here. -PS. Don't forget to add it in your readme as well. +https://post-happy-thoughts.netlify.app ## Collaborators Add your collaborators here. Write their GitHub usernames in square brackets. If there's more than one, separate them with a comma, like this: diff --git a/src/App.jsx b/src/App.jsx index 1091d431..ffc8e569 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,3 +1,11 @@ +import { HappyThoughts } from "./components/HappyThoughts"; +import { HappyThoughtsHeader } from "./components/Header"; + export const App = () => { - return
Find me in src/app.jsx!
; -}; + return ( + <> + + + + ); +}; \ No newline at end of file diff --git a/src/api.js b/src/api.js new file mode 100644 index 00000000..c86c7c5e --- /dev/null +++ b/src/api.js @@ -0,0 +1,25 @@ +// const BASE_URL = "https://happy-thoughts-ux7hkzgmwa-uc.a.run.app/thoughts" + +const BASE_URL = "https://project-happy-thoughts-api-production-5d1d.up.railway.app/thoughts" + +// fetching data from the API +export const fetchThoughts = async () => { + const response = await fetch(BASE_URL) + return response.json() +} + +// function to send the message as a JSON-object to the url via POST +export const postThought = async (message) => { + const response = await fetch(BASE_URL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ message }) + }) + return response.ok +} + +// function to send a POST request to the server to like a message/thought +export const likeThought = async (thoughtId) => { + const response = await fetch(`${BASE_URL}/${thoughtId}/like`, { method: "POST" }) + if (!response.ok) throw new Error("Failed to like thought") +} \ No newline at end of file diff --git a/src/components/HappyThoughts.jsx b/src/components/HappyThoughts.jsx new file mode 100644 index 00000000..c377b51c --- /dev/null +++ b/src/components/HappyThoughts.jsx @@ -0,0 +1,46 @@ +import { useState } from "react"; +import { ThoughtsForm } from "./form/ThoughtForm"; +import { ThoughtList } from "./list/ThoughtList"; +import { useFetchThoughts } from "../hooks"; +import { postThought, likeThought } from "../api"; + +export const HappyThoughts = () => { + const [newThought, setNewThought] = useState("") + const { thoughts, setThoughts, loading, getThoughts } = useFetchThoughts() + + // this function handles the submission of the thoughts form. + // It prevents the default form submission behavior, checks if the input is not empty and then calls the postThought functions to send the new thought to the API. If the post is successful, it clears the input and refreshes the list of the thoughts (getThoughts) + const handleFormSubmit = async (e) => { + e.preventDefault() + if (!newThought.trim()) return + const success = await postThought(newThought) + if (success) { + setNewThought("") + getThoughts() + } + } + + // this function handles the likes of the thought by its ID. It calls the function likeThought, and if successful, updates the local state with the new number of hearts by mapping over the previous thoughts + const handleLike = async (thoughtId, isClicked) => { + try { + await likeThought(thoughtId) + setThoughts((prevThoughts) => + prevThoughts.map((thought) => + thought._id === thoughtId ? { ...thought, hearts: thought.hearts + (isClicked ? 1 : -1) } : thought + )) + } catch (error) { + console.error("Error liking thought", error) + } + } + + return ( +
+ + {loading ?

Loading...

: } +
+ ) +} \ No newline at end of file diff --git a/src/components/Header.jsx b/src/components/Header.jsx new file mode 100644 index 00000000..30d2eea0 --- /dev/null +++ b/src/components/Header.jsx @@ -0,0 +1,10 @@ +export const HappyThoughtsHeader = () => { + return ( +
+
+

Uplifting Moments

+

Share, Like, and Celebrate What Makes Life Beautiful

+
+
+ ) +} \ No newline at end of file diff --git a/src/components/form/Animation.jsx b/src/components/form/Animation.jsx new file mode 100644 index 00000000..bd6c0dd2 --- /dev/null +++ b/src/components/form/Animation.jsx @@ -0,0 +1,18 @@ +import PropTypes from "prop-types"; +import "./animation.css"; + +export const HeartAnimation = ({ isVisible }) => { + return ( + <> + {isVisible && ( +
+ ❤️ +
+ )} + + ) +} + +HeartAnimation.propTypes = { + isVisible: PropTypes.bool.isRequired, +} \ No newline at end of file diff --git a/src/components/form/ThoughtForm.jsx b/src/components/form/ThoughtForm.jsx new file mode 100644 index 00000000..a785fac6 --- /dev/null +++ b/src/components/form/ThoughtForm.jsx @@ -0,0 +1,70 @@ +import PropTypes from "prop-types"; +import "./form.css"; +import { SubmitButton } from "./submitButton/SubmitButton"; +import { CharacterCounter } from "./characterCounter/CharacterCounter"; +import { useState } from "react"; +import { HeartAnimation } from "./Animation"; + +export const ThoughtsForm = ({ newThought, setNewThought, handleFormSubmit }) => { + const maxChars = 140 + const minChars = 5 + const [error, setError] = useState("") + const [showHeart, setShowHeart] = useState(false) + + const handleInputChange = (e) => { + setNewThought(e.target.value) + setError("") + } + + const handleSubmit = (e) => { + e.preventDefault() + // Validation checks + if (newThought.length === 0) { + setError("The message cannot be empty."); + } else if (newThought.length < minChars) { + setError(`The message must be at least ${minChars} characters.`); + } else if (newThought.length > maxChars) { + setError(`The message cannot exceed ${maxChars} characters.`); + } else { + setError("") + setShowHeart(true) + handleFormSubmit(e) + setNewThought("") + setTimeout(() => { + setShowHeart(false) + }, 2000); + } + } + + return ( + <> +
+

What is making you happy right now?

+
+