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

Happy-thoughts-Typescript #111

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 7 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,16 @@
<h1 align="center">
<a href="">
<img src="/src/assets/happy-thoughts.svg" alt="Project Banner Image">
</a>
</h1>

# Happy thoughts Project
### Convert a React project into TypeScript
We had to convert to Typescript one of our projects were we used React, I decided to go for T´Happy thoughts, because it has been one of my favorite projects.

In this week's project, you'll be able to practice your React state skills by fetching and posting data to an API.

## Getting Started with the Project

### Dependency Installation & Startup Development Server

Once cloned, navigate to the project's root directory and this project uses npm (Node Package Manager) to manage its dependencies.

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.

```bash
npm i && code . && npm run dev
```

### The Problem

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?
I had to instal Typescript amd I created a new branch with the same name, I decided to fix the corrections that I had from Matilda when she reviewed the original project, so I could have it 100% ready to convert it to Typescript.

### View it live
I am still strugling a bit determinating where the data types needs to be define, so I was trying to guiding my self with the course material, extra videos on Youtube and Chat gpt.

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.

## Instructions
### View it live

<a href="instructions.md">
See instructions of this project
</a>
Netlify link:
https://happy-thoughts-typescript-estefanny.netlify.app/
Empty file added api.js
Empty file.
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
2 changes: 1 addition & 1 deletion instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ To achieve this, we've built an API with three endpoints. Note that all of the t

## Fetch recent thoughts

`GET https://happy-thoughts-ux7hkzgmwa-uc.a.run.app/thoughts`
`GET `https://happy-thoughts-ux7hkzgmwa-uc.a.run.app/thoughts

This will return the latest 20 thoughts from the API, looking something like this:

Expand Down
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"lint": "eslint . --ext js,jsx,ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"date-fns": "^4.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
Expand All @@ -21,6 +22,10 @@
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"vite": "^4.4.5"
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"typescript": "^5.7.2",
"vite": "^4.4.5",
"vite-plugin-eslint": "^1.6.0"
}
}
3 changes: 0 additions & 3 deletions src/App.jsx

This file was deleted.

58 changes: 58 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { useEffect, useState } from "react";
import ThoughtForm from "./components/ThoughtForm";
import ThoughtList from "./components/ThoughtList";
import "./index.css";
import "./components/styleForm.css";

const API_URL = "https://happy-thoughts-ux7hkzgmwa-uc.a.run.app/thoughts";

// Define the structure of a thought
interface Thought {
_id: string;
message: string;
hearts: number;
createdAt: string;
}

export const App: React.FC = () => {
const [thoughts, setThoughts] = useState<Thought[]>([]); // Array of thoughts
const [error, setError] = useState<string>(""); // Error message

// Fetch thoughts from the API on component mount
useEffect(() => {
fetch(API_URL)
.then((response) => response.json())
.then((data: Thought[]) => setThoughts(data))
.catch((error) => console.error("Error fetching data:", error));
}, []);

// Handle new thought submission
const handleNewThought = (newThought: Thought) => {
setThoughts([newThought, ...thoughts]);
};

// Handle like button click
const handleLikeClick = (thoughtId: string) => {
const likeUrl = `${API_URL}/${thoughtId}/like`;

fetch(likeUrl, { method: "POST" })
.then((response) => response.json())
.then((updatedThought: Thought) => {
setThoughts((prevThoughts) =>
prevThoughts.map((thought) =>
thought._id === updatedThought._id ? updatedThought : thought
)
);
})
.catch((error) => console.error("Error liking thought:", error));
};

return (
<div className="app">
<h1>Happy Thoughts</h1>
<ThoughtForm onNewThought={handleNewThought} setError={setError} />
{error && <p className="error-message">{error}</p>}
<ThoughtList thoughts={thoughts} onLike={handleLikeClick} />
</div>
);
};
57 changes: 57 additions & 0 deletions src/components/ThoughtForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React, { useState } from "react";

const API_URL = "https://happy-thoughts-ux7hkzgmwa-uc.a.run.app/thoughts";

// Define props for the ThoughtForm component
interface ThoughtFormProps {
onNewThought: (newThought: { _id: string; message: string; hearts: number; createdAt: string }) => void;
setError: (error: string) => void;
}

const ThoughtForm: React.FC<ThoughtFormProps> = ({ onNewThought, setError }) => {
const [newThought, setNewThought] = useState<string>("");

const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();

if (newThought.length < 5 || newThought.length > 140) {
setError("Message must be between 5 and 140 characters.");
return;
}

setError("");

fetch(API_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: newThought }),
})
.then((response) => response.json())
.then((data) => {
onNewThought(data); // Pass the new thought to the parent
setNewThought(""); // Clear the input
})
.catch((error) => {
console.error("Error posting thought:", error);
setError("Failed to post the thought. Please try again.");
});
};

return (
<form className="thought-form" onSubmit={handleSubmit}>
<span className="thought-prompt">What's making you happy right now?</span>
<textarea
value={newThought}
onChange={(e) => setNewThought(e.target.value)}
placeholder="Write your happy thought here..."
rows={4} // TypeScript expects numbers for numeric attributes
maxLength={140}
/>
<button type="submit" aria-label="Send your happy thought">
Send Happy Thought
</button>
</form>
);
};

export default ThoughtForm;
41 changes: 41 additions & 0 deletions src/components/ThoughtItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from "react";
import { formatDistanceToNow } from "date-fns";

// Define the type for a single thought
interface Thought {
_id: string;
message: string;
hearts: number;
createdAt: string; // Assuming this is an ISO date string
}

// Define the props for the ThoughtItem component
interface ThoughtItemProps {
thought: Thought;
onLike: (id: string) => void;
}

const ThoughtItem: React.FC<ThoughtItemProps> = ({ thought, onLike }) => {
return (
<li className="thought-item">
<p className="thought-message">{thought.message}</p>
<div className="thought-footer">
<div className="thought-hearts">
<button
className={`heart-button ${thought.hearts > 0 ? "liked" : ""}`}
onClick={() => onLike(thought._id)}
aria-label={`Like the thought: "${thought.message}". Currently has ${thought.hearts} likes`}
>
❤️
</button>
<span>x {thought.hearts}</span>
</div>
<small className="thought-date">
{formatDistanceToNow(new Date(thought.createdAt), { addSuffix: true })}
</small>
</div>
</li>
);
};

export default ThoughtItem;
28 changes: 28 additions & 0 deletions src/components/ThoughtList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from "react";
import ThoughtItem from "./ThoughtItem";

// Define the type for a single thought
interface Thought {
_id: string;
message: string;
hearts: number;
createdAt: string; // ISO date string
}

// Define the props for the ThoughtList component
interface ThoughtListProps {
thoughts: Thought[];
onLike: (id: string) => void;
}

const ThoughtList: React.FC<ThoughtListProps> = ({ thoughts, onLike }) => {
return (
<ul>
{thoughts.map((thought) => (
<ThoughtItem key={thought._id} thought={thought} onLike={onLike} />
))}
</ul>
);
};

export default ThoughtList;
Loading