-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: history and playlist management
- Loading branch information
1 parent
685e9e8
commit 184fff8
Showing
20 changed files
with
845 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import React from "react"; | ||
import { useModal } from "../context/modalContext"; | ||
import { usePlaylist } from "../context/playlistContext"; | ||
import { ButtonGhost } from "../styles/Button"; | ||
import Wrapper from "../styles/PlaylistModal"; | ||
import { CloseIcon } from "./Icons"; | ||
|
||
function PlaylistModal({ video }) { | ||
const { playlistData } = usePlaylist(); | ||
const { | ||
toggleModal, | ||
modalData, | ||
handlePlaylistFormSubmit, | ||
handlePlaylistsCheckboxClick, | ||
} = useModal(); | ||
|
||
return ( | ||
<Wrapper showModal={modalData.showModal}> | ||
<div className={`create-playlist`}> | ||
<form onSubmit={handlePlaylistFormSubmit}> | ||
<div className="modal-header"> | ||
<h3> | ||
<span>Create New Playlist</span> | ||
<CloseIcon onClick={toggleModal} /> | ||
</h3> | ||
</div> | ||
|
||
<input | ||
type="text" | ||
placeholder="Enter playlist name" | ||
id="playlistname" | ||
autoComplete="off" | ||
required | ||
/> | ||
<ButtonGhost type="submit">Create</ButtonGhost> | ||
</form> | ||
|
||
{playlistData.length > 0 && ( | ||
<div className="playlists"> | ||
<h3> | ||
<span>Playlists</span> | ||
</h3> | ||
<ul> | ||
{playlistData.map((p) => ( | ||
<li key={p.name}> | ||
<label htmlFor={p.name}> | ||
<input | ||
id={p.name} | ||
value={p.name} | ||
type="checkbox" | ||
onChange={handlePlaylistsCheckboxClick} | ||
checked={modalData.selectedPlaylists.includes(p.name)} | ||
/> | ||
{p.name} | ||
</label> | ||
</li> | ||
))} | ||
</ul> | ||
</div> | ||
)} | ||
</div> | ||
</Wrapper> | ||
); | ||
} | ||
|
||
export default PlaylistModal; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import { createContext, useContext, useReducer } from "react"; | ||
import { useSnackbar } from "react-simple-snackbar"; | ||
import { | ||
sharedInitialReducerState, | ||
sharedReducer, | ||
} from "../reducer/sharedReducer"; | ||
|
||
const HistoryContext = createContext({ | ||
historyData: { ...sharedInitialReducerState }, | ||
dispatch: () => {}, | ||
addToHistory: () => {}, | ||
deleteHistory: () => {}, | ||
deleteFromHistory: () => {}, | ||
}); | ||
|
||
const HistoryProvider = ({ children }) => { | ||
const [state, dispatch] = useReducer( | ||
sharedReducer, | ||
sharedInitialReducerState | ||
); | ||
|
||
const [showSnackbar] = useSnackbar({ position: "bottom-right" }); | ||
|
||
const addToHistory = (video) => { | ||
const currentHistory = state.data.history; | ||
const videoInCurrentHistory = currentHistory.find( | ||
(data) => data.id === video.id | ||
); | ||
if (videoInCurrentHistory) return; | ||
const newStateData = { ...state.data, history: [...currentHistory, video] }; | ||
localStorage.setItem("maxVideoUserData", JSON.stringify(newStateData)); | ||
dispatch({ type: "ACTION_TYPE_SUCCESS", payload: newStateData }); | ||
}; | ||
|
||
const deleteHistory = () => { | ||
const newStateData = { ...state.data, history: [] }; | ||
localStorage.setItem("maxVideoUserData", JSON.stringify(newStateData)); | ||
dispatch({ type: "ACTION_TYPE_SUCCESS", payload: newStateData }); | ||
showSnackbar("History has been deleted"); | ||
}; | ||
|
||
const deleteFromHistory = (id) => { | ||
if (!id) return; | ||
const newStateData = { | ||
...state.data, | ||
history: state.data.history.filter((video) => video.id !== id), | ||
}; | ||
localStorage.setItem("maxVideoUserData", JSON.stringify(newStateData)); | ||
dispatch({ type: "ACTION_TYPE_SUCCESS", payload: newStateData }); | ||
showSnackbar("Video has been deleted from history"); | ||
}; | ||
|
||
const value = { | ||
historyData: state.data.history, | ||
dispatch, | ||
addToHistory, | ||
deleteHistory, | ||
deleteFromHistory, | ||
}; | ||
|
||
return ( | ||
<HistoryContext.Provider value={value}>{children}</HistoryContext.Provider> | ||
); | ||
}; | ||
|
||
const useHistory = () => useContext(HistoryContext); | ||
|
||
export { HistoryProvider, useHistory }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { createContext, useContext } from "react"; | ||
import { DeleteIcon, LibIcon, SubIcon } from "../components/Icons"; | ||
import { useHistory } from "./historyContext"; | ||
import { useModal } from "./modalContext"; | ||
|
||
const MenuItemsContext = createContext({ | ||
getMenuItems: () => {}, | ||
}); | ||
|
||
const MenuItemsProvider = ({ children }) => { | ||
const { deleteFromHistory } = useHistory(); | ||
const { toggleModal } = useModal(); | ||
|
||
const getMenuItems = (page) => { | ||
let menuItems = [ | ||
{ | ||
name: "Add to Playlist", | ||
icon: <SubIcon />, | ||
onClick: (_id, video) => toggleModal(video), | ||
}, | ||
{ | ||
name: "Save to watch later", | ||
icon: <LibIcon />, | ||
onClick: () => console.log("Save to watch later"), | ||
}, | ||
]; | ||
|
||
if (page === "history") { | ||
menuItems = [ | ||
...menuItems, | ||
{ | ||
name: "Remove from History", | ||
icon: <DeleteIcon />, | ||
onClick: (id) => deleteFromHistory(id), | ||
}, | ||
]; | ||
} | ||
|
||
return menuItems; | ||
}; | ||
|
||
const value = { | ||
getMenuItems, | ||
}; | ||
|
||
return ( | ||
<MenuItemsContext.Provider value={value}> | ||
{children} | ||
</MenuItemsContext.Provider> | ||
); | ||
}; | ||
|
||
const useMenuItems = () => useContext(MenuItemsContext); | ||
|
||
export { MenuItemsProvider, useMenuItems }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import { createContext, useContext, useReducer } from "react"; | ||
import { useSnackbar } from "react-simple-snackbar"; | ||
import PlaylistModal from "../components/PlaylistModal"; | ||
import { initialModalState, modalReducer } from "../reducer/modalReducer"; | ||
import { usePlaylist } from "./playlistContext"; | ||
|
||
const ModalContext = createContext({ | ||
modalData: { ...initialModalState }, | ||
dispatch: () => {}, | ||
toggleModal: () => {}, | ||
handlePlaylistFormSubmit: () => {}, | ||
handlePlaylistsCheckboxClick: () => {}, | ||
}); | ||
|
||
const ModalProvider = ({ children }) => { | ||
const [openSnackbar] = useSnackbar(); | ||
const { playlistData, createPlaylist, addToPlaylist, deleteFromPlaylist } = | ||
usePlaylist(); | ||
const [modal, dispatch] = useReducer(modalReducer, initialModalState); | ||
const toggleModal = (video) => { | ||
dispatch({ type: "TOGGLE_MODAL", payload: video }); | ||
|
||
const videoInPlaylists = playlistData.reduce( | ||
(acc, { name, videos }) => | ||
videos.some(({ id }) => id === video.id) ? [...acc, name] : acc, | ||
[] | ||
); | ||
|
||
dispatch({ | ||
type: "SET_SELECTED_PLAYLISTS", | ||
payload: videoInPlaylists, | ||
}); | ||
}; | ||
|
||
const handlePlaylistFormSubmit = (event) => { | ||
event.preventDefault(); | ||
const name = event.target.elements.playlistname.value; | ||
|
||
if (!name.trim()) { | ||
return openSnackbar("Please enter a playlist name"); | ||
} | ||
|
||
createPlaylist(name, modal.selectedVideo); | ||
|
||
event.target.elements.playlistname.value = ""; | ||
dispatch({ | ||
type: "SET_SELECTED_PLAYLISTS", | ||
payload: [...modal.selectedPlaylists, name], | ||
}); | ||
}; | ||
|
||
const handlePlaylistsCheckboxClick = (event) => { | ||
const { checked, value: name } = event.target; | ||
|
||
const newSelectedPlaylists = checked | ||
? [...modal.selectedPlaylists, name] | ||
: modal.selectedPlaylists.filter((playlistName) => playlistName !== name); | ||
|
||
dispatch({ | ||
type: "SET_SELECTED_PLAYLISTS", | ||
payload: newSelectedPlaylists, | ||
}); | ||
|
||
if (checked) { | ||
addToPlaylist(name, modal.selectedVideo); | ||
} else { | ||
deleteFromPlaylist(name, modal.selectedVideo); | ||
} | ||
}; | ||
|
||
const value = { | ||
modalData: modal, | ||
dispatch, | ||
toggleModal, | ||
handlePlaylistFormSubmit, | ||
handlePlaylistsCheckboxClick, | ||
}; | ||
|
||
return ( | ||
<ModalContext.Provider value={value}> | ||
{children} | ||
<PlaylistModal /> | ||
</ModalContext.Provider> | ||
); | ||
}; | ||
|
||
const useModal = () => useContext(ModalContext); | ||
|
||
export { ModalProvider, useModal }; |
Oops, something went wrong.