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

Jonas Happy Thought App #92

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
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: 12 additions & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
import { HappyThoughts } from './components/HappyThoughts';




export const App = () => {
return <div>Find me in src/app.jsx!</div>;
return (
<div>

< HappyThoughts />
</div>
);
};

72 changes: 72 additions & 0 deletions src/components/HappyThoughts.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Header } from "./Header";
import { useState, useEffect } from "react";
import { ThoughtForm } from "./ThoughtForm";
import { ThoughtList } from "./ThoughtList";

export const HappyThoughts = () => {
const [thoughts, setThoughts] = useState([]);
const [newThought, setNewThought] = useState("");

useEffect(() => {
fetch("https://project-happy-thoughts-api-b7a3.onrender.com/thoughts")
.then((res) => {
if (!res.ok) {
throw new Error("Failed to fetch thoughts");
}
return res.json();
})
.then((data) => {
setThoughts(data); // Backend should return an array of thoughts
})
.catch((error) => {
console.error("Fel vid hämtning av tankar:", error);
});
}, []);

const handleNewThought = (event) => {
setNewThought(event.target.value);
};

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

const PostOptions = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ message: newThought }),
};

fetch("https://project-happy-thoughts-api-b7a3.onrender.com/thoughts", PostOptions)
.then((res) => {
if (!res.ok) {
throw new Error("Failed to post a new thought");
}
return res.json();
})
.then((data) => {
setThoughts((previousThoughts) => [data, ...previousThoughts]);
setNewThought(""); // Clear the input field
})
.catch((error) => {
console.error("Error posting Happy Thought:", error);
});
};

return (
<div>
<Header />
<main className="main-wrapper">
<div className="main-content">
<ThoughtForm
newThought={newThought}
onNewThoughtChange={handleNewThought}
onFormSubmit={onFormSubmit}
/>
<ThoughtList thoughts={thoughts} />
</div>
</main>
</div>
);
};
10 changes: 10 additions & 0 deletions src/components/Header.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Header.jsx

export const Header = () => {
return (
<div className="header">
<h1>Project Happy Thoughts</h1>
<h2>by Jonas</h2>
</div>
);
};
33 changes: 33 additions & 0 deletions src/components/ThoughtForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// ThoughtForm.jsx

import React from "react";


export const ThoughtForm = ({ onFormSubmit, newThought, onNewThoughtChange }) => {

const disableSubmit = newThought.length < 5 || newThought.length > 140;

return (
<form className="form-container" onSubmit={onFormSubmit}>
<label>
<h3>What makes you happy..</h3>
<textarea
className="thought-input"
value={newThought}
onChange={onNewThoughtChange}
placeholder="Write something here"
required
/>
</label>

<button
type="submit"
className="submit-thought"
aria-label="button for submit thought"
disabled={disableSubmit}
>
❤️ Send Happy Thought ❤️
</button>
</form>
);
};
47 changes: 47 additions & 0 deletions src/components/ThoughtLikes.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useState, useEffect } from "react";

export const ThoughtLikes = ({ id, heart }) => {
const [likes, setLikes] = useState(() => {
const savedLikes = localStorage.getItem(`likes-${id}`);
return savedLikes !== null ? Number(savedLikes) : heart || 0;
});

useEffect(() => {
localStorage.setItem(`likes-${id}`, likes);
}, [likes, id]);

const handleLikes = () => {
fetch(`https://project-happy-thoughts-api-b7a3.onrender.com/thoughts/${id}/like`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
})
.then((res) => {
if (!res.ok) {
throw new Error("Failed to like the thought");
}
return res.json();
})
.then(() => {
setLikes((prevLikes) => prevLikes + 1);
})
.catch((error) => {
console.error("Error liking the thought:", error);
});
};

return (
<div className="likes">
<button
onClick={handleLikes}
aria-label="Like-button"
className={likes === 0 ? "heart-button" : "liked-button"}
type="button"
>
❤️
</button>
<p>x {likes}</p>
</div>
);
};
20 changes: 20 additions & 0 deletions src/components/ThoughtList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { formatDistanceToNow } from "date-fns";
import { ThoughtLikes } from "./ThoughtLikes.jsx";

export const ThoughtList = ({ thoughts }) => {
return (
<section className="thought-section">
{thoughts.map((thought) => (
<div className="thought-wrapper" key={thought._id}>
<div className="input-message">{thought.message}</div>
<div className="info-wrapper">
<ThoughtLikes id={thought._id} heart={thought.hearts} />
<div className="time">
{formatDistanceToNow(new Date(thought.createdAt), { addSuffix: true })}
</div>
</div>
</div>
))}
</section>
);
};
129 changes: 126 additions & 3 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,130 @@
-moz-osx-font-smoothing: grayscale;
}

code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}

.header {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 1rem;

}

.main-wrapper {
display: flex;
flex-direction: column;
align-items: center;

}

.form-container,
.thought-wrapper {
background-color: #ebebeb;
border: 1px solid rgb(105, 105, 105);
box-shadow: rgb(0, 0, 0) 8px 8px 0px 0px;
margin: 13px 0 13px 8px;
padding: 1rem;
max-width: 600px;
width: 90%;

}

h3 {
text-align: center;

}





.thought-input {
width: 100%;
height: 80px;
padding: 12px 20px;
border: 2px solid #d6d6d6;
border-radius: 4px;
background-color: #fff;
font-size: 16px;
resize: none;
Copy link
Contributor

Choose a reason for hiding this comment

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

⭐️

}

.submit-thought {
border-radius: 26px;
border: none;
background-color: #ffadad;
color: #000;
font-size: 16px;
font-weight: 600;
margin: 8px 0 5px;
padding: 10px 6px;
cursor: pointer;
width: 100%;

}

.thought-section {
padding: 0px;
display: flex;
flex-direction: column;
margin-bottom: 2rem;
}

.heart-button,
.liked-button {
border-radius: 50%;
border: none;
padding: 16px;
cursor: pointer;
}

.heart-button:hover {
background-color: #ffadad;
}

.likes {
display: flex;
align-items: center;
color: rgb(105, 105, 105);
}

.likes>* {
margin-right: 5px;
}

.time {
color: rgb(105, 105, 105);
font-size: 14px;
padding-bottom: 1rem;
}

.info-wrapper {
display: flex;
justify-content: space-between;
align-items: flex-end;
}


@media (max-width: 600px) {
.main-wrapper {
padding: 0.5rem;

}

h1 {
text-align: center;
padding-bottom: 2px;
}

.form-container,
.thought-wrapper {
width: 95%;

}
}