From 10b18a4eebb6552f84bc100040c71a1a08c1f124 Mon Sep 17 00:00:00 2001 From: Vidwa De Seram Date: Thu, 4 Jan 2024 19:58:02 +0530 Subject: [PATCH 1/4] Update Seating.js --- client/src/components/pages/Seating.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/components/pages/Seating.js b/client/src/components/pages/Seating.js index 429399c..cb81c39 100644 --- a/client/src/components/pages/Seating.js +++ b/client/src/components/pages/Seating.js @@ -45,7 +45,7 @@ export default function Seating() { }, [id, showTimeId]); const handleBooking = async () => { - if (bookedSeats.length === 0) { + if (selectedSeats.length === 0) { Swal.fire({ icon: "error", title: "Oops...", @@ -108,6 +108,7 @@ export default function Seating() { title: "Error", text: "An error occurred while booking.", }); + setLoading(false); } }; From 8ec62659a510908afe29fb2937b5b7f00101c380 Mon Sep 17 00:00:00 2001 From: Leo Gavin Date: Thu, 4 Jan 2024 20:17:23 +0530 Subject: [PATCH 2/4] Update booking.service.ts --- server/src/booking/booking.service.ts | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/server/src/booking/booking.service.ts b/server/src/booking/booking.service.ts index ef9229e..1c3d21a 100644 --- a/server/src/booking/booking.service.ts +++ b/server/src/booking/booking.service.ts @@ -4,9 +4,9 @@ import { Model } from 'mongoose'; import { CreateBookingDto } from './dto/create-booking.dto'; import { UpdateBookingDto } from './dto/update-booking.dto'; import { Booking, BookingDocument } from './entities/booking.entity'; -import { ShowTimesService } from 'src/show-times/show-times.service'; -import { PaypalService } from 'src/paypal/paypal.service'; -import { MoviesService } from 'src/movies/movies.service'; +import { ShowTimesService } from '../show-times/show-times.service'; +import { PaypalService } from '../paypal/paypal.service'; +import { MoviesService } from '../movies/movies.service'; @Injectable() export class BookingService { @@ -74,6 +74,25 @@ export class BookingService { return bookings; } + async findAllForUser(filter = {}): Promise { + const bookings = await this.bookingModel + .find( + filter, + Object.keys(this.bookingModel.schema.obj) + .map((key) => key) + .join(' '), + ) + .populate('userId') + .populate('movieId') + .populate('showTimeId') + .exec(); + + if (bookings.length === 0) { + throw new NotFoundException('No booking found matching the criteria'); + } + return bookings; + } + async findOne(filter: any): Promise { const booking = await this.bookingModel .findOne( From ebfbecd3d5470f2b4c88a1acabe26638d0205049 Mon Sep 17 00:00:00 2001 From: Vidwa De Seram Date: Thu, 4 Jan 2024 21:54:27 +0530 Subject: [PATCH 3/4] admin movie fetch and auth-done --- admin/package-lock.json | 39 ++++++ admin/package.json | 2 + admin/src/App.js | 176 ++++++++++++++++++++-------- admin/src/index.js | 18 +-- admin/src/pages/AllMovies.js | 133 +++++++++++---------- admin/src/pages/Login.js | 118 +++++++++++++------ admin/src/pages/auth/UserContext.js | 33 ++++++ 7 files changed, 361 insertions(+), 158 deletions(-) create mode 100644 admin/src/pages/auth/UserContext.js diff --git a/admin/package-lock.json b/admin/package-lock.json index 4a7e8b7..013ab6d 100644 --- a/admin/package-lock.json +++ b/admin/package-lock.json @@ -11,10 +11,12 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.6.4", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.21.1", "react-scripts": "5.0.1", + "sweetalert2": "^11.10.2", "web-vitals": "^2.1.4" } }, @@ -5423,6 +5425,29 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.4.tgz", + "integrity": "sha512-heJnIs6N4aa1eSthhN9M5ioILu8Wi8vmQW9iHQ9NUvfkJb0lEEDUiIdQNAuBtfUt3FxReaKdpQA5DbmMOqzF/A==", + "dependencies": { + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/axobject-query": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", @@ -14634,6 +14659,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -16559,6 +16589,15 @@ "boolbase": "~1.0.0" } }, + "node_modules/sweetalert2": { + "version": "11.10.2", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.2.tgz", + "integrity": "sha512-BYlIxGw6OF9Rw2z1wlnh1U+fvHHkvtg4BGyimV9nZxQRGvCBfx9uonxgwuYpJuYqCtM+2W1KOm8iMIEb/2v7Hg==", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/limonte" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", diff --git a/admin/package.json b/admin/package.json index b9a91d2..7fb0b6d 100644 --- a/admin/package.json +++ b/admin/package.json @@ -6,10 +6,12 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.6.4", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.21.1", "react-scripts": "5.0.1", + "sweetalert2": "^11.10.2", "web-vitals": "^2.1.4" }, "scripts": { diff --git a/admin/src/App.js b/admin/src/App.js index 8329d44..ede1746 100644 --- a/admin/src/App.js +++ b/admin/src/App.js @@ -1,66 +1,138 @@ -import React, { useState } from 'react'; -import { BrowserRouter as Router, Route, Routes, Navigate } from 'react-router-dom'; -import Header from './components/Header'; -import Sidebar from './components/Sidebar'; -import Login from './pages/Login'; -import Movies from './pages/Movie'; -import MovieUpdate from './pages/MovieUpdate'; -import AddShowtime from './pages/AddShowtime'; -import UpdateShowtime from './pages/UpdateShowtime'; -import DeleteMovieForm from './pages/DeleteMovie'; -import DeleteShowtime from './pages/DeleteShowtime'; -import AddBooking from './pages/AddBooking'; -import UpdateBooking from './pages/UpdateBooking'; -import DeleteBooking from './pages/DeleteBooking'; -import Chat from './pages/Chat'; -import MovieGrid from './pages/AllMovies'; -import ShowtimeGrid from './pages/AllShowtimes'; -import BookingGrid from './pages/AllBookings'; -import RegisterPage from './pages/Signup'; +import React, { useState, useEffect, useContext } from "react"; +import { + BrowserRouter as Router, + Route, + Routes, + Navigate, +} from "react-router-dom"; + +import axios from "axios"; +import Swal from "sweetalert2"; + +import Header from "./components/Header"; +import Sidebar from "./components/Sidebar"; +import Login from "./pages/Login"; +import Movies from "./pages/Movie"; +import MovieUpdate from "./pages/MovieUpdate"; +import AddShowtime from "./pages/AddShowtime"; +import UpdateShowtime from "./pages/UpdateShowtime"; +import DeleteMovieForm from "./pages/DeleteMovie"; +import DeleteShowtime from "./pages/DeleteShowtime"; +import AddBooking from "./pages/AddBooking"; +import UpdateBooking from "./pages/UpdateBooking"; +import DeleteBooking from "./pages/DeleteBooking"; +import Chat from "./pages/Chat"; +import MovieGrid from "./pages/AllMovies"; +import ShowtimeGrid from "./pages/AllShowtimes"; +import BookingGrid from "./pages/AllBookings"; +import RegisterPage from "./pages/Signup"; + +import { UserContext } from "./pages/auth/UserContext"; // Import UserContext +import { UserProvider } from "./pages/auth/UserContext"; const App = () => { + const { updateUserData } = useContext(UserContext); // Use updateUserData from UserContext const [isLoggedIn, setIsLoggedIn] = useState(false); + const checkUserLoggedIn = async () => { + const token = localStorage.getItem("admin-token"); // Use a different token key for admin + if (token) { + try { + const profileResponse = await axios.get( + `${process.env.REACT_APP_API_PATH}/users/profile`, // API endpoint for admin profile + { + headers: { + Authorization: `Bearer ${token}`, // Use the token from localStorage + }, + } + ); + + if (profileResponse.data && profileResponse.data.type === "ADMIN") { + updateUserData(profileResponse.data); + setIsLoggedIn(true); + } else { + // Handle non-admin user + Swal.fire({ + title: "Access Denied", + text: "You are not authorized to access the admin panel.", + icon: "error", + confirmButtonText: "OK", + }); + localStorage.removeItem("admin-token"); // Optionally remove the token + setIsLoggedIn(false); + } + } catch (error) { + console.error("Error fetching admin data:", error); + Swal.fire({ + title: "Error", + text: "An error occurred while fetching user data.", + icon: "error", + confirmButtonText: "OK", + }); + } + } + }; + useEffect(() => { + checkUserLoggedIn(); + }, []); + const handleLogin = () => { setIsLoggedIn(true); }; + function ProtectedRoute({ children }) { + if (!isLoggedIn) { + console.log(isLoggedIn); + return ; + } + return children; + } return ( - - {isLoggedIn ? ( -
-
-
- -
- - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - + + + {isLoggedIn ? ( +
+
+
+ +
+ + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } + /> + +
-
- ) : ( - - } /> - } /> - } /> - - )} - + ) : ( + + } /> + } /> + } /> + + )} + + ); }; diff --git a/admin/src/index.js b/admin/src/index.js index d563c0f..766d3ca 100644 --- a/admin/src/index.js +++ b/admin/src/index.js @@ -1,14 +1,16 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import App from './App'; -import reportWebVitals from './reportWebVitals'; +import React from "react"; +import ReactDOM from "react-dom/client"; +import "./index.css"; +import App from "./App"; +import reportWebVitals from "./reportWebVitals"; -const root = ReactDOM.createRoot(document.getElementById('root')); +import { UserProvider } from "./pages/auth/UserContext"; + +const root = ReactDOM.createRoot(document.getElementById("root")); root.render( - + - + ); // If you want to start measuring performance in your app, pass a function diff --git a/admin/src/pages/AllMovies.js b/admin/src/pages/AllMovies.js index c8d9abc..0e156e6 100644 --- a/admin/src/pages/AllMovies.js +++ b/admin/src/pages/AllMovies.js @@ -1,74 +1,79 @@ -import React from 'react'; -import '../css/AllMovies.css'; // Ensure you have a MovieCard.css file in the same directory -import { Link } from 'react-router-dom'; - -const movies = [ - // JSON-like array of movie objects - { - "id": 1, - "title": "Wonka", - "coverImage": "/images/wonka.jpeg" - }, - { - "id": 2, - "title": "Marvels", - "coverImage": "/images/marvels1.jpg" - }, - { - "id": 3, - "title": "Migration", - "coverImage": "/images/migration.jpg" - }, - { - "id": 4, - "title": "Aquaman", - "coverImage": "/images/aquaman-cover-2.jpg" - } - // ... add more movie objects as needed -]; +import "../css/AllMovies.css"; // Ensure you have a MovieCard.css file in the same directory +import { Link } from "react-router-dom"; +import React, { useState, useEffect } from "react"; +import axios from "axios"; -const MovieCard = ({ movie }) => { - return ( -
- {movie.title} -
-

