From 923f8c747241784575071d2e2c16a3a8e5334666 Mon Sep 17 00:00:00 2001 From: Gitte Beckmann Date: Thu, 24 Oct 2024 15:52:35 +0200 Subject: [PATCH 01/27] - added the components Header, HappyThought, ThoughtForm and ThoughtList --- package.json | 1 + src/App.jsx | 12 +++++- src/components/HappyThoughts.jsx | 64 ++++++++++++++++++++++++++++++++ src/components/Header.jsx | 11 ++++++ src/components/ThoughtForm.jsx | 22 +++++++++++ src/components/ThoughtList.jsx | 15 ++++++++ src/index.css | 16 ++++++++ 7 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 src/components/HappyThoughts.jsx create mode 100644 src/components/Header.jsx create mode 100644 src/components/ThoughtForm.jsx create mode 100644 src/components/ThoughtList.jsx diff --git a/package.json b/package.json index 74245b0c..8fd828e3 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/src/App.jsx b/src/App.jsx index 1091d431..58df8c03 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,3 +1,13 @@ + +import { HappyThoughts } from './components/HappyThoughts'; +import { HappyThoughtsHeader } from './components/Header'; + export const App = () => { - return
Find me in src/app.jsx!
; + return ( + <> + + + + ); }; + diff --git a/src/components/HappyThoughts.jsx b/src/components/HappyThoughts.jsx new file mode 100644 index 00000000..13b22119 --- /dev/null +++ b/src/components/HappyThoughts.jsx @@ -0,0 +1,64 @@ +import { useState, useEffect } from "react" +import { ThoughtsForm } from "./ThoughtForm" +import { ThoughtList } from "./ThoughtList" + +export const HappyThoughts = () => { + const [thoughts, setThoughts] = useState() + const [loading, setLoading] = useState(true) + const [newThought, setNewThought] = useState("") + + + const GET_URL = "https://happy-thoughts-ux7hkzgmwa-uc.a.run.app/thoughts" + const POST_URL = "https://happy-thoughts-ux7hkzgmwa-uc.a.run.app/thoughts" + + // fetch from url to load the thoughts + + const fetchThoughts = async () => { + setLoading(true) + const response = await fetch(GET_URL) + const data = await response.json() + // console.log(data) + setThoughts(data) + setLoading(false) + } + + // function to send the new thought + const handleFormSubmit = async (e) => { + e.preventDefault() + if (newThought.trim() === "") { + return + } + + const response = await fetch(POST_URL, { + method: "POST", + headers: { + "content-Type": "application/json", + }, + body: JSON.stringify({ message: newThought }) + }) + if (response.ok) { + setNewThought("") + fetchThoughts() + } + } + + useEffect(() => { + fetchThoughts() + }, []) + + return ( +
+

Happy Thoughts

+ + + {loading ?

loading...

: ( + + )} +
+ ) +} + diff --git a/src/components/Header.jsx b/src/components/Header.jsx new file mode 100644 index 00000000..869ed521 --- /dev/null +++ b/src/components/Header.jsx @@ -0,0 +1,11 @@ + + +export const HappyThoughtsHeader = () => { + return ( +
+
+

Happy Thoughts Project

+
+
+ ) +} \ No newline at end of file diff --git a/src/components/ThoughtForm.jsx b/src/components/ThoughtForm.jsx new file mode 100644 index 00000000..c50f4594 --- /dev/null +++ b/src/components/ThoughtForm.jsx @@ -0,0 +1,22 @@ +import PropTypes from "prop-types" + +export const ThoughtsForm = ({ newThought, setNewThought, handleFormSubmit }) => { + return ( +
+ setNewThought(e.target.value)} placeholder="Type here..."> + + +
+ ) +} + +// Props Validation + +ThoughtsForm.propTypes = { + newThought: PropTypes.string.isRequired, + setNewThought: PropTypes.func.isRequired, + handleFormSubmit: PropTypes.func.isRequired, +} \ No newline at end of file diff --git a/src/components/ThoughtList.jsx b/src/components/ThoughtList.jsx new file mode 100644 index 00000000..583e5f74 --- /dev/null +++ b/src/components/ThoughtList.jsx @@ -0,0 +1,15 @@ +import PropTypes from "prop-types" + +export const ThoughtList = ({ thoughts }) => { + return ( +
    + {thoughts.map((thought, index) => ( +
  • {thought.message}
  • + ))} +
+ ) +} + +ThoughtList.propTypes = { + thoughts: PropTypes.arrayOf(PropTypes.shape({ message: PropTypes.string.isRequired })) +} \ No newline at end of file diff --git a/src/index.css b/src/index.css index 4558f538..7b0ee503 100644 --- a/src/index.css +++ b/src/index.css @@ -11,3 +11,19 @@ code { font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } + +body { + Width: 100%; + height: 100%; + margin: 0; +} + +.header-container { + height: 20%; + display: flex; +} + +img { + height: 100%; + width: 100%; +} \ No newline at end of file From 95808ff6388d524ae63a1df5a3327756d519f0ef Mon Sep 17 00:00:00 2001 From: Gitte Beckmann Date: Thu, 24 Oct 2024 16:49:50 +0200 Subject: [PATCH 02/27] -worked on CSS --- src/components/HappyThoughts.jsx | 4 ++-- src/components/ThoughtForm.jsx | 22 ---------------------- src/components/ThoughtList.jsx | 15 --------------- src/components/form/ThoughtForm.jsx | 26 ++++++++++++++++++++++++++ src/components/form/form.css | 12 ++++++++++++ src/components/list/ThoughtList.jsx | 19 +++++++++++++++++++ src/components/list/list.css | 18 ++++++++++++++++++ src/index.css | 3 +++ 8 files changed, 80 insertions(+), 39 deletions(-) delete mode 100644 src/components/ThoughtForm.jsx delete mode 100644 src/components/ThoughtList.jsx create mode 100644 src/components/form/ThoughtForm.jsx create mode 100644 src/components/form/form.css create mode 100644 src/components/list/ThoughtList.jsx create mode 100644 src/components/list/list.css diff --git a/src/components/HappyThoughts.jsx b/src/components/HappyThoughts.jsx index 13b22119..b835bd84 100644 --- a/src/components/HappyThoughts.jsx +++ b/src/components/HappyThoughts.jsx @@ -1,6 +1,6 @@ import { useState, useEffect } from "react" -import { ThoughtsForm } from "./ThoughtForm" -import { ThoughtList } from "./ThoughtList" +import { ThoughtsForm } from "./form/ThoughtForm" +import { ThoughtList } from "./list/ThoughtList" export const HappyThoughts = () => { const [thoughts, setThoughts] = useState() diff --git a/src/components/ThoughtForm.jsx b/src/components/ThoughtForm.jsx deleted file mode 100644 index c50f4594..00000000 --- a/src/components/ThoughtForm.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import PropTypes from "prop-types" - -export const ThoughtsForm = ({ newThought, setNewThought, handleFormSubmit }) => { - return ( -
- setNewThought(e.target.value)} placeholder="Type here..."> - - -
- ) -} - -// Props Validation - -ThoughtsForm.propTypes = { - newThought: PropTypes.string.isRequired, - setNewThought: PropTypes.func.isRequired, - handleFormSubmit: PropTypes.func.isRequired, -} \ No newline at end of file diff --git a/src/components/ThoughtList.jsx b/src/components/ThoughtList.jsx deleted file mode 100644 index 583e5f74..00000000 --- a/src/components/ThoughtList.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import PropTypes from "prop-types" - -export const ThoughtList = ({ thoughts }) => { - return ( -
    - {thoughts.map((thought, index) => ( -
  • {thought.message}
  • - ))} -
