diff --git a/frontend/js/actions/notifications.js b/frontend/js/actions/notifications.js new file mode 100644 index 0000000..6e060ae --- /dev/null +++ b/frontend/js/actions/notifications.js @@ -0,0 +1,9 @@ +export const sendNotification = (data) => ({ + type: "ADD_NOTIFICATION", + payload: data, +}) + +export const removeNotification = (id) => ({ + type: "REMOVE_NOTIFICATION", + payload: { id }, +}) diff --git a/frontend/js/app.js b/frontend/js/app.js index 9bae870..497f881 100644 --- a/frontend/js/app.js +++ b/frontend/js/app.js @@ -26,6 +26,7 @@ import store, { history } from "./store" // Components import { AdminPanel } from "./components/admins/panel" +import { Notifications } from "./components/notifications/notifications" import Alert from "./components/alerts/alert" import MovieList from "./components/movies/list" import MoviesRoute from "./components/movies/route" @@ -48,6 +49,7 @@ const App = () => ( + diff --git a/frontend/js/components/notifications/notification.js b/frontend/js/components/notifications/notification.js new file mode 100644 index 0000000..dc82fb9 --- /dev/null +++ b/frontend/js/components/notifications/notification.js @@ -0,0 +1,70 @@ +import React, { useState } from "react" +import PropTypes from "prop-types" +import { connect } from "react-redux" + +import { Toast } from "react-bootstrap" + +import { removeNotification } from "../../actions/notifications" + +const NotificationConnected = ({ + id, + icon, + title, + message, + imageUrl, + autohide, + delay, + removeNotification, +}) => { + const [show, setShow] = useState(true) + + const hide = () => { + setShow(false); + setTimeout(() => removeNotification(id), 200); + } + + return ( + + + {icon !== "" && + + } + {title} + + + {message !== "" && + {message} + } + {imageUrl !== "" && + + } + + + ) +} +NotificationConnected.propTypes = { + id: PropTypes.string, + icon: PropTypes.string, + title: PropTypes.string, + message: PropTypes.string, + imageUrl: PropTypes.string, + autohide: PropTypes.bool, + delay: PropTypes.number, + removeNotification: PropTypes.func, +}; + +NotificationConnected.defaultProps = { + autohide: false, + delay: 5000, + icon: "", + imageUrl: "", + title: "Info", + message: "", +}; + +export const Notification = connect(null, {removeNotification})(NotificationConnected); diff --git a/frontend/js/components/notifications/notifications.js b/frontend/js/components/notifications/notifications.js new file mode 100644 index 0000000..179a24a --- /dev/null +++ b/frontend/js/components/notifications/notifications.js @@ -0,0 +1,34 @@ +import React from "react" +import PropTypes from "prop-types" +import { List } from "immutable" +import { connect } from "react-redux" + +import { Notification } from "./notification" + +const NotificationsConnected = ({ notifications }) => { + return ( +
+ {notifications.map((el) => ( + + ))} +
+ ) +} +NotificationsConnected.propTypes = { + notifications: PropTypes.instanceOf(List), +} + +const mapStateToProps = (state) => ({ + notifications: state.notifications, +}); + +export const Notifications = connect(mapStateToProps)(NotificationsConnected); diff --git a/frontend/js/reducers/index.js b/frontend/js/reducers/index.js index 8e86326..884b3c5 100644 --- a/frontend/js/reducers/index.js +++ b/frontend/js/reducers/index.js @@ -8,6 +8,7 @@ import alerts from "./alerts" import torrentStore from "./torrents" import adminStore from "./admins" import polochon from "./polochon" +import notifications from "./notifications" export default combineReducers({ movieStore, @@ -18,4 +19,5 @@ export default combineReducers({ torrentStore, adminStore, polochon, + notifications, }); diff --git a/frontend/js/reducers/notifications.js b/frontend/js/reducers/notifications.js new file mode 100644 index 0000000..64c0332 --- /dev/null +++ b/frontend/js/reducers/notifications.js @@ -0,0 +1,15 @@ +import { List, fromJS } from "immutable" + +const defaultState = List(); + +const handlers = { + "ADD_NOTIFICATION": (state, action) => state.push(fromJS({ + id: Math.random().toString(36).substring(7), + ...action.payload + })), + "REMOVE_NOTIFICATION": (state, action) => + state.filter((e) => (e.get("id") !== action.payload.id)), +} + +export default (state = defaultState, action) => + handlers[action.type] ? handlers[action.type](state, action) : state; diff --git a/frontend/scss/app.scss b/frontend/scss/app.scss index fb2a248..167775e 100644 --- a/frontend/scss/app.scss +++ b/frontend/scss/app.scss @@ -77,7 +77,7 @@ div.show.dropdown.nav-item > div { .list-details { position: sticky; top: $body-padding-top; - z-index: 1000; + z-index: $zindex-sticky; height: calc(100vh - #{$body-padding-top}); } @@ -130,3 +130,25 @@ div.sweet-alert > h2 { .wishlist-button:hover > i { color: $primary; } + +.notifications { + position: fixed; + top: $body-padding-top; + z-index: $zindex-fixed; + max-height: calc(100vh - #{$body-padding-top}); + right: 1rem; + width: 18rem; +} + +.toast-header { + background-color: $card-cap-bg; + color: $gray-100; + + > button > span { + color: $white; + } +} + +.toast { + background-color: $card-bg; +}