{movie.title}

-
- - - - - - - - - -
-
+const MovieCard = ({ movie, isComingSoon }) => { + return ( +
+ {movie.title} +
+

{movie.title}

+ {isComingSoon &&

Coming Soon

} +
+ + + + + + + + +
- ); +
+
+ ); }; - const MovieGrid = () => { - return ( -
-
- - - - + const [currentMovies, setCurrentMovies] = useState([]); + const [upcomingMovies, setUpcomingMovies] = useState([]); - -
-
- {movies.map((movie, index) => ( - - ))} -
-
- ); + const fetchMovies = async (nowShowing) => { + try { + const adminToken = localStorage.getItem("admin-token"); + const response = await axios.get( + `${process.env.REACT_APP_API_PATH}/movies`, + { + params: { nowShowing }, + headers: { Authorization: `Bearer ${adminToken}` }, + } + ); + if (nowShowing) { + setCurrentMovies(response.data); + } else { + setUpcomingMovies(response.data); + } + } catch (error) { + console.error("Error fetching movies:", error); + } + }; + + useEffect(() => { + fetchMovies(true); // Fetch current movies + fetchMovies(false); // Fetch upcoming movies + }, []); + return ( +
+
+ + + + + +
+
+ {currentMovies.map((movie, index) => ( + + ))} + {upcomingMovies.map((movie, index) => ( + + ))} +
+
+ ); }; export default MovieGrid; diff --git a/admin/src/pages/Login.js b/admin/src/pages/Login.js index 9f46b2e..f5f89b6 100644 --- a/admin/src/pages/Login.js +++ b/admin/src/pages/Login.js @@ -1,43 +1,93 @@ -// Login.js -import React, { useState } from 'react'; -import '../css/Login.css'; // Import the CSS file -import { Link } from 'react-router-dom'; +import React, { useState, useContext } from "react"; +import { Link, useNavigate } from "react-router-dom"; +import axios from "axios"; +import Swal from "sweetalert2"; -const Login = ({ onLogin }) => { - const [username, setUsername] = useState(''); - const [password, setPassword] = useState(''); +import { UserContext } from "./auth/UserContext"; - const handleSubmit = (event) => { - event.preventDefault(); - // Implement your login logic here - onLogin(); - }; +const Login = () => { + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const navigate = useNavigate(); + const { updateUserData } = useContext(UserContext); // Use the context to set user data - return ( -
-
-

Login

-
-
- setUsername(e.target.value)} /> -
-
-
- setPassword(e.target.value)} /> -
-
-
- -
+ const handleSubmit = async (event) => { + event.preventDefault(); + try { + // Authenticate user + const loginResponse = await axios.post( + `${process.env.REACT_APP_API_PATH}/auth/signin`, + { email: username, password } + ); + const token = loginResponse.data.accessToken; + // Fetch user profile + const profileResponse = await axios.get( + `${process.env.REACT_APP_API_PATH}/users/profile`, + { headers: { Authorization: `Bearer ${token}` } } + ); + console.log(profileResponse); + const userData = profileResponse.data; // User data from profile response - -

Register

- -
-
+ if (userData && userData.type === "ADMIN") { + localStorage.setItem("admin-token", token); // Store the token + updateUserData(userData); // Update user data in context + console.log(userData); + navigate("/movies-view"); // Navigate to the admin dashboard + } else { + Swal.fire({ + title: "Access Denied", + text: "You are not authorized to access the admin panel.", + icon: "error", + confirmButtonText: "OK", + }); + } + } catch (error) { + console.error("Login Error:", error); + Swal.fire({ + title: "Login Failed", + text: "Invalid username or password.", + icon: "error", + confirmButtonText: "OK", + }); + } + }; + return ( +
+
+

Login

+
+ +
+ setUsername(e.target.value)} + />
- ); +
+ +
+ setPassword(e.target.value)} + /> +
+
+
+ +
+ + +

