-
Notifications
You must be signed in to change notification settings - Fork 145
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 😊 Joyce Kuo #105
base: main
Are you sure you want to change the base?
Changes from all commits
1e43ceb
6a96929
7be96da
ad45e16
2dd5569
564fcf5
4845ef1
51eb810
3e56559
96828fb
d092d12
096958d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,68 @@ | ||
import { useState, useEffect } from 'react'; | ||
import SubmitForm from './components/SubmitForm'; | ||
import { HappyThought } from './components/HappyThought'; | ||
|
||
const URL = 'https://happy-thoughts-ux7hkzgmwa-uc.a.run.app/thoughts'; | ||
|
||
export const App = () => { | ||
return <div>Find me in src/app.jsx!</div>; | ||
const [thoughts, setThoughts] = useState([]); | ||
|
||
// Fetch the most recent thoughts | ||
useEffect(() => { | ||
const fetchThoughts = async () => { | ||
try { | ||
const response = await fetch(URL); | ||
const result = await response.json(); | ||
setThoughts(result); // Sets the array of 20 latest thoughts | ||
} catch (error) { | ||
console.error('Error fetching thoughts:', error); | ||
} | ||
}; | ||
fetchThoughts(); | ||
}, []); | ||
|
||
// Adds a new thought to the list | ||
const addThought = (newThought) => { | ||
setThoughts((prevThoughts) => [newThought, ...prevThoughts.slice(0, 19)]); | ||
}; | ||
|
||
// Like a thought | ||
const handleLike = async (thoughtId) => { | ||
const likeURL = `${URL}/${thoughtId}/like`; | ||
try { | ||
const response = await fetch(likeURL, { | ||
method: 'POST', | ||
}) | ||
if (response.ok) { | ||
// Update hearts count for liked thought | ||
setThoughts((prevThoughts) => | ||
prevThoughts.map((thought) => | ||
thought._id === thoughtId ? { ...thought, hearts: thought.hearts + 1 } : thought | ||
) | ||
); | ||
} | ||
} catch (error) { | ||
console.error('Error liking the thought:', error); | ||
} | ||
}; | ||
|
||
return ( | ||
<div className="App"> | ||
<h1>Happy Thoughts 😊</h1> | ||
<div className="content"> | ||
<SubmitForm onSubmit={addThought} /> | ||
<div className="HappyThoughts"> | ||
{thoughts.map((thought) => ( | ||
<HappyThought | ||
key={thought._id} | ||
thought={thought} | ||
onLike={handleLike} | ||
/> | ||
))} | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default App; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { useState, useEffect } from 'react'; | ||
|
||
// Function to calculate time difference | ||
const timeAgo = (createdAt) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But could maybe be abstracted into a utils folder? |
||
const now = new Date(); | ||
const createdTime = new Date(createdAt); | ||
const timeDiff = now - createdTime; | ||
|
||
const seconds = Math.floor(timeDiff / 1000); | ||
const minutes = Math.floor(seconds / 60); | ||
const hours = Math.floor(minutes / 60); | ||
const days = Math.floor(hours / 24); | ||
|
||
if (days > 0) { | ||
return `${days} day${days > 1 ? 's' : ''} ago`; | ||
} else if (hours > 0) { | ||
return `${hours} hour${hours > 1 ? 's' : ''} ago`; | ||
} else if (minutes > 0) { | ||
return `${minutes} minute${minutes > 1 ? 's' : ''} ago`; | ||
} else { | ||
return `${seconds} second${seconds > 1 ? 's' : ''} ago`; | ||
} | ||
}; | ||
|
||
export const HappyThought = ({ thought, onLike }) => { | ||
// Track how long ago the thought was posted | ||
const [timeSincePosted, setTimeSincePosted] = useState(timeAgo(thought.createdAt)); | ||
|
||
// Track whether the heart button has been clicked | ||
const [isClicked, setIsClicked] = useState(false); | ||
|
||
// Update time difference every minute | ||
useEffect(() => { | ||
const interval = setInterval(() => { | ||
setTimeSincePosted(timeAgo(thought.createdAt)); | ||
}, 60000); // Update every 60 seconds | ||
return () => clearInterval(interval); | ||
}, [thought.createdAt]); | ||
|
||
const handleLikeClick = () => { | ||
if (!isClicked) { | ||
onLike(thought._id); // Trigger like functionality | ||
setIsClicked(true); // Set the clicked state to true to persist the clicked state | ||
} | ||
}; | ||
|
||
|
||
return ( | ||
<div className="happy-thought"> | ||
<p>{thought.message}</p> | ||
<div className="thought-actions"> | ||
<button | ||
className={`heart-button ${isClicked ? 'liked' : ''}`}// Change color if liked | ||
onClick={handleLikeClick} | ||
>🩷</button> | ||
<span className="likes-count">x {thought.hearts}</span> | ||
<span className="time-posted">{timeSincePosted}</span> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { useState } from 'react'; | ||
import PropTypes from 'prop-types'; | ||
|
||
export const SubmitForm = ({ onSubmit }) => { | ||
|
||
const [thought, setThought] = useState(''); | ||
const [error, setError] = useState(''); | ||
|
||
const handleSubmit = async (event) => { | ||
event.preventDefault(); | ||
|
||
const URL = 'https://happy-thoughts-ux7hkzgmwa-uc.a.run.app/thoughts'; | ||
|
||
// Validate message length | ||
if (thought.length < 5 || thought.length > 140) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⭐ |
||
setError('Message must be between 5 and 140 characters.'); | ||
return; | ||
} | ||
|
||
try { | ||
const res = await fetch(URL, { | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
body: JSON.stringify({ message: thought }), | ||
}); | ||
const newThought = await res.json(); | ||
onSubmit(newThought); // Passes new thought to App.jsx | ||
setThought(''); // Clears input field | ||
setError(''); // Clears any previous errors | ||
} catch (error) { | ||
console.log('Error posting thought:', error); | ||
} | ||
}; | ||
|
||
return ( | ||
<form onSubmit={handleSubmit} className="submit-form"> | ||
<label htmlFor="thought">What's making you happy right now?</label> | ||
<textarea | ||
type="text" | ||
id="thought" | ||
value={thought} | ||
rows={2} | ||
className="happy-thought-input" | ||
placeholder="React is making me happy!!" | ||
onChange={(e) => setThought(e.target.value)} | ||
/> | ||
<button type="submit">🩷 Send Happy Thought 🩷</button> | ||
{error && <p className="error-message">{error}</p>} | ||
</form> | ||
|
||
); | ||
}; | ||
|
||
SubmitForm.propTypes = { | ||
onSubmit: PropTypes.func.isRequired, | ||
}; | ||
|
||
export default SubmitForm; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for sharing your process! I can relate to getting stuck with details 😅