-
-
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
Glasgow 6 | Haroun Alarabi | Full-Stack-Project-Assessment | Level 300 #393
Changes from 15 commits
69edd6a
1bf955f
8e17e0a
c6a5a26
31714ca
11db4a6
f430199
5a65a94
f1719dd
9b6119d
c9b4d77
2a5c39b
e03cf78
693fca6
617033c
bf67c03
47555fc
e9264f8
4c0da5a
9f9b409
1520c40
f2cda8e
aef98ae
320d332
b57ec28
7430cc7
71e8c2d
6f3ee8d
19830d9
30815d8
115139e
e19b396
901b741
e71d384
c92bab6
35cd4f4
a5f68f3
23bad34
16f6fd4
477fbe4
804270b
afd5a23
13b37d3
6671127
5e62bfd
7300e2c
f12b030
43fe4f2
a906534
b306cfc
0eedb67
579aa03
a1e1880
8732f6e
70a7999
261e797
8b5ce3c
92858f6
32dcfc6
f0ff110
b83eb83
76fa5e8
4dcd2c2
ce1b92c
fb135aa
95fc825
ab85fab
3fd740f
5ad87f3
d690b8a
43d9965
d650d30
a6f048b
794f1bb
a4a53f2
e6b4807
c0f1f20
67795d1
578418f
e60997a
893e0f2
6bbea86
24a4ce8
f23cf54
1ea8d8e
e3ebf78
72307fb
8127274
23587e9
ba66db8
3b1894c
fd789d8
5d1268a
dda8446
94e0bd6
8056f8f
bf916fa
4e5f556
3f53887
b731eae
6508f1e
838535c
8ad4d17
16c0651
2e99ba6
617bdf5
d8f9024
6f128f4
f2607a3
930139f
5ee7015
f750ce6
f2cfe23
6c7a8b7
e203619
ea626fb
6aa2965
6eebe36
40ec1c9
6de5644
c1c4962
aab3a4f
48e3a88
c7b1afc
8205303
283d8cc
ba46007
1f540b3
54fb352
1c5bd0f
45b4590
d2297a6
add2bb1
3cd0fa9
11347fc
8df7ad8
abca632
41ad17d
25f60e4
7bfd385
a869b0d
41a2720
533abea
57546ff
0dfdb0d
0f21bb4
f5e4b79
595712c
e25ba7d
49617c4
11a7fb5
8daa84c
0547896
94b8243
ee88fa6
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,3 @@ | ||
{ | ||
"git.ignoreLimitWarning": true | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
node_modules/ |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import React, { useState } from "react"; | ||
import "../index.css"; | ||
|
||
const AddVideo = ({ onVidAdd }) => { | ||
const [title, setTitle] = useState(""); | ||
const [url, setUrl] = useState(""); | ||
|
||
const titleChange = (event) => { | ||
setTitle(event.target.value); | ||
}; | ||
|
||
const urlChange = (event) => { | ||
setUrl(event.target.value); | ||
}; | ||
|
||
const newVid = () => { | ||
if (title !== "" && url !== "") { | ||
let embedUrl = url.replace("https://youtu.be/", "https://www.youtube.com/embed/"); | ||
console.log("Original URL:", url); | ||
console.log("Embed URL:", embedUrl); | ||
|
||
const newInput = { | ||
title: title, | ||
url: embedUrl, | ||
rating: 0, | ||
}; | ||
|
||
onVidAdd(newInput); | ||
|
||
setTitle(""); | ||
setUrl(""); | ||
} else { | ||
alert("Please fill in all fields"); | ||
} | ||
}; | ||
|
||
return ( | ||
<form> | ||
<div className="form-group row"> | ||
<label htmlFor="colFormLabelSm" className="col-sm-2 col-form-label col-form-label-sm"> | ||
Title | ||
</label> | ||
<div className="col-sm-10"> | ||
<input | ||
type="text" | ||
value={title} | ||
onChange={titleChange} | ||
className="form-control form-control-sm" | ||
id="colFormLabelSm" | ||
placeholder="Title" | ||
/> | ||
</div> | ||
</div> | ||
<div className="form-group row"> | ||
<label htmlFor="colFormLabelSm" className="col-sm-2 col-form-label col-form-label-sm"> | ||
URL | ||
</label> | ||
<div className="col-sm-10"> | ||
<input | ||
type="text" | ||
value={url} | ||
onChange={urlChange} | ||
className="form-control form-control-sm" | ||
id="colFormLabelSm" | ||
placeholder="URL" | ||
/> | ||
</div> | ||
</div> | ||
<button onClick={newVid} type="button" className="btn btn-success"> | ||
Add | ||
</button> | ||
<button type="button" className="btn btn-warning"> | ||
Cancel | ||
</button> | ||
</form> | ||
); | ||
}; | ||
|
||
export default AddVideo; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { useState } from "react"; | ||
|
||
function Search({ onSearch }) { | ||
const [search, setSearch] = useState(); | ||
const handleSearch = () => { | ||
if (search) { | ||
onSearch(search); | ||
} else { | ||
alert("please enter a search term"); | ||
} | ||
}; | ||
|
||
return ( | ||
<div> | ||
<input onChange={(e) => setSearch(e.target.value)}></input> | ||
<button onClick={handleSearch}>Search</button> | ||
</div> | ||
); | ||
} | ||
|
||
export default Search; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { useState } from "react"; | ||
|
||
function Video({ videoObj, deleteVideo }) { | ||
const videoId = getVideoIdFromUrl(videoObj.url); | ||
const [vote, setVote] = useState(videoObj.rating); | ||
|
||
|
||
|
||
const updateVote = (increment) => { | ||
const newRating = videoObj.rating + increment; | ||
|
||
fetch(`https://node-server-full-stack.onrender.com/videos/${videoObj.id}`, { | ||
method: "PUT", | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
body: JSON.stringify({ newRating }), | ||
}) | ||
.then((res) => { | ||
if (!res.ok) throw new Error(res.statusText); | ||
return res.json(); | ||
}) | ||
.then(() => { | ||
if (increment === 1) { | ||
setVote(vote + 1); | ||
} else if (increment === -1 && vote > 0) { | ||
setVote(vote - 1); | ||
} | ||
}) | ||
.catch((error) => console.error("Error updating vote:", error)); | ||
}; | ||
|
||
|
||
|
||
|
||
const date = new Date().toDateString(); | ||
|
||
return ( | ||
<div className="card"> | ||
<iframe | ||
src={`https://www.youtube.com/embed/${videoId}`} | ||
frameBorder="0" | ||
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" | ||
title="YouTube video player" | ||
allowFullScreen | ||
height="300px" | ||
></iframe> | ||
<div className="card-body"> | ||
<h5 className="card-title">{videoObj.title}</h5> | ||
<p className="card-text">Requested At: {date}</p> | ||
<div className="buttons"> | ||
<i className="fa-solid fa-thumbs-up" onClick={()=>updateVote(1)}></i> | ||
<p>{vote}</p> | ||
<i className="fa-solid fa-thumbs-down" onClick={()=>updateVote(-1)}></i> | ||
<button onClick={() => deleteVideo(videoObj.id)} className="btn btn-danger"> | ||
<i className="fa fa-trash-o"></i> | ||
</button> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
export default Video; | ||
|
||
function getVideoIdFromUrl(url) { | ||
if (url && typeof url === "string" && url.includes("v=")) { | ||
return url.split("v=")[1]; | ||
} | ||
return null; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import React, { useEffect, useState } from "react"; | ||
import Video from "./Video"; | ||
import AddVideo from "./AddVideo"; | ||
import "../index.css"; | ||
import Search from "./Search"; | ||
|
||
function VideosList() { | ||
const [videos, setVideos] = useState([]); | ||
const [loading, setLoading] = useState(true); | ||
const [searchResult, setSearchResult] = useState([]); | ||
|
||
useEffect(() => { | ||
fetch("https://node-server-full-stack.onrender.com/videos") | ||
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. Use an environment variable for this API url - don't just hardcode the string. Do you understand why this is important? 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 chaned this |
||
.then((res) => res.json()) | ||
.then((data) => { | ||
console.log(data); | ||
setVideos(data); | ||
setLoading(false); | ||
}) | ||
.catch((error) => { | ||
console.error("Error fetching data:", error); | ||
}); | ||
}, []); | ||
|
||
const newVideo = (newInput) => { | ||
newInput.id = videos.length + 1; | ||
newInput.rating = 0; | ||
fetch("https://node-server-full-stack.onrender.com/videos", { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/JSON", | ||
}, | ||
body: JSON.stringify(newInput), | ||
}) | ||
.then((res) => { | ||
fetchVideos(); | ||
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. Call to 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. fetchVideos to update the list of videos after adding new video |
||
return res.json(); | ||
}) | ||
.then((data) => { | ||
if (data && data.message === "Video stored successfully") { | ||
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. Now you're calling |
||
setVideos([...videos, newInput]); | ||
} | ||
}) | ||
.catch((error) => { | ||
console.error("Error:", error); | ||
alert("Something went wrong"); | ||
}); | ||
}; | ||
|
||
const deleteVideoItem = (videoId) => { | ||
fetch(`https://node-server-full-stack.onrender.com/videos/${videoId}`, { | ||
method: "DELETE", | ||
headers: { | ||
"Content-Type": "application/JSON", | ||
}, | ||
}).then((res) => { | ||
if (!res.ok) { | ||
throw new Error('Network response was not ok'); | ||
} | ||
const videoDeleted = videos.filter((el) => el.id !== videoId); | ||
setVideos(videoDeleted); | ||
}) | ||
}; | ||
|
||
const fetchVideos = () => { | ||
fetch("https://node-server-full-stack.onrender.com/videos") | ||
.then((res) => res.json()) | ||
.then((data) => { | ||
setVideos(data); | ||
searchResult(data); | ||
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. Here you're fetching the data and then you
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. the issue here is I used searchResult , so I have to change in to setSearchResult() |
||
}) | ||
.catch((error) => { | ||
console.error("Error fetching data:", error); | ||
}); | ||
}; | ||
const searchByName = (search) => { | ||
fetch(`https://node-server-full-stack.onrender.com/videos/${search}`, { | ||
method: "GET", | ||
headers: { "content-type": "application/JSON" }, | ||
}) | ||
.then((res) => res.json()) | ||
.then((data) => { | ||
setSearchResult(data); | ||
console.log(data) | ||
}); | ||
}; | ||
const sortedVideos = [...videos].sort((a, b) => b.rating - a.rating); | ||
return ( | ||
<> | ||
<Search onSearch={searchByName} /> | ||
<AddVideo onVidAdd={newVideo} /> | ||
<div className="container mt-5"> | ||
{searchResult.length > 0 ? ( | ||
searchResult.map((video) => <Video videoObj={video} key={video.id} deleteVideo={deleteVideoItem} />) | ||
) : loading ? ( | ||
<p>Loading...</p> | ||
) : ( | ||
sortedVideos.map((video) => <Video videoObj={video} key={video.id} deleteVideo={deleteVideoItem} />) | ||
)} | ||
</div> | ||
</> | ||
); | ||
} | ||
|
||
export default VideosList; |
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.
Unclear to me what this function does just by reading its name
onVidAdd