Register

+ +
+
+
+ ); }; export default Login; diff --git a/admin/src/pages/auth/UserContext.js b/admin/src/pages/auth/UserContext.js new file mode 100644 index 0000000..05379e3 --- /dev/null +++ b/admin/src/pages/auth/UserContext.js @@ -0,0 +1,33 @@ +import React, { createContext, useContext, useState } from "react"; + +// Create Context +export const UserContext = createContext({ + userData: null, + setUserData: () => {}, +}); + +export const UserProvider = ({ children }) => { + const [userData, setUserData] = useState(null); + const [isLoading, setIsLoading] = useState(false); // Global loading state + + // Function to update user data + const updateUserData = (data) => { + setUserData(data); + }; + + // Function to set loading state + const setLoading = (loading) => { + setIsLoading(loading); + }; + + return ( + + {children} + + ); +}; + +// Custom hook to use UserContext +export const useUserContext = () => useContext(UserContext); From 3b567b2d44bb88ddb68274a3a6a5de842fd7c46b Mon Sep 17 00:00:00 2001 From: Vidwa De Seram Date: Fri, 5 Jan 2024 00:13:52 +0530 Subject: [PATCH 4/4] movie(add, delete ,update )admin-done --- admin/package-lock.json | 145 ++++++++++++++ admin/package.json | 1 + admin/src/App.js | 2 +- admin/src/css/Movie.css | 101 ++++++---- admin/src/pages/AllMovies.js | 38 +++- admin/src/pages/Movie.js | 134 ++++++++++--- admin/src/pages/MovieUpdate.js | 179 +++++++++++++++--- .../fileUpload_logic/fileUpload_Logic.js | 47 +++++ 8 files changed, 553 insertions(+), 94 deletions(-) create mode 100644 admin/src/pages/fileUpload_logic/fileUpload_Logic.js diff --git a/admin/package-lock.json b/admin/package-lock.json index 013ab6d..5e74bd2 100644 --- a/admin/package-lock.json +++ b/admin/package-lock.json @@ -14,6 +14,7 @@ "axios": "^1.6.4", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-loader-spinner": "^6.1.6", "react-router-dom": "^6.21.1", "react-scripts": "5.0.1", "sweetalert2": "^11.10.2", @@ -2287,6 +2288,24 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", + "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", + "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -4546,6 +4565,11 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" }, + "node_modules/@types/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw==" + }, "node_modules/@types/testing-library__jest-dom": { "version": "5.14.9", "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz", @@ -5990,6 +6014,14 @@ "node": ">= 6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -6425,6 +6457,14 @@ "postcss": "^8.4" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, "node_modules/css-declaration-sorter": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", @@ -6606,6 +6646,16 @@ "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/css-tree": { "version": "1.0.0-alpha.37", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", @@ -14952,6 +15002,27 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/react-loader-spinner": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/react-loader-spinner/-/react-loader-spinner-6.1.6.tgz", + "integrity": "sha512-x5h1Jcit7Qn03MuKlrWcMG9o12cp9SNDVHVJTNRi9TgtGPKcjKiXkou4NRfLAtXaFB3+Z8yZsVzONmPzhv2ErA==", + "dependencies": { + "react-is": "^18.2.0", + "styled-components": "^6.1.2" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-loader-spinner/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -15837,6 +15908,11 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -16364,6 +16440,70 @@ "webpack": "^5.0.0" } }, + "node_modules/styled-components": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.6.tgz", + "integrity": "sha512-DgTLULSC29xpabJ24bbn1+hulU6vvGFQf4RPwBOJrm8WJFnN42yXpo5voBt3jDSJBa5tBd1L6PqswJjQ0wRKdg==", + "dependencies": { + "@emotion/is-prop-valid": "1.2.1", + "@emotion/unitless": "0.8.0", + "@types/stylis": "4.2.0", + "css-to-react-native": "3.2.0", + "csstype": "3.1.2", + "postcss": "8.4.31", + "shallowequal": "1.1.0", + "stylis": "4.3.1", + "tslib": "2.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/styled-components/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, "node_modules/stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -16379,6 +16519,11 @@ "postcss": "^8.2.15" } }, + "node_modules/stylis": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz", + "integrity": "sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ==" + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", diff --git a/admin/package.json b/admin/package.json index 7fb0b6d..b2625f1 100644 --- a/admin/package.json +++ b/admin/package.json @@ -9,6 +9,7 @@ "axios": "^1.6.4", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-loader-spinner": "^6.1.6", "react-router-dom": "^6.21.1", "react-scripts": "5.0.1", "sweetalert2": "^11.10.2", diff --git a/admin/src/App.js b/admin/src/App.js index ede1746..def3a55 100644 --- a/admin/src/App.js +++ b/admin/src/App.js @@ -104,7 +104,7 @@ const App = () => { } /> } /> - } /> + } /> } /> } /> } /> diff --git a/admin/src/css/Movie.css b/admin/src/css/Movie.css index c26930f..bada540 100644 --- a/admin/src/css/Movie.css +++ b/admin/src/css/Movie.css @@ -1,66 +1,93 @@ /* MovieForm.css */ .movie-form-container { - display: flex; - justify-content: center; - align-items: center; - height: 100%; - - - /* Dark background */ + display: flex; + justify-content: center; + align-items: center; + height: 100%; + /* Dark background */ } .movie-form { - max-width: 480px; - margin: auto; - background-color: #444; - /* Slightly lighter background for the form */ - padding: 20px 50px; - border-radius: 5px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5); - color: #fff; - font-family: 'Roboto', sans-serif; + max-width: 480px; + margin: auto; + background-color: #444; + /* Slightly lighter background for the form */ + padding: 20px 50px; + border-radius: 5px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5); + color: #fff; + font-family: "Roboto", sans-serif; } .movie-form h2 { - text-align: center; - color: #ffd700; - /* Theme color for headings */ + text-align: center; + color: #ffd700; + /* Theme color for headings */ } .movie-form input, .movie-form textarea, .movie-form button { - width: 100%; - padding: 10px; - margin-bottom: 15px; - border-radius: 3px; - border: none; + width: 100%; + padding: 10px; + margin-bottom: 15px; + border-radius: 3px; + border: none; } .movie-form textarea { - height: 100px; - resize: vertical; - /* Allow vertical resizing */ + height: 100px; + resize: vertical; + /* Allow vertical resizing */ } .movie-form button { - background-color: #ffd700; - /* Theme color for button */ - color: #333; - cursor: pointer; - font-weight: bold; + background-color: #ffd700; + /* Theme color for button */ + color: #333; + cursor: pointer; + font-weight: bold; } .movie-form .upload-container { - margin-bottom: 15px; + margin-bottom: 15px; } .movie-form input[type="file"] { - color: #fff; + color: #fff; } .upload-container { - margin-top: 10px; - display: flex; -} \ No newline at end of file + margin-top: 10px; + display: flex; +} +.login-wrapper { + position: relative; +} + +.loading-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + background: rgba(255, 255, 255, 0.8); + backdrop-filter: blur(5px); + z-index: 1000; +} + +.loader-container { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 1010; +} + +.blurred { + filter: blur(5px); +} diff --git a/admin/src/pages/AllMovies.js b/admin/src/pages/AllMovies.js index 0e156e6..83d565b 100644 --- a/admin/src/pages/AllMovies.js +++ b/admin/src/pages/AllMovies.js @@ -3,8 +3,34 @@ import { Link } from "react-router-dom"; import React, { useState, useEffect } from "react"; import axios from "axios"; +import Swal from "sweetalert2"; const MovieCard = ({ movie, isComingSoon }) => { + const handleDelete = async (id) => { + const confirmResult = await Swal.fire({ + title: "Are you sure?", + text: "You won't be able to revert this!", + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#3085d6", + cancelButtonColor: "#d33", + confirmButtonText: "Yes, delete it!", + }); + + if (confirmResult.isConfirmed) { + try { + const adminToken = localStorage.getItem("admin-token"); + await axios.delete(`${process.env.REACT_APP_API_PATH}/movies/${id}`, { + headers: { Authorization: `Bearer ${adminToken}` }, + }); + Swal.fire("Deleted!", "The movie has been deleted.", "success"); + // Optionally refresh the list of movies here + } catch (error) { + console.error("Error deleting the movie:", error); + Swal.fire("Error!", "There was a problem deleting the movie.", "error"); + } + } + }; return (
{movie.title} @@ -12,15 +38,19 @@ const MovieCard = ({ movie, isComingSoon }) => {

{movie.title}

{isComingSoon &&

Coming Soon

}
- + - - - + +
diff --git a/admin/src/pages/Movie.js b/admin/src/pages/Movie.js index 1aeaba0..01fae56 100644 --- a/admin/src/pages/Movie.js +++ b/admin/src/pages/Movie.js @@ -1,33 +1,115 @@ -// MovieForm.js -import React from 'react'; -import '../css/MovieUpdate.css'; // Import the CSS file for styling +import React, { useState } from "react"; +import "../css/MovieUpdate.css"; +import axios from "axios"; +import Swal from "sweetalert2"; +import { TailSpin } from "react-loader-spinner"; +import uploadToS3 from "./fileUpload_logic/fileUpload_Logic"; +import { useUserContext } from "./auth/UserContext"; const MovieForm = () => { - return ( + const [movie, setMovie] = useState({ + name: "", + year: "", + trailer: "", + startDate: "", + nowShowing: false, + }); + const { isLoading, setLoading } = useUserContext(); -
-
-

Add a New Movie

- - {/* */} - - - -
-
- - -
-
- - -
-
- -
-
+ const handleSubmit = async (event) => { + event.preventDefault(); + setLoading(true); + let coverImageUrl, bannerImageUrl; + const coverImageInput = document.querySelector('input[type="file"]'); + const bannerImageInput = document.querySelector('input[type="file"]'); + if (coverImageInput && coverImageInput.files.length > 0) { + coverImageUrl = await uploadToS3(coverImageInput.files[0]); + } + if (bannerImageInput && bannerImageInput.files.length > 0) { + bannerImageUrl = await uploadToS3(bannerImageInput.files[0]); + } + const newMovieData = { + ...movie, + coverImage: coverImageUrl, + bannerImage: bannerImageUrl, + }; + const adminToken = localStorage.getItem("admin-token"); + console.log(newMovieData); + try { + await axios.post( + `${process.env.REACT_APP_API_PATH}/movies`, + newMovieData, + { headers: { Authorization: `Bearer ${adminToken}` } } + ); + setLoading(false); + Swal.fire({ + title: "Success!", + text: "New movie added successfully.", + icon: "success", + confirmButtonText: "OK", + }); + } catch (error) { + console.log(error); + setLoading(false); + Swal.fire({ + title: "Error!", + text: "There was a problem adding the movie. Please try again.", + icon: "error", + confirmButtonText: "OK", + }); + } + }; - ); + return ( +
+ {isLoading && ( +
+ +
+ )} +
+
+

Add a New Movie

+ setMovie({ ...movie, name: e.target.value })} + /> + setMovie({ ...movie, year: e.target.value })} + /> + setMovie({ ...movie, trailer: e.target.value })} + /> + setMovie({ ...movie, startDate: e.target.value })} + /> + setMovie({ ...movie, nowShowing: e.target.value })} + /> +
+
+ + +
+
+ + +
+
+ +
+
+
+ ); }; export default MovieForm; diff --git a/admin/src/pages/MovieUpdate.js b/admin/src/pages/MovieUpdate.js index ef7c0f6..be3a2bc 100644 --- a/admin/src/pages/MovieUpdate.js +++ b/admin/src/pages/MovieUpdate.js @@ -1,32 +1,159 @@ -import React from 'react'; -import '../css/Movie.css'; // Import the CSS file for styling +import React, { useState, useEffect } from "react"; +import { useParams } from "react-router-dom"; +import "../css/Movie.css"; +import uploadToS3 from "./fileUpload_logic/fileUpload_Logic"; +import axios from "axios"; +import Swal from "sweetalert2"; +import { TailSpin } from "react-loader-spinner"; +import { useUserContext } from "./auth/UserContext"; const MovieUpdate = () => { - return ( - -
-
-

