Live Demo: https://happythoughtstalo.netlify.app/
A simple, positive-messaging web app built with React, Vite, and Tailwind CSS. Users can post “happy thoughts,” view the latest messages from a public API, and send likes.
Key features include:
- Controlled form for typing and submitting messages with client-side validation
- API integration using
fetch: GET recent thoughts, POST new thoughts, POST likes - Component lifecycle with
useEffectfor initial data fetch and loading state - Tailwind CSS: Utility-first styling, custom
shadow-sharpandanimate-fade-invia@layer utilities - Human-friendly timestamps via Day.js (
x minutes ago) - Per-user like tracking using
localStorageto prevent double-likes - Responsive design (mobile-first min 320px up to desktop 1600px) with Tailwind’s breakpoints
- Accessibility:
<label>associations,aria-invalid,role="alert"for errors, andaria-label/aria-pressedon like buttons - Custom fonts (IvyMode & Eixample Dip) from Adobe Fonts
-
App.jsx (Parent)
- Manages state:
thoughts,loading,likedIds, andlastAddedId - Uses
useEffectto fetch the latest 20 thoughts on mount - Defines
addThoughtto prepend new messages and trigger animation - Defines
handleLiketo POST likes, update state, and persist liked IDs inlocalStorage
- Manages state:
-
Form.jsx (Child)
- Implements a controlled
<textarea>bound tomessagestate - Client-side validation for 5–140 characters, with inline error messages (
role="alert") - Live character counter under the textarea, turns red at 140+
- Posts new thoughts to the API, handles loading state (
submitting) and errors
- Implements a controlled
-
ThoughtCard.jsx (Sibling)
- Receives props:
message,hearts,createdAt,isLiked,isNew - Renders a card with message text, like button, count, and relative timestamp (
dayjs.fromNow()) - Applies
animate-fade-inwhenisNewis true for a smooth entry animation - Accessible ❤️ button with
aria-labelandaria-pressed
- Receives props:
-
Styling & Utilities
- Tailwind CSS for utility-first styling and responsive layout
- Custom shadow-sharp utility for crisp black drop-shadows
@layer utilitiesinindex.cssfor manual keyframes and.animate-fade-inclass
Requirements
☑️ Design fidelity: Follows the provided mockup closely
☑️ Responsive: Mobile-first layout (320px) up to large screens (1600px+)
☑️ Form & card rendering: Textarea + submit button clears on click and displays the new message in a card
☑️ Clean code: Component-based structure, readable JSX, and DRY utility classes
Stretch Goals
☑️ Live character counter under the textarea, turns red at 140+
☑️ Error states: Inline error message when input is invalid
☑️ Entry animation: New thoughts animate with a fade-in slide-down effect
Requirements
☑️ POST new thoughts: Happy thoughts are sent to the API on form submission
☑️ List rendering & update: Thoughts are fetched/listed (newest first) at load and update live after submission
☑️ Show content & likes: Each card displays message text and current like count
☑️ Like button: Sends a like to the API and increments the count on click
Stretch Goals
☑️ Per-user like tracking via localStorage
☑️ Loading states: “Loading…” message on initial fetch and disabled UI during POST requests
- API Documentation: GET
/returns JSON and list of all endpoints (Express List Endpoints) - Read all thoughts: GET
/thoughtswith pagination (.skip(),.limit()), filtering (?minHearts=), and sorting (?sortBy=&order=) - Read single thought: GET
/thoughts/:id(404 if not found) - Like a thought: POST
/thoughts/:id/like(atomic$inchearts) - Create thought (auth): POST
/thoughtsprotected by JWT, setsowner - Update thought (auth): PUT
/thoughts/:id(only owner, validation,runValidators) - Delete thought (auth): DELETE
/thoughts/:id(only owner) - Sign up: POST
/auth/registerwithusername,email,password(express-validator + bcrypt) - Log in: POST
/auth/loginreturns JWT (1h expiry) - Mongoose models:
User&Thoughtschemas with built-in validation, virtuals, methods - Input validation & errors: Detailed error messages & correct status codes; unique
username/email - Error handling: Central error-handler, per-route try/catch, status codes
- Password hashing:
bcryptwith salt rounds in Mongoose virtual setter - Deployed backend: Running on Render, connected to MongoDB Atlas
- Frontend integration: All endpoints reflected in React, protected writes, UI error‐handling
- Filtering & sorting on backend & UI (
?minHearts=, sort dropdown) - Pagination & infinite scroll on frontend
- Field-level error display on registration form
- Token persistence: Store JWT in
localStorage, send on all write requests - “My Likes” view: Frontend-only filter of liked thoughts (or GET
/thoughts?owner=if preferred) - Express Router structure:
routes/,middleware/,models/separation