diff --git a/README.md b/README.md index f9037e1..f9b1a3d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,33 @@ +# Events App + +## Welcome to the Events App, a platform designed to help you discover, browse, and purchase tickets for various events. From concerts to conferences, workshops to sports events, the Events App has you covered. + +## Features + +- **Event Discovery**: Browse through a diverse range of events happening locally or globally. +- **Ticket Purchase**: Seamlessly purchase tickets for your favorite events with just a few clicks. +- **User Authentication**: Securely log in to your account to manage tickets and preferences. +- **Event Details**: Access comprehensive information about each event, including date, time, venue, description, and ticket availability. +- **Purchase History**: Keep track of all the events you've purchased tickets for. +- **Email Confirmation**: Receive a confirmation email upon successful ticket purchase. +- **Responsive Design**: Enjoy a smooth experience across all devices, whether it's desktop, tablet, or mobile. + +## Technologies Used + +- **Frontend**: React.js, React Router, Material-UI +- **Backend**: Strapi +- **Authentication**: JSON Web Tokens (JWT) +- **API Integration**: Axios, Mapbox, Strapi, Stripe +- **Database**: MongoDB Atlas +- **Deployment**: Vercel (Frontend), Heroku (Backend) +- **Email Service**: SendGrid, Nodemailer + +## Setup + +1. **Clone the Repository**: + ```bash + git clone https://github.com/Saulul/KV6002-Web-App/ + ## Running React on Repl.it [React](https://reactjs.org/) is a popular JavaScript library for building user interfaces. @@ -7,24 +37,41 @@ Using the two in conjunction is one of the fastest ways to build a web app. ### Getting Started -- Hit run +- Click "Run" to start the development server - Edit [App.tsx](#src/App.tsx) and watch it live update! -By default, Replit runs the `dev` script, but you can configure it by changing the `run` field in the [configuration file](#.replit). Here are the vite docs for [serving production websites](https://vitejs.dev/guide/build.html) +By default, Replit runs the `dev` script, but you can configure it by changing the `run` field in the [configuration file](#.replit). Here are the Vite docs for [serving production websites](https://vitejs.dev/guide/build.html) + +### TypeScript -### Typescript +Simply rename any file from `.jsx` to `.tsx`. Alternatively, you can use our [TypeScript Template](https://replit.com/@replit/React-TypeScript) -Just rename any file from `.jsx` to `.tsx`. You can also try our [TypeScript Template](https://replit.com/@replit/React-TypeScript) +### Installation of Packages +1. **Install Stripe Integration**: + ```bash + npm install @stripe/stripe-js -### Installation of packages -**install** npm install @stripe/stripe-js +2. **Install Material-UI**: + ```bash + npm install @mui/material @emotion/react @emotion/styled -(for stripe integration) +3. **Install Additional Material-UI Packages**: + ```bash + npm install @mui/material @mui/icons-material react -**install** npm install @mui/material @emotion/react @emotion/styled +If you encounter any errors, check the console for missing package installations. -(React component library that provides pre-designed and customizable components following the Material Design guidelines developed by Google) +### Login and Signup Component Credentials +- **Username**: todd@gmail.com +- **Password**: password -**install** npm install @mui/material @mui/icons-material react +### Testing Stripe Payment Integration +For testing purposes, use the following payment information: +- **Card Number**: 4242 4242 4242 4242 +- **Expiration Date**: 20/09 +- **CSV**: 456 +- **Name**: Mickey Mouse +- **Email**: mickeymouse@gmail.com +- **Address**: *any* -(Material-UI components for React applications. These components are designed following the Material Design guidelines and cover a wide range of UI elements such as buttons, forms, cards, navigation components, and more.) \ No newline at end of file +Feel free to explore and enjoy the Events App! If you have any questions or feedback, don't hesitate to reach out. diff --git a/src/App.tsx b/src/App.tsx index 7bc6dc6..803ac7a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -25,7 +25,6 @@ const App: FC = () => { } /> } /> } />" - } /> } /> } /> } /> diff --git a/src/Purchase.css b/src/Purchase.css index 050bb41..7e301b1 100644 --- a/src/Purchase.css +++ b/src/Purchase.css @@ -6,7 +6,8 @@ background-color: #fff; border-radius: 10px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); - margin-top:20px; + margin-top: 20px; + align-self: center; } .ticket-header { @@ -24,149 +25,103 @@ .ticket-header p { font-size: 16px; - } .ticket-body { padding: 20px; } -.ticket-description { - margin-bottom: 20px; - -} - .ticket-tickets { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; + align-self: center; + +} + +.purchase-button { + background-color: #333; + color: #fff; + border: 2px solid #000; + border-radius: 5px; + padding: 10px 20px; + cursor: pointer; } .ticket-ticket { background-color: #f8f9fa; border-radius: 5px; padding: 15px; - border: 2px solid #121111; + border: 2px solid #ccc; + border: 2px solid #1d1b1b; + + } .ticket-ticket h3 { font-size: 18px; margin-bottom: 10px; - border: 2px solid #121111; - font-weight:bold; - - -} -..input{ - border-block-color: #121111; + font-weight: bold; } + .ticket-ticket input { width: 100%; padding: 10px; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 5px; - border: 2px solid #121111; - - -} -.ticke-ticket button{ - border: 2px solid #121111; -} + border: 2px solid #1d1b1b; -.ticket-footer { - text-align: center; - border: 2px solid #121111; - border-block-end-width: 2px; - border-radius: 10px 10px; - -} - -.purchase-button { - background-color: transparent; - color: #333; - border: 2px solid #121111; - padding: 12px 24px; /* Adjust padding */ - border-radius: 5px; - font-size: 16px; - cursor: pointer; - transition: background-color 0.3s ease; } -.purchase-button:hover { +.ticket-ticket button { background-color: #333; color: #fff; + border: 2px solid #000; + border-radius: 5px; + padding: 10px 20px; + border-radius: 5px; + cursor: pointer; } - .dialog { - position: fixed; + position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: #fff; - padding: 20px; - border-radius: 10px; - box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); - z-index: 999; - text-bold: bold; - border: 2px solid #121111; -} - -.dialog h3 { - font-size: 20px; - margin-bottom: 10px; - border: 2px solid #121111; - text-bold: bold; -} - -.dialog p { - font-size: 16px; - margin-bottom: 10px; - border: 2px solid #121111; -} -.ticket { - - /* Your existing CSS styles for the ticket container */ - position: relative; /* Set the container to relative position */ -} - -.dialog { - /* Styles for the dialog */ - position: absolute; /* Set the dialog to absolute position */ - top: 50%; /* Center vertically */ - left: 50%; /* Center horizontally */ - transform: translate(-50%, -50%); /* Center the dialog perfectly */ - background-color: #fff; border-radius: 5px; padding: 20px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); + border: 2px solid #ccc; + border: 2px solid #1d1b1b; + } .dialog button { - /* Styles for the buttons inside the dialog */ background-color: #333; color: #fff; - border-color: #121111;; + border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; + border: 2px solid #1d1b1b; + } .error-dialog { - /* Styles for the error dialog */ - position: absolute; /* Set the error dialog to absolute position */ - top: 50%; /* Center vertically */ - left: 50%; /* Center horizontally */ - transform: translate(-50%, -50%); /* Center the error dialog perfectly */ + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); background-color: #fff; border-radius: 5px; padding: 20px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); + border: 2px solid #1d1b1b; } .error-dialog button { - /* Styles for the close button inside the error dialog */ background-color: #333; color: #fff; border: none; @@ -175,98 +130,59 @@ cursor: pointer; } -.error-dialog button:hover { - background-color: #555; /* Change color on hover */ -} -.ticket { - width: 80%; - margin: auto; - padding: 20px; - border: 1px solid #ccc; - border-radius: 5px; -} - -.ticket-header { - margin-bottom: 20px; -} - -.ticket-header h1 { - font-size: 24px; - margin-bottom: 5px; -} - -.ticket-header p { - font-size: 16px; - margin-bottom: 5px; -} - -.ticket-body { - margin-bottom: 20px; -} - -.ticket-body h2 { - font-size: 20px; - margin-bottom: 10px; -} - -.ticket-body p { - font-size: 16px; -} - -.ticket-tickets { +.purchase-confirmation-container { display: flex; - justify-content: space-between; - margin-bottom: 20px; -} - -.ticket-tickets div { - flex-basis: 48%; + justify-content: center; + align-items: center; + height: 100vh; + background-color: #f8f9fa; /* Light gray background */ } -.ticket-tickets h3 { - font-size: 18px; - margin-bottom: 10px; +.purchase-confirmation { + background-color: #ffffff; /* White background */ + border-radius: 10px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + padding: 20px; + text-align: center; } -.ticket-tickets p { - font-size: 16px; - margin-bottom: 5px; +.purchase-confirmation h2 { + color: #333; /* Dark gray text */ } -.ticket-footer { - text-align: center; +.purchase-confirmation p { + margin-bottom: 20px; + color: #555; /* Medium gray text */ } -.dialog { - border: 1px solid #ccc; - border-radius: 5px; - padding: 10px; - margin-bottom: 10px; +.confirmation-options { + margin-top: 20px; } -.dialog h3 { - font-size: 18px; +.confirmation-options p { + font-weight: bold; margin-bottom: 10px; + color: #333; /* Dark gray text */ } -.dialog p { - font-size: 16px; - margin-bottom: 5px; +.confirmation-options div { + display: flex; + justify-content: center; } -.error-dialog { - border: 1px solid #ff0000; +.option-link { + margin: 0 10px; + padding: 10px 20px; + background-color: #333; /* Dark gray background */ + color: #fff; /* White text */ + text-decoration: none; border-radius: 5px; - padding: 10px; - margin-bottom: 10px; + transition: background-color 0.3s ease; } -.error-dialog p { - font-size: 16px; - color: #ff0000; - margin-bottom: 5px; +.option-link:hover { + background-color: #555; /* Darker gray background on hover */ } -/*///// purchase confirmation css /////*/ .purchase-confirmation-container { display: flex; @@ -277,7 +193,7 @@ } .purchase-confirmation { - background-color: #ffffff; /* White background */ + background-color: #fff; /* White background */ border-radius: 10px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); padding: 20px; @@ -285,12 +201,13 @@ } .purchase-confirmation h2 { - color: #343a40; /* Dark gray text */ + color: #333; /* Dark gray text */ + margin-bottom: 20px; } .purchase-confirmation p { + color: #555; /* Medium gray text */ margin-bottom: 20px; - color: #6c757d; /* Medium gray text */ } .confirmation-options { @@ -300,6 +217,7 @@ .confirmation-options p { font-weight: bold; margin-bottom: 10px; + color: #333; /* Dark gray text */ } .confirmation-options div { @@ -310,13 +228,46 @@ .option-link { margin: 0 10px; padding: 10px 20px; - background-color: #343a40; /* Dark gray background */ - color: #ffffff; /* White text */ + background-color: #333; /* Dark gray background */ + color: #fff; /* White text */ text-decoration: none; border-radius: 5px; transition: background-color 0.3s ease; } .option-link:hover { - background-color: #495057; /* Darker gray background on hover */ + background-color: #555; /* Darker gray background on hover */ +} + + + + + +/* not-found.css */ +.not-found-container { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + background-color: #f8f9fa; /* Light gray background */ +} + +.not-found-content { + background-color: #ffffff; /* White background */ + border-radius: 10px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + padding: 20px; + text-align: center; +} + +.not-found-title { + color: #343a40; /* Dark gray text */ + font-size: 2.5rem; + margin-bottom: 20px; +} + +.not-found-message { + color: #6c757d; /* Medium gray text */ + font-size: 1.2rem; + line-height: 1.5; } diff --git a/src/components/ConfirmationPage.jsx b/src/components/ConfirmationPage.jsx index 002d452..54504ea 100644 --- a/src/components/ConfirmationPage.jsx +++ b/src/components/ConfirmationPage.jsx @@ -1,12 +1,50 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { Link } from 'react-router-dom'; +import axios from 'axios'; + +function PurchaseConfirmation({ event }) { // Pass the event data as a prop + + useEffect(() => { + const sendEmail = async (data) => { + try { + const response = await axios.post('https://apis-sk.vercel.app/api/send-email', data); + console.log(response.data); // Log success message + return response.data; + } catch (error) { + console.error('Error:', error); + throw new Error('Error sending email'); + } + }; + + const updateTicketsSold = async () => { + try { + const response = await axios.patch(`https://your-api.com/events/${event.id}`, { + ticketsSold: event.ticketsSoldRegular + 1 + }); + console.log(response.data); // Log success message + } catch (error) { + console.error('Error updating tickets sold:', error); + } + }; + + // Fake email data + const emailData = { + to: 'findyourway88@example.com', + subject: 'Test Ticket Purchase Confirmation', + message: 'Dear customer, Your tickets have been successfully purchased.', + }; + + // Call sendEmail + sendEmail(emailData); + updateTicketsSold(); + }, [event]); -function PurchaseConfirmation() { return (