Update a New Movie

- - {/* */} - - - -
-
- - -
-
- - -
-
- -
-
+ const { id } = useParams(); + const [movie, setMovie] = useState({ + name: "", + year: "", + trailer: "", + startDate: "", + }); + + const { isLoading, setLoading } = useUserContext(); + + useEffect(() => { + const fetchMovie = async () => { + const adminToken = localStorage.getItem("admin-token"); + try { + setLoading(true); + const response = await axios.get( + `${process.env.REACT_APP_API_PATH}/movies/${id}`, + { + headers: { Authorization: `Bearer ${adminToken}` }, + } + ); + setMovie(response.data); + setLoading(false); + } catch (error) { + console.error("Error fetching movie:", error); + setLoading(false); + Swal.fire({ + title: "Error!", + text: "Failed to fetch movie data. Please try again.", + icon: "error", + confirmButtonText: "OK", + }); + } + }; + + fetchMovie(); + }, [id]); + + const handleSubmit = async (event) => { + event.preventDefault(); + + try { + setLoading(true); - ); + let coverImageUrl = movie.coverImage; + let bannerImageUrl = movie.bannerImage; + + const coverImageInput = document.querySelector( + 'input[type="file"][name="coverImage"]' + ); + const bannerImageInput = document.querySelector( + 'input[type="file"][name="bannerImage"]' + ); + + if (coverImageInput && coverImageInput.files.length > 0) { + coverImageUrl = await uploadToS3(coverImageInput.files[0]); + } + console.log(bannerImageInput.files); + if (bannerImageInput && bannerImageInput.files.length > 0) { + bannerImageUrl = await uploadToS3(bannerImageInput.files[0]); + console.log(bannerImageUrl); + } + + const updateData = { + ...movie, + coverImage: coverImageUrl, + bannerImage: bannerImageUrl, + }; + + const adminToken = localStorage.getItem("admin-token"); + await axios.patch( + `${process.env.REACT_APP_API_PATH}/movies/${id}`, + updateData, + { + headers: { Authorization: `Bearer ${adminToken}` }, + } + ); + setLoading(false); + Swal.fire({ + title: "Success!", + text: "Movie updated successfully.", + icon: "success", + confirmButtonText: "OK", + }); + setLoading(false); + } catch (error) { + console.error("Error updating movie:", error); + setLoading(false); + Swal.fire({ + title: "Error!", + text: "There was a problem updating the movie. Please try again.", + icon: "error", + confirmButtonText: "OK", + }); + } + }; + + return ( +
+ {isLoading && ( +
+ +
+ )} +
+
+

Update a Movie

+ setMovie({ ...movie, name: e.target.value })} + /> + setMovie({ ...movie, year: e.target.value })} + /> + setMovie({ ...movie, trailer: e.target.value })} + /> + setMovie({ ...movie, startDate: e.target.value })} + /> +
+
+ + +
+
+ + +
+
+ +
+
+
+ ); }; export default MovieUpdate; diff --git a/admin/src/pages/fileUpload_logic/fileUpload_Logic.js b/admin/src/pages/fileUpload_logic/fileUpload_Logic.js new file mode 100644 index 0000000..4abf23f --- /dev/null +++ b/admin/src/pages/fileUpload_logic/fileUpload_Logic.js @@ -0,0 +1,47 @@ +import Swal from "sweetalert2"; + +async function uploadToS3(file) { + if (!file) { + throw new Error("Please select a file first."); + } + + const fileName = file.name; + const contentType = file.type; + const domain = "MOVIE"; // Adjust as needed + + try { + // POST request to your backend + const response = await fetch( + "https://king-prawn-app-4l8vr.ondigitalocean.app/v1/s3/generate-presigned-url", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ fileName, domain, contentType }), + } + ); + + const data = await response.json(); + + // PUT request to S3 using the pre-signed URL + const result = await fetch(data.presignedUrl, { + method: "PUT", + headers: { + "Content-Type": file.type, + }, + body: file, + }); + + if (result.status !== 200) { + throw new Error("Upload failed."); + } + + return data.s3url; + } catch (error) { + console.error("Error:", error); + throw error; + } +} + +export default uploadToS3;