- ) -} - -ThoughtList.propTypes = { - thoughts: PropTypes.arrayOf(PropTypes.shape({ message: PropTypes.string.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..8b165b3e --- /dev/null +++ b/src/components/form/ThoughtForm.jsx @@ -0,0 +1,26 @@ +import PropTypes from "prop-types" +import "./form.css" + +export const ThoughtsForm = ({ newThought, setNewThought, handleFormSubmit }) => { + return ( +
+

What is making you happy right now?

+
+ setNewThought(e.target.value)} placeholder="Type here..."> + + +
+
+ ) +} + +// Props Validation + +ThoughtsForm.propTypes = { + newThought: PropTypes.string.isRequired, + setNewThought: PropTypes.func.isRequired, + handleFormSubmit: PropTypes.func.isRequired, +} \ No newline at end of file diff --git a/src/components/form/form.css b/src/components/form/form.css new file mode 100644 index 00000000..91bc8fdf --- /dev/null +++ b/src/components/form/form.css @@ -0,0 +1,12 @@ +.form-container { + display: flex; + flex-direction: column; + background-color: rgb(235, 231, 231); + width: 280px; +} + +form { + display: flex; + flex-direction: column; + +} \ No newline at end of file diff --git a/src/components/list/ThoughtList.jsx b/src/components/list/ThoughtList.jsx new file mode 100644 index 00000000..09e7c242 --- /dev/null +++ b/src/components/list/ThoughtList.jsx @@ -0,0 +1,19 @@ +import PropTypes from "prop-types" +import "./list.css" + +export const ThoughtList = ({ thoughts }) => { + return ( +
+
    + {thoughts.map((thought, index) => ( +
  • {thought.message}
  • + ))} + +
+
+ ) +} + +ThoughtList.propTypes = { + thoughts: PropTypes.arrayOf(PropTypes.shape({ message: PropTypes.string.isRequired })) +} \ No newline at end of file diff --git a/src/components/list/list.css b/src/components/list/list.css new file mode 100644 index 00000000..aba1d90c --- /dev/null +++ b/src/components/list/list.css @@ -0,0 +1,18 @@ +.list-container { + max-width: 280px; + margin: 0 auto; + padding: 10px; + border: solid black; +} + +.list-container ul { + list-style-type: none; + padding: 0; +} + +.list-container li { + margin: 5px 0; + word-wrap: break-word; + border: solid black; + height: 100px; +} \ No newline at end of file diff --git a/src/index.css b/src/index.css index 7b0ee503..d1e8e5e6 100644 --- a/src/index.css +++ b/src/index.css @@ -16,6 +16,9 @@ body { Width: 100%; height: 100%; margin: 0; + display: flex; + flex-direction: column; + justify-content: center; } .header-container { From c213610fbc9e53307942bfa22aee8764ff23bd78 Mon Sep 17 00:00:00 2001 From: Gitte Beckmann Date: Fri, 25 Oct 2024 10:41:06 +0200 Subject: [PATCH 03/27] - added a submit button component, - changed input field into textarea - styled textarea --- src/components/form/ThoughtForm.jsx | 12 +++++--- src/components/form/form.css | 29 ++++++++++++++++++-- src/components/list/list.css | 2 +- src/components/submitButton/SubmitButton.jsx | 8 ++++++ src/components/submitButton/submitButton.css | 3 ++ src/index.css | 6 ---- 6 files changed, 47 insertions(+), 13 deletions(-) create mode 100644 src/components/submitButton/SubmitButton.jsx create mode 100644 src/components/submitButton/submitButton.css diff --git a/src/components/form/ThoughtForm.jsx b/src/components/form/ThoughtForm.jsx index 8b165b3e..2dfe05f2 100644 --- a/src/components/form/ThoughtForm.jsx +++ b/src/components/form/ThoughtForm.jsx @@ -1,17 +1,21 @@ import PropTypes from "prop-types" import "./form.css" +import { SubmitButton } from "../submitButton/SubmitButton" export const ThoughtsForm = ({ newThought, setNewThought, handleFormSubmit }) => { return (

What is making you happy right now?

- setNewThought(e.target.value)} placeholder="Type here..."> - - + onChange={(e) => setNewThought(e.target.value)} + placeholder="Type here..." + maxLength="140"> + + + {/* */}
) diff --git a/src/components/form/form.css b/src/components/form/form.css index 91bc8fdf..6ab78a92 100644 --- a/src/components/form/form.css +++ b/src/components/form/form.css @@ -1,12 +1,37 @@ .form-container { display: flex; flex-direction: column; - background-color: rgb(235, 231, 231); - width: 280px; + justify-content: center; + background-color: #f2f0f0; + max-width: 500px; + padding: 10px; + margin: 0 auto; } form { display: flex; flex-direction: column; + justify-content: center; +} + +p { + margin: 0; + padding: 10px; +} +textarea { + width: 90%; + height: 100px; + resize: none; + padding: 10px; + font-family: "Courier New", Courier, "Lucida Console", monospace; + font-weight: bold; + font-size: 16px; + box-sizing: border-box; + border: 1px solid #ccc; + border-radius: 1px; + overflow: hidden; + border: solid #cccccc; + color: #111111; + margin-bottom: 10px; } \ No newline at end of file diff --git a/src/components/list/list.css b/src/components/list/list.css index aba1d90c..d0f8e56d 100644 --- a/src/components/list/list.css +++ b/src/components/list/list.css @@ -1,5 +1,5 @@ .list-container { - max-width: 280px; + max-width: 500px; margin: 0 auto; padding: 10px; border: solid black; diff --git a/src/components/submitButton/SubmitButton.jsx b/src/components/submitButton/SubmitButton.jsx new file mode 100644 index 00000000..665e4eb8 --- /dev/null +++ b/src/components/submitButton/SubmitButton.jsx @@ -0,0 +1,8 @@ +import "./submitButton.css" + +export const SubmitButton = () => { + return ( + + + ) +} \ No newline at end of file diff --git a/src/components/submitButton/submitButton.css b/src/components/submitButton/submitButton.css new file mode 100644 index 00000000..2f854833 --- /dev/null +++ b/src/components/submitButton/submitButton.css @@ -0,0 +1,3 @@ +button { + width: 50%; +} \ No newline at end of file diff --git a/src/index.css b/src/index.css index d1e8e5e6..9aa1b845 100644 --- a/src/index.css +++ b/src/index.css @@ -14,7 +14,6 @@ code { body { Width: 100%; - height: 100%; margin: 0; display: flex; flex-direction: column; @@ -24,9 +23,4 @@ body { .header-container { height: 20%; display: flex; -} - -img { - height: 100%; - width: 100%; } \ No newline at end of file From 0b98bf326f7eb32ce3982875baee755e85f9e77f Mon Sep 17 00:00:00 2001 From: Gitte Beckmann Date: Fri, 25 Oct 2024 12:06:36 +0200 Subject: [PATCH 04/27] - added like LikeButton - re-aranged components --- src/components/form/ThoughtForm.jsx | 33 ++++++++++--------- src/components/form/form.css | 8 +++-- .../{ => form}/submitButton/SubmitButton.jsx | 0 .../form/submitButton/submitButton.css | 8 +++++ src/components/list/ThoughtList.jsx | 12 +++++-- src/components/list/likeButton/LikeButton.jsx | 24 ++++++++++++++ src/components/list/likeButton/likeButton.css | 13 ++++++++ src/components/list/list.css | 30 ++++++++++++++--- src/components/list/time/Time.jsx | 0 src/components/list/time/time.css | 0 src/components/submitButton/submitButton.css | 3 -- 11 files changed, 103 insertions(+), 28 deletions(-) rename src/components/{ => form}/submitButton/SubmitButton.jsx (100%) create mode 100644 src/components/form/submitButton/submitButton.css create mode 100644 src/components/list/likeButton/LikeButton.jsx create mode 100644 src/components/list/likeButton/likeButton.css create mode 100644 src/components/list/time/Time.jsx create mode 100644 src/components/list/time/time.css delete mode 100644 src/components/submitButton/submitButton.css diff --git a/src/components/form/ThoughtForm.jsx b/src/components/form/ThoughtForm.jsx index 2dfe05f2..efb8f081 100644 --- a/src/components/form/ThoughtForm.jsx +++ b/src/components/form/ThoughtForm.jsx @@ -1,23 +1,26 @@ import PropTypes from "prop-types" import "./form.css" -import { SubmitButton } from "../submitButton/SubmitButton" +import { SubmitButton } from "./submitButton/SubmitButton" export const ThoughtsForm = ({ newThought, setNewThought, handleFormSubmit }) => { return ( -
-

What is making you happy right now?

-
- - - {/* */} - -
+ <> +
+

What is making you happy right now?

+
+ + + {/* */} + +
+ + ) } diff --git a/src/components/form/form.css b/src/components/form/form.css index 6ab78a92..4763c6fc 100644 --- a/src/components/form/form.css +++ b/src/components/form/form.css @@ -3,9 +3,11 @@ flex-direction: column; justify-content: center; background-color: #f2f0f0; - max-width: 500px; - padding: 10px; + max-width: 280px; + padding: 5px; margin: 0 auto; + box-shadow: 10px 10px 0px #111111; + border: solid #7f7f7f; } form { @@ -17,6 +19,7 @@ form { p { margin: 0; padding: 10px; + font-size: 14px; } textarea { @@ -34,4 +37,5 @@ textarea { border: solid #cccccc; color: #111111; margin-bottom: 10px; + } \ No newline at end of file diff --git a/src/components/submitButton/SubmitButton.jsx b/src/components/form/submitButton/SubmitButton.jsx similarity index 100% rename from src/components/submitButton/SubmitButton.jsx rename to src/components/form/submitButton/SubmitButton.jsx diff --git a/src/components/form/submitButton/submitButton.css b/src/components/form/submitButton/submitButton.css new file mode 100644 index 00000000..5bbb68e4 --- /dev/null +++ b/src/components/form/submitButton/submitButton.css @@ -0,0 +1,8 @@ +button { + width: 80%; + background-color: #ffaead; + border-radius: 20px; + padding: 10px; + border: none; + font-size: 16px; +} \ No newline at end of file diff --git a/src/components/list/ThoughtList.jsx b/src/components/list/ThoughtList.jsx index 09e7c242..a9762c0a 100644 --- a/src/components/list/ThoughtList.jsx +++ b/src/components/list/ThoughtList.jsx @@ -1,16 +1,22 @@ import PropTypes from "prop-types" import "./list.css" +import { LikeButton } from "./likeButton/LikeButton" export const ThoughtList = ({ thoughts }) => { return (
-
    +
      {thoughts.map((thought, index) => ( -
    1. {thought.message}
    2. +
    3. {thought.message} +
      + +
      +
    4. ))} -
+
+ ) } diff --git a/src/components/list/likeButton/LikeButton.jsx b/src/components/list/likeButton/LikeButton.jsx new file mode 100644 index 00000000..68caf276 --- /dev/null +++ b/src/components/list/likeButton/LikeButton.jsx @@ -0,0 +1,24 @@ +import "./likeButton.css" +import { useState } from "react" + +export const LikeButton = () => { + const [count, setCount] = useState(0) + const [isClicked, setIsClicked] = useState(false) + + const handleClick = () => { + setCount(prevCount => prevCount + 1) + setIsClicked(true) + } + + return ( + <> + + x {count} + + ) + +} \ No newline at end of file diff --git a/src/components/list/likeButton/likeButton.css b/src/components/list/likeButton/likeButton.css new file mode 100644 index 00000000..c02e4469 --- /dev/null +++ b/src/components/list/likeButton/likeButton.css @@ -0,0 +1,13 @@ +.like-button { + width: 40px; + background-color: #eaeaea; +} + +.like-button.clicked { + background-color: #ffaead; +} + +.counter { + padding-left: 10px; + color: #838383; +} \ No newline at end of file diff --git a/src/components/list/list.css b/src/components/list/list.css index d0f8e56d..70b27bbd 100644 --- a/src/components/list/list.css +++ b/src/components/list/list.css @@ -1,18 +1,38 @@ .list-container { - max-width: 500px; + display: flex; + justify-content: center; + width: 100%; margin: 0 auto; - padding: 10px; - border: solid black; + margin-top: 15px; } -.list-container ul { +.list-container ol { list-style-type: none; padding: 0; + width: 300px; } .list-container li { - margin: 5px 0; + display: flex; + flex-direction: column; + justify-content: space-between; + margin: 15px 0; word-wrap: break-word; border: solid black; height: 100px; + border: solid #7f7f7f; + box-shadow: 5px 5px 0px #111111; +} + +li { + font-family: "Courier New", Courier, "Lucida Console", monospace; + font-weight: bold; + padding: 15px 10px; + border-radius: 2px; + +} + +.like-container { + display: flex; + align-items: center; } \ No newline at end of file diff --git a/src/components/list/time/Time.jsx b/src/components/list/time/Time.jsx new file mode 100644 index 00000000..e69de29b diff --git a/src/components/list/time/time.css b/src/components/list/time/time.css new file mode 100644 index 00000000..e69de29b diff --git a/src/components/submitButton/submitButton.css b/src/components/submitButton/submitButton.css deleted file mode 100644 index 2f854833..00000000 --- a/src/components/submitButton/submitButton.css +++ /dev/null @@ -1,3 +0,0 @@ -button { - width: 50%; -} \ No newline at end of file From 7dd735c60001587c386aa1f0dcd5403d5b9c3668 Mon Sep 17 00:00:00 2001 From: Gitte Beckmann Date: Fri, 25 Oct 2024 13:51:32 +0200 Subject: [PATCH 05/27] - added function to update likes --- src/components/HappyThoughts.jsx | 24 ++++++++++++++++++- src/components/list/ThoughtList.jsx | 17 ++++++++++--- src/components/list/likeButton/LikeButton.jsx | 12 +++++----- 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/components/HappyThoughts.jsx b/src/components/HappyThoughts.jsx index b835bd84..91131f5c 100644 --- a/src/components/HappyThoughts.jsx +++ b/src/components/HappyThoughts.jsx @@ -42,6 +42,28 @@ export const HappyThoughts = () => { } } + const handleLike = async (thoughtId) => { + try { + const response = await fetch(`${POST_URL}/${thoughtId}/like`, { + method: "POST", + }); + + if (!response.ok) throw new Error('Failed to like thought'); + + // Update the local state instead of refetching all thoughts + setThoughts(prevThoughts => + prevThoughts.map(thought => + thought._id === thoughtId + ? { ...thought, likes: thought.likes + 1 } // Increment likes locally + : thought + ) + ); + } catch (error) { + console.error("Error liking thought:", error); + } + }; + + useEffect(() => { fetchThoughts() }, []) @@ -56,7 +78,7 @@ export const HappyThoughts = () => { /> {loading ?

loading...

: ( - + )} ) diff --git a/src/components/list/ThoughtList.jsx b/src/components/list/ThoughtList.jsx index a9762c0a..f8e88375 100644 --- a/src/components/list/ThoughtList.jsx +++ b/src/components/list/ThoughtList.jsx @@ -2,14 +2,18 @@ import PropTypes from "prop-types" import "./list.css" import { LikeButton } from "./likeButton/LikeButton" -export const ThoughtList = ({ thoughts }) => { +export const ThoughtList = ({ thoughts, onLike }) => { return (
    {thoughts.map((thought, index) => (
  1. {thought.message}
    - +
  2. ))} @@ -21,5 +25,12 @@ export const ThoughtList = ({ thoughts }) => { } ThoughtList.propTypes = { - thoughts: PropTypes.arrayOf(PropTypes.shape({ message: PropTypes.string.isRequired })) + thoughts: PropTypes.arrayOf( + PropTypes.shape({ + _id: PropTypes.string.isRequired, + message: PropTypes.string.isRequired, + likes: PropTypes.number.isRequired, + }) + ).isRequired, + onLike: PropTypes.func.isRequired, } \ No newline at end of file diff --git a/src/components/list/likeButton/LikeButton.jsx b/src/components/list/likeButton/LikeButton.jsx index 68caf276..2aac1439 100644 --- a/src/components/list/likeButton/LikeButton.jsx +++ b/src/components/list/likeButton/LikeButton.jsx @@ -1,15 +1,16 @@ import "./likeButton.css" import { useState } from "react" -export const LikeButton = () => { - const [count, setCount] = useState(0) +export const LikeButton = ({ thoughtId, hearts, onLike }) => { const [isClicked, setIsClicked] = useState(false) - const handleClick = () => { - setCount(prevCount => prevCount + 1) + const handleClick = async () => { + console.log("button clicked") + await onLike(thoughtId) setIsClicked(true) } + return ( <> - x {count} + x {hearts} ) - } \ No newline at end of file From bbe3f3687d0fc2574fd4d69fb8d25ac99e0463d9 Mon Sep 17 00:00:00 2001 From: Gitte Beckmann Date: Fri, 25 Oct 2024 14:00:42 +0200 Subject: [PATCH 06/27] - fixed a bug by replacing two prop names --- src/components/HappyThoughts.jsx | 2 +- src/components/list/ThoughtList.jsx | 8 +------- src/components/list/likeButton/LikeButton.jsx | 7 +++++++ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/components/HappyThoughts.jsx b/src/components/HappyThoughts.jsx index 91131f5c..f9278b7d 100644 --- a/src/components/HappyThoughts.jsx +++ b/src/components/HappyThoughts.jsx @@ -54,7 +54,7 @@ export const HappyThoughts = () => { setThoughts(prevThoughts => prevThoughts.map(thought => thought._id === thoughtId - ? { ...thought, likes: thought.likes + 1 } // Increment likes locally + ? { ...thought, hearts: thought.hearts + 1 } // Increment likes locally : thought ) ); diff --git a/src/components/list/ThoughtList.jsx b/src/components/list/ThoughtList.jsx index f8e88375..1757ed31 100644 --- a/src/components/list/ThoughtList.jsx +++ b/src/components/list/ThoughtList.jsx @@ -25,12 +25,6 @@ export const ThoughtList = ({ thoughts, onLike }) => { } ThoughtList.propTypes = { - thoughts: PropTypes.arrayOf( - PropTypes.shape({ - _id: PropTypes.string.isRequired, - message: PropTypes.string.isRequired, - likes: PropTypes.number.isRequired, - }) - ).isRequired, + thoughts: PropTypes.array.isRequired, onLike: PropTypes.func.isRequired, } \ No newline at end of file diff --git a/src/components/list/likeButton/LikeButton.jsx b/src/components/list/likeButton/LikeButton.jsx index 2aac1439..2736d2a5 100644 --- a/src/components/list/likeButton/LikeButton.jsx +++ b/src/components/list/likeButton/LikeButton.jsx @@ -1,5 +1,6 @@ import "./likeButton.css" import { useState } from "react" +import PropTypes from "prop-types" export const LikeButton = ({ thoughtId, hearts, onLike }) => { const [isClicked, setIsClicked] = useState(false) @@ -21,4 +22,10 @@ export const LikeButton = ({ thoughtId, hearts, onLike }) => { x {hearts} ) +} + +LikeButton.propTypes = { + thoughtId: PropTypes.string.isRequired, + hearts: PropTypes.number.isRequired, + onLike: PropTypes.func.isRequired, } \ No newline at end of file From 58af9d444a092aeb1373e68bd1ec277b1feebeb0 Mon Sep 17 00:00:00 2001 From: Gitte Beckmann Date: Fri, 25 Oct 2024 15:12:42 +0200 Subject: [PATCH 07/27] - added the time component --- src/components/HappyThoughts.jsx | 15 ++++++---- src/components/Header.jsx | 3 +- src/components/list/ThoughtList.jsx | 15 ++++++---- src/components/list/list.css | 6 ++-- src/components/list/time/Time.jsx | 44 +++++++++++++++++++++++++++++ src/components/list/time/time.css | 6 ++++ src/index.css | 6 ++++ 7 files changed, 81 insertions(+), 14 deletions(-) diff --git a/src/components/HappyThoughts.jsx b/src/components/HappyThoughts.jsx index f9278b7d..11c95bf2 100644 --- a/src/components/HappyThoughts.jsx +++ b/src/components/HappyThoughts.jsx @@ -54,14 +54,15 @@ export const HappyThoughts = () => { setThoughts(prevThoughts => prevThoughts.map(thought => thought._id === thoughtId - ? { ...thought, hearts: thought.hearts + 1 } // Increment likes locally + ? { ...thought, hearts: thought.hearts + 1 } : thought ) - ); + ) + } catch (error) { console.error("Error liking thought:", error); } - }; + } useEffect(() => { @@ -70,7 +71,7 @@ export const HappyThoughts = () => { return (
    -

    Happy Thoughts

    + { /> {loading ?

    loading...

    : ( - + )}
    ) diff --git a/src/components/Header.jsx b/src/components/Header.jsx index 869ed521..069c2a17 100644 --- a/src/components/Header.jsx +++ b/src/components/Header.jsx @@ -4,7 +4,8 @@ export const HappyThoughtsHeader = () => { return (
    -

    Happy Thoughts Project

    +

    Uplifting Moments

    +

    Share, Like, and Celebrate What Makes Life Beautiful

    ) diff --git a/src/components/list/ThoughtList.jsx b/src/components/list/ThoughtList.jsx index 1757ed31..7fc3c828 100644 --- a/src/components/list/ThoughtList.jsx +++ b/src/components/list/ThoughtList.jsx @@ -1,6 +1,8 @@ import PropTypes from "prop-types" import "./list.css" import { LikeButton } from "./likeButton/LikeButton" +import { Time } from "./time/Time" + export const ThoughtList = ({ thoughts, onLike }) => { return ( @@ -9,11 +11,14 @@ export const ThoughtList = ({ thoughts, onLike }) => { {thoughts.map((thought, index) => (
  3. {thought.message}
    - +
    + +
    +
  4. ))} diff --git a/src/components/list/list.css b/src/components/list/list.css index 70b27bbd..8d9b7c32 100644 --- a/src/components/list/list.css +++ b/src/components/list/list.css @@ -19,7 +19,7 @@ margin: 15px 0; word-wrap: break-word; border: solid black; - height: 100px; + height: 120px; border: solid #7f7f7f; box-shadow: 5px 5px 0px #111111; } @@ -27,12 +27,12 @@ li { font-family: "Courier New", Courier, "Lucida Console", monospace; font-weight: bold; - padding: 15px 10px; + padding: 5px 10px 20px 10px; border-radius: 2px; - } .like-container { display: flex; align-items: center; + justify-content: space-between; } \ No newline at end of file diff --git a/src/components/list/time/Time.jsx b/src/components/list/time/Time.jsx index e69de29b..75940808 100644 --- a/src/components/list/time/Time.jsx +++ b/src/components/list/time/Time.jsx @@ -0,0 +1,44 @@ +import "./time.css" +import { useEffect, useState } from 'react' +import PropTypes from 'prop-types' + + +const getTimeDifference = (createdAt) => { + const now = new Date(); + const createdDate = new Date(createdAt); + const differenceInSeconds = Math.floor((now - createdDate) / 1000) + + if (differenceInSeconds < 60) { + return `${differenceInSeconds} seconds ago` + } else if (differenceInSeconds < 3600) { + const minutes = Math.floor(differenceInSeconds / 60) + return `${minutes} ${minutes === 1 ? "minute" : "minutes"} ago` + } else if (differenceInSeconds < 86400) { + const hours = Math.floor(differenceInSeconds / 3600) + return `${hours} ${hours === 1 ? "hour" : "hours"} ago` + } else if (differenceInSeconds < 2592000) { + const days = Math.floor(differenceInSeconds / 86400) + return `${days} ${days === 1 ? "day" : "days"} ago` + } else { + const months = Math.floor(differenceInSeconds / 2592000) + return `${months} ${months === 1 ? "month" : "months"} ago` + } +}; + +export const Time = ({ createdAt }) => { + const [timeAgo, setTimeAgo] = useState(getTimeDifference(createdAt)) + + useEffect(() => { + const timer = setInterval(() => { + setTimeAgo(getTimeDifference(createdAt)) + }, 60000) + + return () => clearInterval(timer); + }, [createdAt]); + + return {timeAgo} +} + +Time.propTypes = { + createdAt: PropTypes.string.isRequired, +} \ No newline at end of file diff --git a/src/components/list/time/time.css b/src/components/list/time/time.css index e69de29b..860f6ea5 100644 --- a/src/components/list/time/time.css +++ b/src/components/list/time/time.css @@ -0,0 +1,6 @@ +.time { + color: #c0c0c0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; +} \ No newline at end of file diff --git a/src/index.css b/src/index.css index 9aa1b845..5f7915de 100644 --- a/src/index.css +++ b/src/index.css @@ -23,4 +23,10 @@ body { .header-container { height: 20%; display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background-color: #ffaead; + margin-bottom: 15px; + padding: 10px; } \ No newline at end of file From 0cd9b8ab1b060554f8d31c9f0339c31251c45762 Mon Sep 17 00:00:00 2001 From: Gitte Beckmann Date: Sat, 26 Oct 2024 20:26:30 +0200 Subject: [PATCH 08/27] - restructured the components by adding two new js files fot the hooks and the api --- src/api.js | 23 +++++ src/components/HappyThoughts.jsx | 87 ++++--------------- src/components/list/ThoughtList.jsx | 14 ++- src/components/list/likeButton/LikeButton.jsx | 1 - src/components/list/time/Time.jsx | 31 +++---- src/hooks.js | 21 +++++ 6 files changed, 83 insertions(+), 94 deletions(-) create mode 100644 src/api.js create mode 100644 src/hooks.js diff --git a/src/api.js b/src/api.js new file mode 100644 index 00000000..5c751b46 --- /dev/null +++ b/src/api.js @@ -0,0 +1,23 @@ +const BASE_URL = "https://happy-thoughts-ux7hkzgmwa-uc.a.run.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}/hearts`, { method: "POST" }) + if (!response.ok) throw new Error("Failed to like throught") +} \ No newline at end of file diff --git a/src/components/HappyThoughts.jsx b/src/components/HappyThoughts.jsx index 11c95bf2..ca23e94b 100644 --- a/src/components/HappyThoughts.jsx +++ b/src/components/HappyThoughts.jsx @@ -1,90 +1,41 @@ -import { useState, useEffect } from "react" +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 [thoughts, setThoughts] = useState() - const [loading, setLoading] = useState(true) const [newThought, setNewThought] = useState("") + const { thoughts, setThoughts, loading, getThoughts } = useFetchThoughts() - - const GET_URL = "https://happy-thoughts-ux7hkzgmwa-uc.a.run.app/thoughts" - const POST_URL = "https://happy-thoughts-ux7hkzgmwa-uc.a.run.app/thoughts" - - // fetch from url to load the thoughts - - const fetchThoughts = async () => { - setLoading(true) - const response = await fetch(GET_URL) - const data = await response.json() - // console.log(data) - setThoughts(data) - setLoading(false) - } - - // function to send the new thought + // 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 response = await fetch(POST_URL, { - method: "POST", - headers: { - "content-Type": "application/json", - }, - body: JSON.stringify({ message: newThought }) - }) - if (response.ok) { + if (!newThought.trim()) return + const success = await postThought(newThought) + if (success) { setNewThought("") - fetchThoughts() + 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) => { try { - const response = await fetch(`${POST_URL}/${thoughtId}/like`, { - method: "POST", - }); - - if (!response.ok) throw new Error('Failed to like thought'); - - // Update the local state instead of refetching all thoughts - setThoughts(prevThoughts => - prevThoughts.map(thought => - thought._id === thoughtId - ? { ...thought, hearts: thought.hearts + 1 } - : thought - ) - ) - + await likeThought(thoughtId) + setThoughts((prevThoughts) => + prevThoughts.map((thought) => + thought._id === thoughtId ? { ...thought, hearts: thought.hearts + 1 } : thought + )) } catch (error) { - console.error("Error liking thought:", error); + console.error("Error liking thought", error) } } - - - useEffect(() => { - fetchThoughts() - }, []) - return (
    - - - - {loading ?

    loading...

    : ( - - )} + + {loading ?

    Loading...

    : }
    ) } diff --git a/src/components/list/ThoughtList.jsx b/src/components/list/ThoughtList.jsx index 7fc3c828..1087911b 100644 --- a/src/components/list/ThoughtList.jsx +++ b/src/components/list/ThoughtList.jsx @@ -1,15 +1,15 @@ import PropTypes from "prop-types" import "./list.css" import { LikeButton } from "./likeButton/LikeButton" -import { Time } from "./time/Time" +import { Time } from "./time" export const ThoughtList = ({ thoughts, onLike }) => { return (
      - {thoughts.map((thought, index) => ( -
    1. {thought.message} + {thoughts.map((thought) => ( +
    2. {thought.message}
      { } ThoughtList.propTypes = { - thoughts: PropTypes.array.isRequired, + thoughts: PropTypes.arrayOf(PropTypes.shape({ + _id: PropTypes.string.isRequired, + message: PropTypes.string.isRequired, + hearts: PropTypes.number.isRequired, + createdAt: PropTypes.string.isRequired + }) + ).isRequired, onLike: PropTypes.func.isRequired, } \ No newline at end of file diff --git a/src/components/list/likeButton/LikeButton.jsx b/src/components/list/likeButton/LikeButton.jsx index 2736d2a5..19879888 100644 --- a/src/components/list/likeButton/LikeButton.jsx +++ b/src/components/list/likeButton/LikeButton.jsx @@ -6,7 +6,6 @@ export const LikeButton = ({ thoughtId, hearts, onLike }) => { const [isClicked, setIsClicked] = useState(false) const handleClick = async () => { - console.log("button clicked") await onLike(thoughtId) setIsClicked(true) } diff --git a/src/components/list/time/Time.jsx b/src/components/list/time/Time.jsx index 75940808..8ce81321 100644 --- a/src/components/list/time/Time.jsx +++ b/src/components/list/time/Time.jsx @@ -2,38 +2,27 @@ import "./time.css" import { useEffect, useState } from 'react' import PropTypes from 'prop-types' - +// this function calculates the difference between the current time (now) and the time when the message has been posted (createdDate). createdAt is the parameter which repesents when the event occured. const getTimeDifference = (createdAt) => { const now = new Date(); const createdDate = new Date(createdAt); const differenceInSeconds = Math.floor((now - createdDate) / 1000) - if (differenceInSeconds < 60) { - return `${differenceInSeconds} seconds ago` - } else if (differenceInSeconds < 3600) { - const minutes = Math.floor(differenceInSeconds / 60) - return `${minutes} ${minutes === 1 ? "minute" : "minutes"} ago` - } else if (differenceInSeconds < 86400) { - const hours = Math.floor(differenceInSeconds / 3600) - return `${hours} ${hours === 1 ? "hour" : "hours"} ago` - } else if (differenceInSeconds < 2592000) { - const days = Math.floor(differenceInSeconds / 86400) - return `${days} ${days === 1 ? "day" : "days"} ago` - } else { - const months = Math.floor(differenceInSeconds / 2592000) - return `${months} ${months === 1 ? "month" : "months"} ago` - } -}; + if (differenceInSeconds < 60) return `${differenceInSeconds} seconds ago` + if (differenceInSeconds < 3600) return `${Math.floor(differenceInSeconds / 60)} minutes ago` + if (differenceInSeconds < 86400) return `${Math.floor(differenceInSeconds / 3600)} hours ago` + if (differenceInSeconds < 2592000) return `${Math.floor(differenceInSeconds / 86400)} days ago` + return `${Math.floor(differenceInSeconds / 2592000)} months ago` + +} export const Time = ({ createdAt }) => { const [timeAgo, setTimeAgo] = useState(getTimeDifference(createdAt)) useEffect(() => { - const timer = setInterval(() => { - setTimeAgo(getTimeDifference(createdAt)) - }, 60000) + const timer = setInterval(() => setTimeAgo(getTimeDifference(createdAt)), 60000) - return () => clearInterval(timer); + return () => clearInterval(timer) }, [createdAt]); return {timeAgo} diff --git a/src/hooks.js b/src/hooks.js new file mode 100644 index 00000000..60f6c724 --- /dev/null +++ b/src/hooks.js @@ -0,0 +1,21 @@ +import { useState, useEffect } from "react" +import { fetchThoughts } from "./api" + +// this is a hook that fetched the thoughts/messages and manages the loading state +export const useFetchThoughts = () => { + const [thoughts, setThoughts] = useState([]) + const [loading, setLoading] = useState(true) + + // this function fetched thoughts from the API and updates the state + const getThoughts = async () => { + setLoading(true) + const data = await fetchThoughts() + setThoughts(data) + setLoading(false) + } + useEffect(() => { + getThoughts() + }, []) + + return { thoughts, setThoughts, loading, getThoughts } +} \ No newline at end of file From d57a9c4b24a55b2a1980fd00a49db6f2544278b8 Mon Sep 17 00:00:00 2001 From: Gitte Beckmann Date: Sat, 26 Oct 2024 21:20:56 +0200 Subject: [PATCH 09/27] - worked on the CSS --- src/components/HappyThoughts.jsx | 4 ++-- src/components/form/ThoughtForm.jsx | 6 ++++-- src/components/form/form.css | 12 ++++++++++-- src/components/form/submitButton/submitButton.css | 2 +- src/components/list/ThoughtList.jsx | 2 +- src/components/list/list.css | 4 +++- src/index.css | 8 ++++++++ 7 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/components/HappyThoughts.jsx b/src/components/HappyThoughts.jsx index ca23e94b..f742653f 100644 --- a/src/components/HappyThoughts.jsx +++ b/src/components/HappyThoughts.jsx @@ -33,10 +33,10 @@ export const HappyThoughts = () => { } } return ( -
      +
      {loading ?

      Loading...

      : } -
      +
      ) } diff --git a/src/components/form/ThoughtForm.jsx b/src/components/form/ThoughtForm.jsx index efb8f081..fac3ba23 100644 --- a/src/components/form/ThoughtForm.jsx +++ b/src/components/form/ThoughtForm.jsx @@ -15,8 +15,10 @@ export const ThoughtsForm = ({ newThought, setNewThought, handleFormSubmit }) => placeholder="Type here..." maxLength="140"> - - {/* */} +
      + + {/* */} +
      diff --git a/src/components/form/form.css b/src/components/form/form.css index 4763c6fc..e9dfabdf 100644 --- a/src/components/form/form.css +++ b/src/components/form/form.css @@ -3,17 +3,20 @@ flex-direction: column; justify-content: center; background-color: #f2f0f0; - max-width: 280px; padding: 5px; - margin: 0 auto; + margin: 10px; box-shadow: 10px 10px 0px #111111; border: solid #7f7f7f; + width: 90%; + max-width: 480px; } form { display: flex; flex-direction: column; justify-content: center; + align-items: center; + width: 100%; } p { @@ -38,4 +41,9 @@ textarea { color: #111111; margin-bottom: 10px; +} + +.submit-button-container { + display: flex; + width: 90%; } \ No newline at end of file diff --git a/src/components/form/submitButton/submitButton.css b/src/components/form/submitButton/submitButton.css index 5bbb68e4..2d388a26 100644 --- a/src/components/form/submitButton/submitButton.css +++ b/src/components/form/submitButton/submitButton.css @@ -1,5 +1,5 @@ button { - width: 80%; + width: 90%; background-color: #ffaead; border-radius: 20px; padding: 10px; diff --git a/src/components/list/ThoughtList.jsx b/src/components/list/ThoughtList.jsx index 1087911b..0b4ec8e3 100644 --- a/src/components/list/ThoughtList.jsx +++ b/src/components/list/ThoughtList.jsx @@ -1,7 +1,7 @@ import PropTypes from "prop-types" import "./list.css" import { LikeButton } from "./likeButton/LikeButton" -import { Time } from "./time" +import { Time } from "./time/Time" export const ThoughtList = ({ thoughts, onLike }) => { diff --git a/src/components/list/list.css b/src/components/list/list.css index 8d9b7c32..5a5a1902 100644 --- a/src/components/list/list.css +++ b/src/components/list/list.css @@ -9,7 +9,9 @@ .list-container ol { list-style-type: none; padding: 0; - width: 300px; + min-width: 300px; + max-width: 500px; + margin: 10px; } .list-container li { diff --git a/src/index.css b/src/index.css index 5f7915de..2f47068c 100644 --- a/src/index.css +++ b/src/index.css @@ -29,4 +29,12 @@ body { background-color: #ffaead; margin-bottom: 15px; padding: 10px; +} + +.content { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100%; } \ No newline at end of file From de22a47e340f8c64ccd5e13f5d0c06aabcf23f13 Mon Sep 17 00:00:00 2001 From: Gitte Beckmann Date: Sat, 26 Oct 2024 21:23:46 +0200 Subject: [PATCH 10/27] - added a @media for the submit button --- src/components/form/submitButton/submitButton.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/form/submitButton/submitButton.css b/src/components/form/submitButton/submitButton.css index 2d388a26..e96d2b62 100644 --- a/src/components/form/submitButton/submitButton.css +++ b/src/components/form/submitButton/submitButton.css @@ -5,4 +5,10 @@ button { padding: 10px; border: none; font-size: 16px; +} + +@media (min-width: 600px) { + button { + width: 50%; + } } \ No newline at end of file From 83c84e9a0e0f2b91a3061e0da9468022971321b7 Mon Sep 17 00:00:00 2001 From: Gitte Beckmann Date: Sat, 26 Oct 2024 21:31:05 +0200 Subject: [PATCH 11/27] -changed the color of the time and hearts counter to make them accessible --- src/components/list/likeButton/likeButton.css | 2 +- src/components/list/time/time.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/list/likeButton/likeButton.css b/src/components/list/likeButton/likeButton.css index c02e4469..d387e8a4 100644 --- a/src/components/list/likeButton/likeButton.css +++ b/src/components/list/likeButton/likeButton.css @@ -9,5 +9,5 @@ .counter { padding-left: 10px; - color: #838383; + color: #353333; } \ No newline at end of file diff --git a/src/components/list/time/time.css b/src/components/list/time/time.css index 860f6ea5..a2a66730 100644 --- a/src/components/list/time/time.css +++ b/src/components/list/time/time.css @@ -1,5 +1,5 @@ .time { - color: #c0c0c0; + color: #353333; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; From 17d1008428acd7928716fd734c0ee42dc265a0b3 Mon Sep 17 00:00:00 2001 From: Gitte Beckmann Date: Sat, 26 Oct 2024 21:33:34 +0200 Subject: [PATCH 12/27] - worked on CSS --- src/components/form/form.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/form/form.css b/src/components/form/form.css index e9dfabdf..0e33c9ee 100644 --- a/src/components/form/form.css +++ b/src/components/form/form.css @@ -23,6 +23,7 @@ p { margin: 0; padding: 10px; font-size: 14px; + font-weight: bold; } textarea { From 593d84c891a9e6119e0e2ff81d2dc1b8497ecf5d Mon Sep 17 00:00:00 2001 From: Gitte Beckmann Date: Sat, 26 Oct 2024 21:40:48 +0200 Subject: [PATCH 13/27] - added netlify link --- pull_request_template.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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: From 35f8b7b166bd0db5018f5b2b2583c907f74a3302 Mon Sep 17 00:00:00 2001 From: Gitte Beckmann Date: Sat, 26 Oct 2024 23:31:31 +0200 Subject: [PATCH 14/27] - added character Counter --- src/components/form/ThoughtForm.jsx | 39 +++++++++++++++++-- .../characterCounter/CharacterCounter.jsx | 18 +++++++++ .../characterCounter/characterCounter.css | 13 +++++++ 3 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 src/components/form/characterCounter/CharacterCounter.jsx create mode 100644 src/components/form/characterCounter/characterCounter.css diff --git a/src/components/form/ThoughtForm.jsx b/src/components/form/ThoughtForm.jsx index fac3ba23..ab2dd3e4 100644 --- a/src/components/form/ThoughtForm.jsx +++ b/src/components/form/ThoughtForm.jsx @@ -1,23 +1,54 @@ import PropTypes from "prop-types" import "./form.css" import { SubmitButton } from "./submitButton/SubmitButton" +import { CharacterCounter } from "./characterCounter/CharacterCounter" +import { useState } from "react" export const ThoughtsForm = ({ newThought, setNewThought, handleFormSubmit }) => { + const maxChars = 140 + const minChars = 5 + const [error, setError] = useState("") + + 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("") + handleFormSubmit(e) + setNewThought("") + } + } + return ( <>

      What is making you happy right now?

      -
      + +
      + +
      + {error &&

      {error}

      }
      - {/* */}
      diff --git a/src/components/form/characterCounter/CharacterCounter.jsx b/src/components/form/characterCounter/CharacterCounter.jsx new file mode 100644 index 00000000..9c1bc270 --- /dev/null +++ b/src/components/form/characterCounter/CharacterCounter.jsx @@ -0,0 +1,18 @@ +import PropTypes from "prop-types" +import "./characterCounter.css" + +export const CharacterCounter = ({ currentLength, maxChars }) => { + const charsLeft = maxChars - currentLength + const isOverLimit = currentLength > 140 + + return ( +
      + {charsLeft} characters left +
      + ) +} + +CharacterCounter.propTypes = { + currentLength: PropTypes.number.isRequired, + maxChars: PropTypes.number.isRequired, +} \ No newline at end of file diff --git a/src/components/form/characterCounter/characterCounter.css b/src/components/form/characterCounter/characterCounter.css new file mode 100644 index 00000000..4f1909b7 --- /dev/null +++ b/src/components/form/characterCounter/characterCounter.css @@ -0,0 +1,13 @@ +.counter-container { + display: flex; + justify-content: end; + width: 90%; +} + +.character-count { + font-weight: bold; +} + +.character-count.warning { + color: red; +} \ No newline at end of file From 5378baf9a934ef975b3aaf56f5793d9ba31df1eb Mon Sep 17 00:00:00 2001 From: Gitte Beckmann Date: Sat, 26 Oct 2024 23:46:38 +0200 Subject: [PATCH 15/27] - toggle for the like button --- src/components/list/likeButton/LikeButton.jsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/list/likeButton/LikeButton.jsx b/src/components/list/likeButton/LikeButton.jsx index 19879888..1aba2629 100644 --- a/src/components/list/likeButton/LikeButton.jsx +++ b/src/components/list/likeButton/LikeButton.jsx @@ -6,8 +6,11 @@ export const LikeButton = ({ thoughtId, hearts, onLike }) => { const [isClicked, setIsClicked] = useState(false) const handleClick = async () => { - await onLike(thoughtId) - setIsClicked(true) + const newClickedState = !isClicked + setIsClicked(newClickedState) + + await onLike(thoughtId, newClickedState) + // setIsClicked(true) } @@ -18,7 +21,7 @@ export const LikeButton = ({ thoughtId, hearts, onLike }) => { onClick={handleClick} > ❤️ - x {hearts} + x {hearts + (isClicked ? 1 : 0)} ) } From 334bda4981bdbdbe8bda5b61de9acf779e6d9269 Mon Sep 17 00:00:00 2001 From: Gitte Beckmann Date: Sun, 27 Oct 2024 00:50:33 +0200 Subject: [PATCH 16/27] - added an animation --- package.json | 3 ++- src/components/form/Animation.jsx | 20 +++++++++++++++++ src/components/form/ThoughtForm.jsx | 13 ++++++++--- src/components/form/animation.css | 35 +++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 src/components/form/Animation.jsx create mode 100644 src/components/form/animation.css diff --git a/package.json b/package.json index 8fd828e3..89698cc4 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "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/src/components/form/Animation.jsx b/src/components/form/Animation.jsx new file mode 100644 index 00000000..8eed7ec4 --- /dev/null +++ b/src/components/form/Animation.jsx @@ -0,0 +1,20 @@ + +import PropTypes from "prop-types" +import "./animation.css" + +export const HeartAnimation = ({ isVisible }) => { + return ( + <> + {isVisible && ( +
      + ❤️ {/* Heart emoji */} +
      + )} + + ) +} + +HeartAnimation.propTypes = { + isVisible: PropTypes.bool.isRequired, // Prop to control visibility +} + diff --git a/src/components/form/ThoughtForm.jsx b/src/components/form/ThoughtForm.jsx index ab2dd3e4..0a29e026 100644 --- a/src/components/form/ThoughtForm.jsx +++ b/src/components/form/ThoughtForm.jsx @@ -3,11 +3,13 @@ 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) @@ -26,8 +28,13 @@ export const ThoughtsForm = ({ newThought, setNewThought, handleFormSubmit }) => setError(`The message cannot exceed ${maxChars} characters.`); } else { setError("") + setShowHeart(true) handleFormSubmit(e) setNewThought("") + + setTimeout(() => { + setShowHeart(false) + }, 2000); } } @@ -41,8 +48,8 @@ export const ThoughtsForm = ({ newThought, setNewThought, handleFormSubmit }) => value={newThought} onChange={handleInputChange} placeholder="Type here..." - maxLength="maxChars"> - + maxLength={maxChars} + />
      @@ -52,7 +59,7 @@ export const ThoughtsForm = ({ newThought, setNewThought, handleFormSubmit }) =>
    - + ) } diff --git a/src/components/form/animation.css b/src/components/form/animation.css new file mode 100644 index 00000000..2c7c2491 --- /dev/null +++ b/src/components/form/animation.css @@ -0,0 +1,35 @@ +.heart-animation { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-size: 200px; + color: red; + animation: pulse 0.6s ease-in-out forwards, scale 0.6s ease-in-out forwards; + pointer-events: none; + z-index: 1000; +} + +@keyframes pulse { + 0% { + transform: translate(-50%, -50%) scale(1); + } + + 50% { + transform: translate(-50%, -50%) scale(1.2); + } + + 100% { + transform: translate(-50%, -50%) scale(1); + } +} + +@keyframes scale { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + } +} \ No newline at end of file From cf1e32563edbca908276e1c74cb537a1f91cb12d Mon Sep 17 00:00:00 2001 From: Gitte Beckmann Date: Sun, 27 Oct 2024 01:10:33 +0200 Subject: [PATCH 17/27] - max-length of characters changed --- src/components/form/ThoughtForm.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/form/ThoughtForm.jsx b/src/components/form/ThoughtForm.jsx index 0a29e026..c8ca543d 100644 --- a/src/components/form/ThoughtForm.jsx +++ b/src/components/form/ThoughtForm.jsx @@ -48,7 +48,7 @@ export const ThoughtsForm = ({ newThought, setNewThought, handleFormSubmit }) => value={newThought} onChange={handleInputChange} placeholder="Type here..." - maxLength={maxChars} + maxLength="maxChars" />
    From b20f2e28a14621606e3fcd4f80edbee62d32e1db Mon Sep 17 00:00:00 2001 From: Gitte Beckmann Date: Tue, 29 Oct 2024 21:47:47 +0100 Subject: [PATCH 18/27] - worked on the like function --- src/api.js | 4 ++-- src/components/HappyThoughts.jsx | 7 ++++++- src/components/list/likeButton/LikeButton.jsx | 3 +-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/api.js b/src/api.js index 5c751b46..868f2128 100644 --- a/src/api.js +++ b/src/api.js @@ -18,6 +18,6 @@ export const postThought = async (message) => { // 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}/hearts`, { method: "POST" }) - if (!response.ok) throw new Error("Failed to like throught") + 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 index f742653f..8c1a9d7c 100644 --- a/src/components/HappyThoughts.jsx +++ b/src/components/HappyThoughts.jsx @@ -32,9 +32,14 @@ export const HappyThoughts = () => { console.error("Error liking thought", error) } } + return (
    - + {loading ?

    Loading...

    : }
    ) diff --git a/src/components/list/likeButton/LikeButton.jsx b/src/components/list/likeButton/LikeButton.jsx index 1aba2629..1f1d773d 100644 --- a/src/components/list/likeButton/LikeButton.jsx +++ b/src/components/list/likeButton/LikeButton.jsx @@ -13,7 +13,6 @@ export const LikeButton = ({ thoughtId, hearts, onLike }) => { // setIsClicked(true) } - return ( <> - x {hearts + (isClicked ? 1 : 0)} + x {hearts} ) } From 0f57ab50ee1bd8b83b433aeea1c9c563bebaffdb Mon Sep 17 00:00:00 2001 From: Gitte Beckmann Date: Wed, 30 Oct 2024 08:53:22 +0100 Subject: [PATCH 19/27] - worked on the like button --- src/components/HappyThoughts.jsx | 4 ++-- src/components/list/likeButton/LikeButton.jsx | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/components/HappyThoughts.jsx b/src/components/HappyThoughts.jsx index 8c1a9d7c..70290504 100644 --- a/src/components/HappyThoughts.jsx +++ b/src/components/HappyThoughts.jsx @@ -21,12 +21,12 @@ export const HappyThoughts = () => { } // 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) => { + const handleLike = async (thoughtId, isClicked) => { try { await likeThought(thoughtId) setThoughts((prevThoughts) => prevThoughts.map((thought) => - thought._id === thoughtId ? { ...thought, hearts: thought.hearts + 1 } : thought + thought._id === thoughtId ? { ...thought, hearts: thought.hearts + (isClicked ? 1 : -1) } : thought )) } catch (error) { console.error("Error liking thought", error) diff --git a/src/components/list/likeButton/LikeButton.jsx b/src/components/list/likeButton/LikeButton.jsx index 1f1d773d..83891744 100644 --- a/src/components/list/likeButton/LikeButton.jsx +++ b/src/components/list/likeButton/LikeButton.jsx @@ -1,13 +1,27 @@ import "./likeButton.css" -import { useState } from "react" +import { useState, useEffect } from "react" import PropTypes from "prop-types" export const LikeButton = ({ thoughtId, hearts, onLike }) => { const [isClicked, setIsClicked] = useState(false) + useEffect(() => { + const likedThoughts = JSON.parse(localStorage.getItem("likedThoughts")) || [] + if (likedThoughts.includes(thoughtId)) { + setIsClicked(true) + } + }, [thoughtId]) + + const handleClick = async () => { const newClickedState = !isClicked setIsClicked(newClickedState) + const likedThoughts = JSON.parse(localStorage.getItem("LikedThoughts")) || [] + if (newClickedState) { + localStorage.setItem("likeThoughts", JSON.stringify([...likedThoughts, thoughtId])) + } else { + localStorage.setItem("likedThoughts", JSON.stringify(likedThoughts.filter(id => id !== thoughtId))) + } await onLike(thoughtId, newClickedState) // setIsClicked(true) From 0475f758a6e9f230b4a1f8494cbcb3a6050a14a6 Mon Sep 17 00:00:00 2001 From: Gitte Beckmann Date: Sun, 10 Nov 2024 18:50:35 +0100 Subject: [PATCH 20/27] - I tidied up the code to improve readability --- src/App.jsx | 8 +- src/api.js | 20 ++-- src/components/HappyThoughts.jsx | 69 ++++++----- src/components/Header.jsx | 18 ++- src/components/form/Animation.jsx | 21 ++-- src/components/form/ThoughtForm.jsx | 108 +++++++++--------- src/components/form/animation.css | 48 ++++---- .../characterCounter/CharacterCounter.jsx | 18 +-- .../characterCounter/characterCounter.css | 10 +- src/components/form/form.css | 71 ++++++------ .../form/submitButton/SubmitButton.jsx | 7 +- .../form/submitButton/submitButton.css | 18 +-- src/components/list/ThoughtList.jsx | 58 +++++----- src/components/list/likeButton/LikeButton.jsx | 66 +++++------ src/components/list/likeButton/likeButton.css | 10 +- src/components/list/list.css | 52 ++++----- src/components/list/time/Time.jsx | 36 +++--- src/components/list/time/time.css | 8 +- src/hooks.js | 26 ++--- 19 files changed, 326 insertions(+), 346 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 58df8c03..ffc8e569 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,6 +1,5 @@ - -import { HappyThoughts } from './components/HappyThoughts'; -import { HappyThoughtsHeader } from './components/Header'; +import { HappyThoughts } from "./components/HappyThoughts"; +import { HappyThoughtsHeader } from "./components/Header"; export const App = () => { return ( @@ -9,5 +8,4 @@ export const App = () => { ); -}; - +}; \ No newline at end of file diff --git a/src/api.js b/src/api.js index 868f2128..af4f344f 100644 --- a/src/api.js +++ b/src/api.js @@ -2,22 +2,22 @@ const BASE_URL = "https://happy-thoughts-ux7hkzgmwa-uc.a.run.app/thoughts" // fetching data from the API export const fetchThoughts = async () => { - const response = await fetch(BASE_URL) - return response.json() + 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 + 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") + 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 index 70290504..9471cd27 100644 --- a/src/components/HappyThoughts.jsx +++ b/src/components/HappyThoughts.jsx @@ -5,43 +5,42 @@ import { useFetchThoughts } from "../hooks" import { postThought, likeThought } from "../api" export const HappyThoughts = () => { - const [newThought, setNewThought] = useState("") - const { thoughts, setThoughts, loading, getThoughts } = useFetchThoughts() + 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 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) - } + // 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...

    : } -
    - ) -} - + return ( +
    + + {loading ?

    Loading...

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

    Uplifting Moments

    -

    Share, Like, and Celebrate What Makes Life Beautiful

    -
    -
    - ) + 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 index 8eed7ec4..8dcf3a8e 100644 --- a/src/components/form/Animation.jsx +++ b/src/components/form/Animation.jsx @@ -1,20 +1,19 @@ - import PropTypes from "prop-types" import "./animation.css" export const HeartAnimation = ({ isVisible }) => { - return ( - <> - {isVisible && ( -
    - ❤️ {/* Heart emoji */} -
    - )} - - ) + return ( + <> + {isVisible && ( +
    + ❤️ {/* Heart emoji */} +
    + )} + + ) } HeartAnimation.propTypes = { - isVisible: PropTypes.bool.isRequired, // Prop to control visibility + isVisible: PropTypes.bool.isRequired, // Prop to control visibility } diff --git a/src/components/form/ThoughtForm.jsx b/src/components/form/ThoughtForm.jsx index c8ca543d..c080d523 100644 --- a/src/components/form/ThoughtForm.jsx +++ b/src/components/form/ThoughtForm.jsx @@ -6,68 +6,66 @@ 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 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() + const handleInputChange = (e) => { + setNewThought(e.target.value) + setError("") + } - // 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("") + const handleSubmit = (e) => { + e.preventDefault() - setTimeout(() => { - setShowHeart(false) - }, 2000); - } + // 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?

    -
    -