diff --git a/src/App.css b/src/App.css
index a64d7ea..89c2d86 100755
--- a/src/App.css
+++ b/src/App.css
@@ -1,4 +1,6 @@
.App {
+ display: flex;
+ flex-direction: column;
text-align: center;
}
@@ -49,6 +51,14 @@ img {
border: 1px solid #ccc;
}
+.favorite{
+ cursor: pointer;
+ margin: 0.5rem auto;
+}
+.favorite:hover {
+ fill: rgb(250, 218, 148);
+}
+
@keyframes spin {
0% {
transform: rotate(0deg);
@@ -65,4 +75,85 @@ img {
to {
transform: rotate(360deg);
}
+
+
+}
+
+.options-nav {
+ display: flex;
+}
+
+/* Favorites list section */
+.favorites-list-section {
+ /* padding: 1rem; */
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+}
+
+.favorite-item {
+ background-color: rgb(242, 242, 242);
+ border-radius: 2px;
+ margin: 1rem;
+ padding:1rem;
+ display: flex;
+ justify-content: space-between;
}
+
+.favorite-item .action-container {
+ cursor: pointer;
+ display: flex;
+ justify-content: space-between;
+ /* height: 200px; */
+}
+
+.favorite-item .action-container > * {
+ margin: 5px;
+}
+
+.favorite-item .img-container {
+ flex-grow: 0;
+ flex-shrink: 0;
+ flex-basis: 150px;
+ height: 150px;
+}
+
+.favorite-item .img-container > img {
+ max-height: 100%;
+ max-width: 100%;
+ object-position: center;
+ object-fit: cover;
+}
+
+.favorite-item .action-container .informations {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: flex-start;
+}
+
+.favorite-item .informations > .title {
+ font-weight: bold;
+ font-size: 21px;
+}
+
+.favorite-item .informations > .description {
+ text-overflow: ellipsis;
+ overflow: hidden;
+ /* CSS properties for ellipsis overflow after 5 lines of text */
+ display: block;
+ display: -webkit-box;
+ -webkit-line-clamp: 5;
+ -webkit-box-orient: vertical;
+ max-height: 5 * 1.6rem;
+}
+
+@media screen and (max-width: 600px) {
+ .favorite-item {
+ flex-direction: column;
+ }
+
+ .favorite-item .action-container {
+ flex-direction: column;
+ }
+}
\ No newline at end of file
diff --git a/src/App.js b/src/App.js
index cdfec73..0cf425f 100755
--- a/src/App.js
+++ b/src/App.js
@@ -1,4 +1,4 @@
-import React, { useState } from "react";
+import React, { useEffect, useState } from "react";
import {
Button,
Dialog,
@@ -12,6 +12,9 @@ import "./App.css";
import EpisodeList from "./components/EpisodeList";
import UserForm from "./components/UserForm";
import LoadingStatus from "./components/LoadingStatus";
+import FavoriteDialog from "./components/FavoriteDialog";
+import { useRef } from "react";
+import SearchHistory from "./components/SearchHistory";
const App = ({ fetching }) => {
const [fetched, setFetched] = useState({});
@@ -19,12 +22,15 @@ const App = ({ fetching }) => {
const [previousFeeds, setPreviousFeeds] = useState([]);
const [past, setPast] = useState(false);
const [error, setError] = useState(false);
+ const [favoriteFeeds, setFavoriteFeeds] = useState([]);
+
+ const favoritesPopUpRef = useRef();
const getFeed = (event) => {
setFetching((prev) => !prev);
if (event.preventDefault != null)
event.preventDefault();
- const feed_url = event.target.elements.feed_url.value;
+ const feed_url = formatUrl(event.target.elements.feed_url.value);
const Parser = require("rss-parser");
const parser = new Parser({
customFields: {
@@ -43,6 +49,7 @@ const App = ({ fetching }) => {
program_title: feed.title,
program_image: feed.image.url,
program_description: feed.description,
+ program_link: feed_url,
});
setFetching((prev) => !prev);
setPreviousFeeds([...new Set([...previousFeeds, feed_url])]);
@@ -63,6 +70,20 @@ const App = ({ fetching }) => {
}
};
+ // Formatting method for URLs to avoid having duplicate favorites in localstorage as the key used is the URL
+ const formatUrl = (url) => {
+ let urlSequence;
+ let finalUrl;
+ if (url.includes('://')) {
+ urlSequence = url.split('://')[1];
+ finalUrl = 'https://' + urlSequence;
+ } else {
+ finalUrl = 'https://' + url;
+ }
+
+ return finalUrl;
+ };
+
const handleClose = () => {
setFetching(false);
setError(false);
@@ -90,28 +111,53 @@ const App = ({ fetching }) => {
);
+ const isFavoriteSelected = (link) => {
+ return favoriteFeeds.some(el => el.program_link === link);
+ }
+
+ const updateFavoritesFeeds = () => {
+ let favorites = [];
+ for (let [key, value] of Object.entries(localStorage)) {
+ if (key.startsWith('favorite-')) {
+ favorites.push(JSON.parse(value));
+ }
+ }
+ setFavoriteFeeds(favorites);
+ }
+
+ useEffect(() => {
+ updateFavoritesFeeds();
+ }, []);
+
return (
setFetching(true)}
- past={past}
- previous_feeds={[...previousFeeds]}
- />
+ getFeed={getFeed} />
+
+ {past ? <>
+
+
+ > : Please enter an RSS feed
}
+
{error ? renderAlert() : }
- {!past ? Please enter an RSS feed
: }
-
+ {/* Favorite feeds list dialog component */}
+
);
};
diff --git a/src/components/Episode.jsx b/src/components/Episode.jsx
index aa79690..32bb374 100644
--- a/src/components/Episode.jsx
+++ b/src/components/Episode.jsx
@@ -32,27 +32,4 @@ const Episode = ({ link, title }) => {
);
};
-// class Episode extends Component {
-// divStyles = {
-// width: "77vw",
-// float: "right",
-// marginRight: "1vw",
-// };
-// render() {
-// return (
-//
-// );
-// }
-// }
-
export default Episode;
diff --git a/src/components/EpisodeList.jsx b/src/components/EpisodeList.jsx
index 280da89..3638930 100644
--- a/src/components/EpisodeList.jsx
+++ b/src/components/EpisodeList.jsx
@@ -1,63 +1,79 @@
-import React, { Component } from "react";
+import React from "react";
+import FavoriteButton from "./FavoriteButton";
import Episode from "./Episode";
+import { useState } from "react";
-class EpisodeList extends Component {
- cardStyle = {
- width: "20vw",
- float: "left",
- };
- render() {
- const {
+const EpisodeList = ({
+ program_title,
+ program_description,
+ program_image,
+ episodes,
+ program_link,
+ updateFavorites,
+ isFavoriteSelected
+ }) => {
+
+ // eslint-disable-next-line no-unused-vars
+ const [cardStyle, setCardStyle] = useState({width: "20vw", float: "left"});
+
+ const toggleFavorite = () => {
+ const feed_data = {
program_title,
program_description,
program_image,
- episodes,
- } = this.props;
+ program_link,
+ };
+
+ isFavoriteSelected(program_link) ? localStorage.removeItem(`favorite-${program_link}`) : localStorage.setItem(`favorite-${program_link}`, JSON.stringify(feed_data));
+ updateFavorites();
+ }
- return (
-
- {episodes ? (
-
-
-
-
![{program_title}]({program_image})
+ {episodes ? (
+
+
+
+
![{program_title}]({program_image})
+
- {episodes.map((episode, i) => (
-
- ))}
- ) : (
-
- )}
-
- );
- }
+ {episodes.map((episode, i) => (
+
+ ))}
+
+ ) : (
+
+ )}
+
+ );
}
export default EpisodeList;
diff --git a/src/components/FavoriteButton.jsx b/src/components/FavoriteButton.jsx
new file mode 100644
index 0000000..76a4574
--- /dev/null
+++ b/src/components/FavoriteButton.jsx
@@ -0,0 +1,43 @@
+import React from "react";
+import SvgIcon from '@material-ui/core/SvgIcon';
+import amber from '@material-ui/core/colors/amber';
+import grey from '@material-ui/core/colors/grey';
+import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
+
+const FavoriteButton = ({selected, onClickAction}) => {
+
+ const theme = createMuiTheme({
+ palette: {
+ primary: {
+ light: amber[100],
+ main: amber[500],
+ dark: amber[800],
+ contrastText: '#fff',
+ },
+ secondary: {
+ light: grey[100],
+ main: grey[400],
+ dark: grey[800],
+ contrastText: '#000',
+ }
+ },
+ });
+
+ const StarIcon = (props) => {
+ return (
+
+
+
+ );
+ };
+
+ return (
+
+
+
+
+
+ );
+};
+
+export default FavoriteButton;
\ No newline at end of file
diff --git a/src/components/FavoriteDialog.jsx b/src/components/FavoriteDialog.jsx
new file mode 100644
index 0000000..80f20cb
--- /dev/null
+++ b/src/components/FavoriteDialog.jsx
@@ -0,0 +1,68 @@
+import React, { useState } from 'react';
+import Button from '@material-ui/core/Button';
+import Dialog from '@material-ui/core/Dialog';
+import DialogActions from '@material-ui/core/DialogActions';
+import DialogContent from '@material-ui/core/DialogContent';
+import DialogTitle from '@material-ui/core/DialogTitle';
+import "../App.css";
+import FavoriteItem from './FavoriteItem';
+const { forwardRef, useImperativeHandle } = React;
+
+const FavoriteDialog = forwardRef((props, ref) => {
+
+ const [open, setOpen] = useState(false);
+
+ const handleClose = () => {
+ setOpen(false);
+ }
+ const handleClickOpen = () => {
+ setOpen(true);
+ }
+
+ useImperativeHandle(ref, () => { return {
+ handleClickOpen: handleClickOpen,
+ handleClose: handleClose
+ }});
+
+ return (
+
+
+
+ );
+});
+
+
+export default FavoriteDialog;
\ No newline at end of file
diff --git a/src/components/FavoriteItem.jsx b/src/components/FavoriteItem.jsx
new file mode 100644
index 0000000..de8d1c0
--- /dev/null
+++ b/src/components/FavoriteItem.jsx
@@ -0,0 +1,37 @@
+import React from "react";
+import "../App.css";
+import FavoriteButton from "./FavoriteButton";
+
+const FavoriteItem = ({item, index, closePopUp, updateFavorites, isFavoriteSelected, getFeed}) => {
+
+ const removeFavorite = () => {
+ localStorage.removeItem(`favorite-${item.program_link}`);
+ updateFavorites();
+ }
+
+ const seeFavorite = (link) => {
+ getFeed({target: {elements: {feed_url: {value: link}}}});
+ closePopUp();
+ };
+
+ return (
+
+
seeFavorite(item.program_link)}>
+
+
![{item.program_title}]({item.program_image})
+
+
+
{item.program_title}
+
{item.program_description}
+
+
+
+
+ )
+}
+
+export default FavoriteItem;
\ No newline at end of file
diff --git a/src/components/SearchHistory.jsx b/src/components/SearchHistory.jsx
index 1856814..48afd04 100644
--- a/src/components/SearchHistory.jsx
+++ b/src/components/SearchHistory.jsx
@@ -9,7 +9,7 @@ export default function SearchHistory(props) {
};
const handleClose = (event) => {
- if (event.currentTarget.innerText != '')
+ if (event.currentTarget.innerText !== '')
props.getFeed({target: {elements: {feed_url: {value: event.currentTarget.innerText}}}});
setAnchorEl(null);
};
diff --git a/src/components/UserForm.jsx b/src/components/UserForm.jsx
index 79bdbba..f0f1972 100644
--- a/src/components/UserForm.jsx
+++ b/src/components/UserForm.jsx
@@ -1,9 +1,8 @@
import React, { useState } from "react";
import Input from "@material-ui/core/Input";
import Button from "@material-ui/core/Button";
-import SearchHistory from "./SearchHistory";
-const UserForm = ({ getFeed, previous_feeds, past }) => {
+const UserForm = ({ getFeed }) => {
const [enabled, setEnabled] = useState(true);
const [feeds, setFeeds] = useState([]);
@@ -39,7 +38,6 @@ const UserForm = ({ getFeed, previous_feeds, past }) => {
>
Submit
- {past ?
:
}
);