-
-
Notifications
You must be signed in to change notification settings - Fork 358
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
London10-AfshaHossain-FullStackProjectAssessment-Week2-Level 250 #399
Changes from 24 commits
88fdbd2
362ac54
546d415
9f59cba
d9bbe3f
1536a64
97f2257
6203909
4365e2d
ed7800d
6fb98d0
094ddfe
3cac61d
d106af8
ec85784
d41d555
b1f23f3
9771eaf
d09385c
2eb4d6b
c138bad
46c4fa8
654f50c
c394491
1b9e46f
e004e72
b4a3335
0a98d5d
6fa3eb6
5df3897
1f88ff6
1012a51
482bd51
8ff9f0d
9e97097
8d9682b
f832310
2570763
9b96469
bd7dc12
bf46c8b
734637a
ec5f6d8
2d9b9bb
9b3ed5f
e5ee4c6
8733d6a
cf043d0
70781ac
c55af80
49688fd
f7ed0b9
e4f65f8
9d35892
594afe7
357f4b8
26ceafd
a197fee
d12703c
69fc9ab
d8e6153
cf27ce9
e9d30a8
282d860
2214a6c
befd54c
822cd7c
7f7d27b
96168c8
fa97d00
55d528b
d3f3df6
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 |
---|---|---|
@@ -0,0 +1 @@ | ||
node_modules/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
node_modules/ | ||
plan.drawio | ||
plan.md |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
<mxfile host="65bd71144e"> | ||
<diagram id="BdzzjRRzsWgrQ7JI_RHI" name="Page-1"> | ||
<mxGraphModel dx="529" dy="348" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0"> | ||
<root> | ||
<mxCell id="0"/> | ||
<mxCell id="1" parent="0"/> | ||
<mxCell id="3" value="App.jsx<br>videoList, setVideoList as props" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1"> | ||
<mxGeometry x="10" y="80" width="150" height="110" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="4" value="VideoList.jsx" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1"> | ||
<mxGeometry x="200" y="80" width="90" height="30" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="5" value="VideoCard.jsx" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1"> | ||
<mxGeometry x="200" y="200" width="90" height="40" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="7" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="4" target="5" edge="1"> | ||
<mxGeometry width="50" height="50" relative="1" as="geometry"> | ||
<mxPoint x="240" y="310" as="sourcePoint"/> | ||
<mxPoint x="290" y="260" as="targetPoint"/> | ||
</mxGeometry> | ||
</mxCell> | ||
<mxCell id="10" value="" style="endArrow=classic;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.25;exitDx=0;exitDy=0;" parent="1" source="3" target="4" edge="1"> | ||
<mxGeometry width="50" height="50" relative="1" as="geometry"> | ||
<mxPoint x="240" y="310" as="sourcePoint"/> | ||
<mxPoint x="290" y="260" as="targetPoint"/> | ||
</mxGeometry> | ||
</mxCell> | ||
<mxCell id="11" value="Main" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1"> | ||
<mxGeometry x="80" y="360" width="90" height="60" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="16" value="" style="edgeStyle=none;html=1;" edge="1" parent="1" source="12" target="13"> | ||
<mxGeometry relative="1" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="12" value="100" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1"> | ||
<mxGeometry x="80" y="460" width="80" height="60" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="13" value="200" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1"> | ||
<mxGeometry x="80" y="550" width="80" height="60" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="14" value="200" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1"> | ||
<mxGeometry x="205" y="360" width="80" height="60" as="geometry"/> | ||
</mxCell> | ||
<mxCell id="15" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="11" target="12"> | ||
<mxGeometry width="50" height="50" relative="1" as="geometry"> | ||
<mxPoint x="180" y="480" as="sourcePoint"/> | ||
<mxPoint x="230" y="430" as="targetPoint"/> | ||
<Array as="points"/> | ||
</mxGeometry> | ||
</mxCell> | ||
</root> | ||
</mxGraphModel> | ||
</diagram> | ||
</mxfile> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
|
||
1. Videos should be loaded from a local javascript variable containing the data included in `exampleresponse.json` | ||
```json | ||
{ | ||
"id": 523523, | ||
"title": "Never Gonna Give You Up", | ||
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", | ||
"rating": 23 | ||
}, | ||
``` | ||
- Log `exampleresponse.json` into app to check data is arriving into frontend. ✅ | ||
|
||
- We will create a VideoCard component. This will contain a single videos data.✅ | ||
- Hardcode the VideoCard component with information from the object above.✅ | ||
* replace watch?v= with embed/; | ||
* data.url.replace("watch?v=", "embed/"); | ||
|
||
**Notes** We will use props to send the data from exampleresponse.json file to our VideoList component | ||
|
||
2. For each video, display a React component that contains | ||
- The videos title ✅ | ||
- An embedded video ✅ | ||
- The number of votes the video has ✅ | ||
- A button that when clicked removes the video ✅ | ||
|
||
- Create a VideoList component with videoList prop (which is state from app level) ✅ | ||
- We need to map through each of the objects from the state that we are passing as a single prop to the VideoList component ✅ | ||
- For each of these objects we are rendering a VideoCard component ✅ | ||
- Each VideoCard component will take the props [id={id}, title={title}, url={url}, rating={rating}] ✅ | ||
|
||
3. On each video submission there should be two buttons | ||
- "Up Vote" - This increases the vote score when clicked ✅ | ||
- "Down Vote" - This decreases the vote score when clicked ✅ | ||
|
||
- Hard code one button at time add state where necessary | ||
|
||
4. On the page there must be another React component that will add a Video. | ||
- It should include fields to add a | ||
- Title ✅ | ||
- Url ✅ | ||
- When a button is clicked the video should be added to the list ✅ | ||
|
||
a. Create a VideoForm component ✅ | ||
b. Create mock structure below ✅ | ||
```html | ||
<form> | ||
<label for="title"> | ||
Please enter your video title below: | ||
<input type="text" name="title" id="title" placeholder="Enter video title here" required/> | ||
</label> | ||
<label for="url"> | ||
Please enter your video url below: | ||
<input type="url" name="url" id="url" placeholder="Enter video url here"/> | ||
</label> | ||
<button type="submit">Submit</button> | ||
</form> | ||
``` | ||
notes: Check implementation above is correct | ||
|
||
|
||
|
||
5. Your website must follow accessibility guidelines (see below for more details) | ||
|
||
========================================================== | ||
|
||
App.jsx | ||
|
||
State variable to hold videoList (initial value will be the data we get from exampleresponse.json) | ||
|
||
VideoList.jsx -> props will be [videoList={videoList}] | ||
VideoCard.jsx -> props will be [id={id}, title={title}, url={url}, rating={rating}] | ||
VideoForm.jsx -> props will be [videoList={videoList} setVideoList={setVideoList}] | ||
|
||
State variable to hold information that will be updating the videoList state variable declared in App.jsx | ||
|
||
|
||
|
||
## Youtube URL | ||
embedded version: | ||
https://www.youtube.com/embed/lJIrF4YjHfQ | ||
watch version: | ||
https://www.youtube.com/watch?v=lJIrF4YjHfQ | ||
|
||
|
||
## Youtube URL from data | ||
embedded version: | ||
https://www.youtube.com/embed/dQw4w9WgXcQ | ||
watch version: | ||
https://www.youtube.com/watch?v=dQw4w9WgXcQ | ||
|
||
|
||
Helpful related articles: | ||
|
||
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace | ||
|
||
|
||
|
||
=========================================== | ||
|
||
Make reusable button | ||
|
||
We will always need to hand down the following: | ||
|
||
- content (can be a text or icon; this goes between the button tags) | ||
- type attribute (eg. submit or delete) | ||
- onClickHandler | ||
- | ||
|
||
============================================= | ||
|
||
Validate URL Workings: | ||
|
||
function validateUrl(urlObject) { | ||
console.log(urlObject); | ||
if (urlObject.protocol !== "https:") { | ||
throw new Error("Protocol must be https:"); | ||
} | ||
return true; | ||
// return either validated URL or an error | ||
} | ||
// const unValidatedTitle = event.target.form.title.value; | ||
const unValidatedUrl = new URL(event.target.form.url.value); | ||
// const isValidated = validateUrl(unValidatedUrl); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
// prettier.config.js | ||
module.exports = { | ||
plugins: ["prettier-plugin-tailwindcss"], | ||
}; |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,17 @@ | ||
import "./App.css"; | ||
import React from "react"; | ||
import MainContainer from "./components/MainContainer"; | ||
import youtubeLogo from "./components/images/youtubeLogo.png"; | ||
|
||
function App() { | ||
const App = () => { | ||
return ( | ||
<div className="App"> | ||
<header className="App-header"> | ||
<h1>Video Recommendation</h1> | ||
</header> | ||
<div className="App text-left"> | ||
<div className="font-semi-bold flex flex-row justify-center pt-8 text-5xl"> | ||
<img src={youtubeLogo} alt="Logo" className="h-13 mx-7 w-20" /> | ||
<h1>Video Recommendations</h1> | ||
</div> | ||
<MainContainer /> | ||
</div> | ||
); | ||
} | ||
}; | ||
|
||
export default App; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import React from "react"; | ||
|
||
function Button({ onDelete, id }) { | ||
return ( | ||
<button | ||
onClick={() => onDelete(id)} | ||
className="rounded bg-gray-200 p-2 text-lg font-bold" | ||
> | ||
Delete ❌ | ||
</button> | ||
); | ||
} | ||
|
||
export default Button; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import React from "react"; | ||
import VideoCard from "./VideoCard"; | ||
|
||
function CardsContainer({ videoData, setVideoData, setFetchData }) { | ||
async function handleDelete(id) { | ||
fetch(`http://localhost:5000/videos/${id}`, { | ||
method: "delete", | ||
}) | ||
.then((response) => response.json()) | ||
.then((result) => console.log(result)) | ||
.catch((error) => console.log("error", error)); | ||
setFetchData(true); | ||
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. What is 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. I have removed setFetchData and added a new useState variable called "setVideoAdded" and set it as false and true when adding new videos data. |
||
} | ||
|
||
return ( | ||
<div className="grid justify-center gap-9"> | ||
{videoData | ||
?.sort((a, b) => b.rating - a.rating) | ||
.map((singleVideo) => { | ||
return ( | ||
<VideoCard | ||
key={singleVideo.id} | ||
videoData={videoData} | ||
singleVideo={singleVideo} | ||
onDelete={handleDelete} | ||
setVideoData={setVideoData} | ||
/> | ||
); | ||
})} | ||
</div> | ||
); | ||
} | ||
|
||
export default CardsContainer; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import React, { useEffect, useState } from "react"; | ||
import CardsContainer from "./CardsContainer"; | ||
import VideoForm from "./VideoForm"; | ||
|
||
function MainContainer() { | ||
const [videoData, setVideoData] = useState(); | ||
const [fetchData, setFetchData] = useState(true); | ||
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. When is this state ever |
||
|
||
useEffect(() => { | ||
if (fetchData) { | ||
fetch("http://localhost:5000/videos") | ||
.then((response) => response.json()) | ||
.then((data) => setVideoData(data)); | ||
} | ||
setFetchData(false); | ||
}, [fetchData, videoData]); | ||
|
||
return ( | ||
<div> | ||
<VideoForm | ||
setFetchData={setFetchData} | ||
videoData={videoData} | ||
setVideoData={setVideoData} | ||
/> | ||
<CardsContainer | ||
setFetchData={setFetchData} | ||
videoData={videoData} | ||
setVideoData={setVideoData} | ||
/> | ||
</div> | ||
); | ||
} | ||
|
||
export default MainContainer; |
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.
A couple of things to think about with this component:
onDelete
is a slightly strange name - "on" as a prefix normally means this function is meant to be called when something has happened (e.g. onClick is for when something has been clicked), but this actually does the delete, rather than handles it. Can you think of a name which suggests more this does the delete, rather than is called as a result of the delete?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.
Thank you @illicitonion for pointing these out and paying attention to details! Having the right names is super helpful for me to understand my own code when I look at them.
After going through your review, I made changes to my level 300 as my level 300 is built on top of my level 250.