Adding Cart page
This commit is contained in:
@@ -7,7 +7,7 @@ import { store, persistor } from "./services/store";
|
|||||||
import PosterSelector from "./components/PosterSelector";
|
import PosterSelector from "./components/PosterSelector";
|
||||||
import PosterGallery from "./components/PosterGallery";
|
import PosterGallery from "./components/PosterGallery";
|
||||||
import UploadDiary from "./components/UploadDiary";
|
import UploadDiary from "./components/UploadDiary";
|
||||||
import GlobalDownloadButton from "./components/DownloadButton";
|
import Cart from "./components/Cart";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@@ -23,11 +23,11 @@ function App() {
|
|||||||
path="/posters/:movieName/:movieYear"
|
path="/posters/:movieName/:movieYear"
|
||||||
element={<PosterGallery />}
|
element={<PosterGallery />}
|
||||||
/>
|
/>
|
||||||
|
<Route path="/Cart" element={<Cart />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</Router>
|
||||||
}
|
}
|
||||||
<GlobalDownloadButton />
|
|
||||||
</PersistGate>
|
</PersistGate>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
239
src/components/Cart.js
Normal file
239
src/components/Cart.js
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
import {
|
||||||
|
Container, Typography, Card, CardMedia, CardContent,
|
||||||
|
Button, Checkbox, FormControlLabel, TextField, Dialog, DialogTitle, DialogContent, DialogActions,
|
||||||
|
Paper
|
||||||
|
} from '@material-ui/core';
|
||||||
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
import DeleteIcon from '@material-ui/icons/Delete';
|
||||||
|
import GetAppIcon from '@material-ui/icons/GetApp';
|
||||||
|
import ShareIcon from '@material-ui/icons/Share';
|
||||||
|
import EditIcon from '@material-ui/icons/Edit';
|
||||||
|
import { removePoster } from '../services/action';
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
root: {
|
||||||
|
marginTop: theme.spacing(4),
|
||||||
|
marginBottom: theme.spacing(8),
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
|
posterList: {
|
||||||
|
flex: 2,
|
||||||
|
marginRight: theme.spacing(2),
|
||||||
|
maxHeight: 'calc(100vh - 200px)',
|
||||||
|
overflowY: 'auto',
|
||||||
|
},
|
||||||
|
actionPanel: {
|
||||||
|
flex: 1,
|
||||||
|
position: 'sticky',
|
||||||
|
top: theme.spacing(4),
|
||||||
|
height: 'fit-content',
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
display: 'flex',
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
backgroundColor: '#2c3e50',
|
||||||
|
},
|
||||||
|
cardMedia: {
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
cardContent: {
|
||||||
|
flex: '1 0 auto',
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: theme.spacing(2),
|
||||||
|
},
|
||||||
|
slider: {
|
||||||
|
width: '100%',
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
typography: {
|
||||||
|
color: '#ecf0f1',
|
||||||
|
},
|
||||||
|
formControl: {
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
},
|
||||||
|
checkbox: {
|
||||||
|
color: '#3498db',
|
||||||
|
},
|
||||||
|
paper: {
|
||||||
|
padding: theme.spacing(3),
|
||||||
|
backgroundColor: '#34495e',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const Cart = () => {
|
||||||
|
const classes = useStyles();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const selectedPosters = useSelector((state) => {
|
||||||
|
const selections = state.posterSelections;
|
||||||
|
return Object.entries(selections).flatMap(([movieId, posters]) =>
|
||||||
|
posters.map(posterId => ({ movieId, posterId }))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const [selectedForDownload, setSelectedForDownload] = useState(selectedPosters.map(() => true));
|
||||||
|
const [downloadFormat, setDownloadFormat] = useState('zip');
|
||||||
|
const [renameDialogOpen, setRenameDialogOpen] = useState(false);
|
||||||
|
const [currentPosterIndex, setCurrentPosterIndex] = useState(null);
|
||||||
|
const [newPosterName, setNewPosterName] = useState('');
|
||||||
|
|
||||||
|
const handleRemovePoster = (index) => {
|
||||||
|
dispatch(removePoster(selectedPosters[index].movieId, selectedPosters[index].posterId));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDownload = async () => {
|
||||||
|
const postersToDownload = selectedPosters
|
||||||
|
.filter((_, index) => selectedForDownload[index])
|
||||||
|
.map((poster) => ({
|
||||||
|
movieId: poster.movieId,
|
||||||
|
posterId: poster.posterId,
|
||||||
|
movieName: poster.movieName,
|
||||||
|
movieYear: poster.movieYear
|
||||||
|
}));
|
||||||
|
|
||||||
|
console.log('Downloading posters:', postersToDownload);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post('http://localhost:5000/api/download-posters', {
|
||||||
|
posters: postersToDownload,
|
||||||
|
format: downloadFormat,
|
||||||
|
}, {
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
|
||||||
|
const blob = new Blob([response.data], { type: response.headers['content-type'] });
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = window.URL.createObjectURL(blob);
|
||||||
|
link.download = `posters.${downloadFormat}`;
|
||||||
|
link.click();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to download posters:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleShare = () => {
|
||||||
|
console.log('Sharing selected posters');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRename = (index) => {
|
||||||
|
setCurrentPosterIndex(index);
|
||||||
|
setNewPosterName(selectedPosters[index].posterId);
|
||||||
|
setRenameDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRenameConfirm = () => {
|
||||||
|
console.log(`Renaming poster ${currentPosterIndex} to ${newPosterName}`);
|
||||||
|
setRenameDialogOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className={classes.root}>
|
||||||
|
<div className={classes.posterList}>
|
||||||
|
<Typography variant="h4" gutterBottom className={classes.typography}>Your Cart</Typography>
|
||||||
|
{selectedPosters.map((poster, index) => (
|
||||||
|
<Card key={index} className={classes.card}>
|
||||||
|
<CardMedia
|
||||||
|
className={classes.cardMedia}
|
||||||
|
image={`https://image.tmdb.org/t/p/w500${poster.posterId}`}
|
||||||
|
title={`Poster ${index + 1}`}
|
||||||
|
/>
|
||||||
|
<CardContent className={classes.cardContent}>
|
||||||
|
<Typography variant="h6"
|
||||||
|
className={classes.typography}>{`Movie ID: ${poster.movieId}`}</Typography>
|
||||||
|
<Typography variant="body2"
|
||||||
|
className={classes.typography}>{poster.posterId}</Typography>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={selectedForDownload[index]}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newSelected = [...selectedForDownload];
|
||||||
|
newSelected[index] = e.target.checked;
|
||||||
|
setSelectedForDownload(newSelected);
|
||||||
|
}}
|
||||||
|
className={classes.checkbox}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Select for download"
|
||||||
|
className={classes.typography}
|
||||||
|
/>
|
||||||
|
<Button startIcon={<DeleteIcon/>} onClick={() => handleRemovePoster(index)}
|
||||||
|
className={classes.typography}>Remove</Button>
|
||||||
|
<Button startIcon={<EditIcon/>} onClick={() => handleRename(index)}
|
||||||
|
className={classes.typography}>Rename</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<Paper className={classes.actionPanel} elevation={3}>
|
||||||
|
<div className={classes.actions}>
|
||||||
|
<Typography gutterBottom>Download Format</Typography>
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
value={downloadFormat}
|
||||||
|
onChange={(e) => setDownloadFormat(e.target.value)}
|
||||||
|
SelectProps={{
|
||||||
|
native: true,
|
||||||
|
}}
|
||||||
|
className={classes.formControl}
|
||||||
|
>
|
||||||
|
<option value="zip">ZIP</option>
|
||||||
|
<option value="tar">TAR</option>
|
||||||
|
<option value="7z">7Z</option>
|
||||||
|
</TextField>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
startIcon={<GetAppIcon/>}
|
||||||
|
onClick={handleDownload}
|
||||||
|
disabled={selectedForDownload.every((selected) => !selected)}
|
||||||
|
className={classes.button}
|
||||||
|
>
|
||||||
|
Download Selected
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="secondary"
|
||||||
|
startIcon={<ShareIcon/>}
|
||||||
|
onClick={handleShare}
|
||||||
|
className={classes.button}
|
||||||
|
>
|
||||||
|
Share Selection
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Paper>
|
||||||
|
<Dialog open={renameDialogOpen} onClose={() => setRenameDialogOpen(false)}>
|
||||||
|
<DialogTitle>Rename Poster</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<TextField
|
||||||
|
autoFocus
|
||||||
|
margin="dense"
|
||||||
|
label="New Name"
|
||||||
|
type="text"
|
||||||
|
fullWidth
|
||||||
|
value={newPosterName}
|
||||||
|
onChange={(e) => setNewPosterName(e.target.value)}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setRenameDialogOpen(false)} color="primary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleRenameConfirm} color="primary">
|
||||||
|
Rename
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Cart;
|
||||||
78
src/components/NavBar.js
Normal file
78
src/components/NavBar.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { AppBar, Toolbar, IconButton, Badge, Button } from '@material-ui/core';
|
||||||
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
import ShoppingCartIcon from '@material-ui/icons/ShoppingCart';
|
||||||
|
import ArrowBackIcon from '@material-ui/icons/ArrowBack';
|
||||||
|
import { useNavigate, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
appBar: {
|
||||||
|
top: 'auto',
|
||||||
|
bottom: 0,
|
||||||
|
backgroundColor: '#1c1f23',
|
||||||
|
},
|
||||||
|
toolbar: {
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
|
backButton: {
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
cartButton: {
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
refreshButton: {
|
||||||
|
color: 'white',
|
||||||
|
marginRight: theme.spacing(2),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const NavBar = ({onRefreshFiles}) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
const selectedPosters = useSelector((state) => {
|
||||||
|
const selections = state.posterSelections;
|
||||||
|
return Object.values(selections).flat();
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalSelected = selectedPosters.length;
|
||||||
|
|
||||||
|
const handleBack = () => {
|
||||||
|
navigate(-1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCart = () => {
|
||||||
|
navigate('/Cart');
|
||||||
|
};
|
||||||
|
|
||||||
|
const isPosterSelectorPage = location.pathname !== '/PosterSelector';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppBar position="fixed" className={classes.appBar}>
|
||||||
|
<Toolbar className={classes.toolbar}>
|
||||||
|
{isPosterSelectorPage ? (
|
||||||
|
<IconButton edge="start" className={classes.backButton} onClick={handleBack}>
|
||||||
|
<ArrowBackIcon />
|
||||||
|
</IconButton>
|
||||||
|
) : (
|
||||||
|
<IconButton edge="start" className={classes.backButton} disabled>
|
||||||
|
{/* Un IconButton vide et désactivé */}
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<Button className={classes.refreshButton} onClick={onRefreshFiles}>
|
||||||
|
Refresh Files
|
||||||
|
</Button>
|
||||||
|
<IconButton edge="end" className={classes.cartButton} onClick={handleCart}>
|
||||||
|
<Badge badgeContent={totalSelected} color="secondary">
|
||||||
|
<ShoppingCartIcon />
|
||||||
|
</Badge>
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
</Toolbar>
|
||||||
|
</AppBar>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NavBar;
|
||||||
@@ -15,10 +15,12 @@ import {
|
|||||||
Paper,
|
Paper,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import CheckIcon from "@material-ui/icons/Check";
|
import CheckIcon from "@material-ui/icons/Check";
|
||||||
|
import NavBar from "./NavBar";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
root: {
|
root: {
|
||||||
marginTop: theme.spacing(4),
|
marginTop: theme.spacing(4),
|
||||||
|
marginBottom: theme.spacing(4),
|
||||||
},
|
},
|
||||||
paper: {
|
paper: {
|
||||||
padding: theme.spacing(3),
|
padding: theme.spacing(3),
|
||||||
@@ -83,6 +85,8 @@ const PosterGallery = ({ movieId }) => {
|
|||||||
(state) => state.posterSelections[movieId] || []
|
(state) => state.posterSelections[movieId] || []
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log(movieId);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchPosters = async () => {
|
const fetchPosters = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -110,6 +114,7 @@ const PosterGallery = ({ movieId }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<Container className={classes.root}>
|
<Container className={classes.root}>
|
||||||
<Paper elevation={3} className={classes.paper}>
|
<Paper elevation={3} className={classes.paper}>
|
||||||
<Typography variant="h4" gutterBottom className={classes.title}>
|
<Typography variant="h4" gutterBottom className={classes.title}>
|
||||||
@@ -146,6 +151,8 @@ const PosterGallery = ({ movieId }) => {
|
|||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
</Container>
|
</Container>
|
||||||
|
<NavBar />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -17,10 +17,12 @@ import Pagination from "@mui/material/Pagination";
|
|||||||
import { createTheme, ThemeProvider } from "@mui/material/styles";
|
import { createTheme, ThemeProvider } from "@mui/material/styles";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import pulpGif from "../static/images/pulp.gif";
|
import pulpGif from "../static/images/pulp.gif";
|
||||||
|
import NavBar from "./NavBar";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
root: {
|
root: {
|
||||||
marginTop: theme.spacing(4),
|
marginTop: theme.spacing(4),
|
||||||
|
marginBottom: theme.spacing(8),
|
||||||
},
|
},
|
||||||
paper: {
|
paper: {
|
||||||
padding: theme.spacing(3),
|
padding: theme.spacing(3),
|
||||||
@@ -37,7 +39,7 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
},
|
},
|
||||||
list: {
|
list: {
|
||||||
maxHeight: "800px",
|
maxHeight: "800px",
|
||||||
minHeight: "1000px",
|
minHeight: "900px",
|
||||||
},
|
},
|
||||||
listItem: {
|
listItem: {
|
||||||
marginBottom: theme.spacing(2),
|
marginBottom: theme.spacing(2),
|
||||||
@@ -93,7 +95,7 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
height: "200px", // Adjust height as needed to keep component size consistent
|
height: "200px",
|
||||||
},
|
},
|
||||||
progressLabel: {
|
progressLabel: {
|
||||||
marginTop: theme.spacing(2),
|
marginTop: theme.spacing(2),
|
||||||
@@ -186,8 +188,9 @@ const PosterSelector = () => {
|
|||||||
const parsed = queryString.parse(location.search);
|
const parsed = queryString.parse(location.search);
|
||||||
const pageNumber = parsed.page ? parseInt(parsed.page, 10) : 1;
|
const pageNumber = parsed.page ? parseInt(parsed.page, 10) : 1;
|
||||||
setPage(pageNumber);
|
setPage(pageNumber);
|
||||||
fetchMovies(pageNumber);
|
|
||||||
fetchUsername();
|
fetchMovies(pageNumber).then(r => console.log(r));
|
||||||
|
// fetchUsername();
|
||||||
}, [location.search]);
|
}, [location.search]);
|
||||||
|
|
||||||
const fetchUsername = async () => {
|
const fetchUsername = async () => {
|
||||||
@@ -231,7 +234,7 @@ const PosterSelector = () => {
|
|||||||
navigate(
|
navigate(
|
||||||
`/posters/${encodeURIComponent(movieName)}/${encodeURIComponent(
|
`/posters/${encodeURIComponent(movieName)}/${encodeURIComponent(
|
||||||
movieYear
|
movieYear
|
||||||
)}?page=${page}`
|
)}`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -263,6 +266,7 @@ const PosterSelector = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<Container className={classes.root}>
|
<Container className={classes.root}>
|
||||||
<Paper elevation={3} className={classes.paper}>
|
<Paper elevation={3} className={classes.paper}>
|
||||||
<Typography variant="h4" gutterBottom className={classes.title}>
|
<Typography variant="h4" gutterBottom className={classes.title}>
|
||||||
@@ -355,13 +359,6 @@ const PosterSelector = () => {
|
|||||||
/>
|
/>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</Box>
|
</Box>
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
className={classes.refreshButton}
|
|
||||||
onClick={handleRefreshFiles}
|
|
||||||
>
|
|
||||||
Refresh File
|
|
||||||
</Button>
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Box className={classes.noMoviesContainer}>
|
<Box className={classes.noMoviesContainer}>
|
||||||
@@ -388,6 +385,8 @@ const PosterSelector = () => {
|
|||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
</Container>
|
</Container>
|
||||||
|
<NavBar onRefreshFiles={handleRefreshFiles} />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,3 +9,9 @@ export const deselectPoster = (movieId, posterId) => ({
|
|||||||
movieId,
|
movieId,
|
||||||
posterId,
|
posterId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const removePoster = (movieId, posterId) => ({
|
||||||
|
type: "REMOVE_POSTER",
|
||||||
|
movieId,
|
||||||
|
posterId,
|
||||||
|
});
|
||||||
@@ -16,6 +16,13 @@ const posterSelectionReducer = (state = {}, action) => {
|
|||||||
(id) => id !== action.posterId
|
(id) => id !== action.posterId
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
case "REMOVE_POSTER":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
[action.movieId]: state[action.movieId].filter(
|
||||||
|
(id) => id !== action.posterId
|
||||||
|
),
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user