From 5516900b1b2b432cdce55c375361f3ee1182d7ad Mon Sep 17 00:00:00 2001 From: Hugo Pierret Date: Sun, 22 Sep 2024 21:55:57 +0200 Subject: [PATCH] Adding redux --- package-lock.json | 56 ++++++++++++++++++++++++++++++++ package.json | 3 ++ src/App.js | 35 +++++++++++++------- src/components/DownloadButton.js | 45 +++++++++++++++++++++++++ src/components/PosterGallery.js | 48 +++++++++++++++++++++++++-- src/services/action.js | 11 +++++++ src/services/store.js | 37 +++++++++++++++++++++ 7 files changed, 221 insertions(+), 14 deletions(-) create mode 100644 src/components/DownloadButton.js create mode 100644 src/services/action.js create mode 100644 src/services/store.js diff --git a/package-lock.json b/package-lock.json index 4372246..69a1e58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,8 +23,11 @@ "query-string": "^9.1.0", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-redux": "^9.1.2", "react-router-dom": "^6.26.1", "react-scripts": "5.0.1", + "redux": "^5.0.1", + "redux-persist": "^6.0.0", "web-vitals": "^2.1.4" } }, @@ -5556,6 +5559,12 @@ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "license": "MIT" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==", + "license": "MIT" + }, "node_modules/@types/ws": { "version": "8.5.12", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", @@ -17203,6 +17212,29 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "license": "MIT" }, + "node_modules/react-redux": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz", + "integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25", + "react": "^18.0", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -17393,6 +17425,21 @@ "node": ">=8" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-persist": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz", + "integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==", + "license": "MIT", + "peerDependencies": { + "redux": ">4.0.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", @@ -19749,6 +19796,15 @@ "requires-port": "^1.0.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 3247065..45b7bb1 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,11 @@ "query-string": "^9.1.0", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-redux": "^9.1.2", "react-router-dom": "^6.26.1", "react-scripts": "5.0.1", + "redux": "^5.0.1", + "redux-persist": "^6.0.0", "web-vitals": "^2.1.4" }, "scripts": { diff --git a/src/App.js b/src/App.js index 43e84cf..3c2575b 100644 --- a/src/App.js +++ b/src/App.js @@ -1,24 +1,35 @@ import React from "react"; import "./styles/App.css"; import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; +import { Provider } from "react-redux"; +import { PersistGate } from "redux-persist/integration/react"; +import { store, persistor } from "./services/store"; import PosterSelector from "./components/PosterSelector"; import PosterGallery from "./components/PosterGallery"; import UploadDiary from "./components/UploadDiary"; +import GlobalDownloadButton from "./components/DownloadButton"; function App() { return ( - -
- - } /> - } /> - } - /> - -
-
+ + + { + +
+ + } /> + } /> + } + /> + +
+
+ } + +
+
); } diff --git a/src/components/DownloadButton.js b/src/components/DownloadButton.js new file mode 100644 index 0000000..c95cd2a --- /dev/null +++ b/src/components/DownloadButton.js @@ -0,0 +1,45 @@ +import React from "react"; +import { useSelector } from "react-redux"; +import { Fab, makeStyles } from "@material-ui/core"; +import GetAppIcon from "@material-ui/icons/GetApp"; + +const useStyles = makeStyles((theme) => ({ + downloadFab: { + backgroundColor: "#1caff2", + position: "fixed", + bottom: theme.spacing(4), + left: "50%", + transform: "translateX(-50%)", + zIndex: 1000, + }, +})); + +const GlobalDownloadButton = () => { + const classes = useStyles(); + const selectedPosters = useSelector((state) => { + const selections = state.posterSelections; + return Object.values(selections).flat(); + }); + + const totalSelected = selectedPosters.length; + + if (totalSelected === 0) return null; + + const handleDownload = () => { + // Implement your download logic here + console.log("Downloading", totalSelected, "posters"); + }; + + return ( + + + Download ({totalSelected}) + + ); +}; + +export default GlobalDownloadButton; diff --git a/src/components/PosterGallery.js b/src/components/PosterGallery.js index 47aba4b..4b6c3ba 100644 --- a/src/components/PosterGallery.js +++ b/src/components/PosterGallery.js @@ -1,6 +1,8 @@ import React, { useState, useEffect } from "react"; import { useParams } from "react-router-dom"; import axios from "axios"; +import { useSelector, useDispatch } from "react-redux"; +import { selectPoster, deselectPoster } from "../services/action"; import { Container, Typography, @@ -12,6 +14,7 @@ import { makeStyles, Paper, } from "@material-ui/core"; +import CheckIcon from "@material-ui/icons/Check"; const useStyles = makeStyles((theme) => ({ root: { @@ -29,11 +32,30 @@ const useStyles = makeStyles((theme) => ({ fontWeight: "400", }, posterCard: { + position: "relative", height: "100%", display: "flex", flexDirection: "column", backgroundColor: "transparent", boxShadow: "none", + cursor: "pointer", + transition: "all 0.3s ease", + "&:hover": { + transform: "scale(1.05)", + }, + }, + selectedPoster: { + border: `4px solid ${theme.palette.primary.main}`, + borderRadius: theme.shape.borderRadius, + }, + checkIcon: { + position: "absolute", + top: theme.spacing(1), + right: theme.spacing(1), + backgroundColor: theme.palette.primary.main, + color: theme.palette.common.white, + borderRadius: "50%", + padding: theme.spacing(0.5), }, posterImage: { paddingTop: "150%", @@ -51,11 +73,15 @@ const useStyles = makeStyles((theme) => ({ }, })); -const PosterGallery = () => { +const PosterGallery = ({ movieId }) => { const classes = useStyles(); const [posters, setPosters] = useState([]); const [loading, setLoading] = useState(true); const { movieName, movieYear } = useParams(); + const dispatch = useDispatch(); + const selectedPosters = useSelector( + (state) => state.posterSelections[movieId] || [] + ); useEffect(() => { const fetchPosters = async () => { @@ -75,6 +101,14 @@ const PosterGallery = () => { fetchPosters(); }, [movieName, movieYear]); + const handlePosterSelect = (posterId) => { + if (selectedPosters.includes(posterId)) { + dispatch(deselectPoster(movieId, posterId)); + } else { + dispatch(selectPoster(movieId, posterId)); + } + }; + return ( @@ -89,12 +123,22 @@ const PosterGallery = () => { {posters.map((poster, index) => ( - + handlePosterSelect(poster.file_path)} + > + {selectedPosters.includes(poster.file_path) && ( + + )} ))} diff --git a/src/services/action.js b/src/services/action.js new file mode 100644 index 0000000..0a46933 --- /dev/null +++ b/src/services/action.js @@ -0,0 +1,11 @@ +export const selectPoster = (movieId, posterId) => ({ + type: "SELECT_POSTER", + movieId, + posterId, +}); + +export const deselectPoster = (movieId, posterId) => ({ + type: "DESELECT_POSTER", + movieId, + posterId, +}); diff --git a/src/services/store.js b/src/services/store.js new file mode 100644 index 0000000..5a6062e --- /dev/null +++ b/src/services/store.js @@ -0,0 +1,37 @@ +import { legacy_createStore as createStore, combineReducers } from "redux"; +import { persistStore, persistReducer } from "redux-persist"; +import storage from "redux-persist/lib/storage"; + +const posterSelectionReducer = (state = {}, action) => { + switch (action.type) { + case "SELECT_POSTER": + return { + ...state, + [action.movieId]: [...(state[action.movieId] || []), action.posterId], + }; + case "DESELECT_POSTER": + return { + ...state, + [action.movieId]: state[action.movieId].filter( + (id) => id !== action.posterId + ), + }; + default: + return state; + } +}; + +const rootReducer = combineReducers({ + posterSelections: posterSelectionReducer, + // other reducers... +}); + +const persistConfig = { + key: "root", + storage, +}; + +const persistedReducer = persistReducer(persistConfig, rootReducer); + +export const store = createStore(persistedReducer); +export const persistor = persistStore(store);