Thank you for your purchase!

Your tickets have been successfully purchased.

+

A confirmation email has been sent to your registered email address.

What would you like to do next?

diff --git a/src/components/NoPage.jsx b/src/components/NoPage.jsx index 09a14f7..ec9d454 100644 --- a/src/components/NoPage.jsx +++ b/src/components/NoPage.jsx @@ -1,13 +1,14 @@ -// NotFound.js import React from 'react'; -const NoPage = () => { +const NotFound = () => { return ( -
-

404 - Not Found

-

The page you are looking for does not exist.

+
+
+

404 - Not Found

+

The page you are looking for does not exist.

+
); } -export default NoPage; +export default NotFound; diff --git a/src/components/Purchase.jsx b/src/components/Purchase.jsx index 69a8062..2352a0f 100644 --- a/src/components/Purchase.jsx +++ b/src/components/Purchase.jsx @@ -1,6 +1,8 @@ import React, { useState, useEffect } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; -import PurchaseConfirmation from './ConfirmationPage'; // Import the PurchaseConfirmation component +import PurchaseConfirmation from './ConfirmationPage'; +import SignIn from './UserLogin'; + import Header from './Header'; import Footer from './Footer'; @@ -37,6 +39,14 @@ function Purchase() { }); }, [eventId]); + useEffect(() => { + // Check if the user is logged in + const jwt = localStorage.getItem('jwt'); + if (!jwt) { + navigate("/login"); // Redirect to login page if not logged in + } + }, [navigate]); + if (error) { return
Error: {error}
; } @@ -44,10 +54,14 @@ function Purchase() { if (!event) { return
Loading...
; } + const handleRegularPurchaseClick = () => { - const regularAvailable = event.data.attributes.ticketQuantityRegular - event.data.attributes.ticketsSoldRegular; + + + const regularAvailable = event.data.attributes.ticketQuantityRegular - event.data.attributes.ticketsSoldRegular; + if (regularQuantity <= 0) { setErrorMessage('Please enter a valid quantity.'); return; @@ -62,8 +76,11 @@ function Purchase() { }; const handleVIPPurchaseClick = () => { - const vipAvailable = event.data.attributes.ticketQuantityVIP - event.data.attributes.ticketSoldVIP; + + + const vipAvailable = event.data.attributes.ticketQuantityVIP - event.data.attributes.ticketSoldVIP; + if (vipQuantity <= 0) { setErrorMessage('Please enter a valid quantity.'); return; @@ -128,7 +145,7 @@ function Purchase() { onChange={(e) => setRegularQuantity(e.target.value)} placeholder="Enter quantity" /> - +

VIP Tickets

@@ -140,7 +157,7 @@ function Purchase() { onChange={(e) => setVIPQuantity(e.target.value)} placeholder="Enter quantity" /> - +
{showRegularDialog && (