diff --git a/app/__init__.py b/app/__init__.py index 6d2b047da24fe98ebc941b4c9aea32393ea0cd43..8cbfb643751a3a97067028c7d1476640f0fd4a38 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,4 +1,4 @@ -from flask import Flask, g, abort, current_app, request, session, redirect, url_for +from flask import Flask, g, abort, request, session, redirect, url_for from config import Config from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate @@ -10,9 +10,7 @@ from app.logger import auth_logger, error_logger from functools import wraps import os import pymysql -from sqlalchemy.sql import text -# Initialize extensions db = SQLAlchemy() migrate = Migrate() login_manager = LoginManager() @@ -20,15 +18,15 @@ csrf = CSRFProtect() principal = Principal() def permission_required(permission): - def decorator(f): + def check_role(f): @wraps(f) - def decorated_function(*args, **kwargs): + def check_permission(*args, **kwargs): if not permission.can(): auth_logger.debug(f'Permission denied for {current_user} attempting to access {request.endpoint}.') abort(403) return f(*args, **kwargs) - return decorated_function - return decorator + return check_permission + return check_role super_admin_permission = Permission(RoleNeed('super-admin')) admin_permission = Permission(RoleNeed('admin')) @@ -159,18 +157,18 @@ def create_app(config_class=Config): response.headers['X-Frame-Options'] = 'SAMEORIGIN' return response - # @app.errorhandler(Exception) - # def handle_exception(e): - # app.logger.error(f"Unhandled exception: {e}") - # session['error_message'] = str(e) - # return redirect(url_for('errors.quandary')) - - @app.errorhandler(403) + @app.errorhandler(Exception) def handle_exception(e): app.logger.error(f"Unhandled exception: {e}") session['error_message'] = str(e) return redirect(url_for('errors.quandary')) + @app.errorhandler(403) + def handle_exception(e): + app.logger.debug(f"Unauthorized: {e}") + session['error_message'] = str(e) + return redirect(url_for('errors.no_permission')) + @app.before_request def before_request(): g.admin_permission = None diff --git a/app/admin/routes.py b/app/admin/routes.py index 9ca30fe732cedf0ee6f03be714036484fe3eea38..fcef3b57c7b12e5f110a5c5572899ac1428c4316 100644 --- a/app/admin/routes.py +++ b/app/admin/routes.py @@ -1,11 +1,9 @@ from flask import render_template, redirect, url_for, request, jsonify, flash from app import db from app import admin_permission, permission_required, super_admin_permission -from app.models import Listings, ListingImages, User +from app.models import Listings, ListingImages, User, Role from app.admin import bp from app.main.utils import generate_time_options -from sqlalchemy.sql import text - @bp.route('/home') @permission_required(admin_permission) @@ -47,10 +45,11 @@ def edit_booking(id): @permission_required(super_admin_permission) def edit_user(id): user = User.search_user_id(id) - + roles = Role.get_all_roles() return render_template( 'admin/edit_user.html', - user=user + user=user, + roles=roles ) @bp.route('/manage_users') @@ -298,20 +297,34 @@ def delete_image(image_id): db.session.rollback() return jsonify({'success': False, 'error': str(e)}), 500 - -@bp.route('/init/database', methods=['GET']) -def check_database_exists(): - try: - if Listings.check_table_exists(): - flash ("Database already exists, 'error") +@permission_required(super_admin_permission) +@bp.route('/update_user_field', methods=['POST']) +def update_user_field(): + data = request.get_json() + user_id = data.get('user_id') + field = data.get('field') + value = data.get('value') + user = User.query.get(user_id) + + if user: + if field == 'userName': + user.username = value + elif field == 'userEmail': + user.email = value + elif field == 'userRole': + role = Role.query.get(value) + if role: + user.role_id = role.id + else: + return jsonify(success=False, message="Invalid role"), 400 else: - raise Exception('Schema exists but database does not') - except: - with open('sql-setup/init.sql', 'r') as file: - sql_commands = file.read().split(';') - for command in sql_commands: - if command.strip(): - db.session.execute(text(command)) - - db.session.commit() - flash ("Database initialised", 'success') + return jsonify(success=False, message="Invalid field"), 400 + + try: + db.session.commit() + return jsonify(success=True) + except Exception as e: + db.session.rollback() + return jsonify(success=False, message=str(e)), 500 + else: + return jsonify(success=False, message="User not found"), 404 \ No newline at end of file diff --git a/app/bookings/routes.py b/app/bookings/routes.py index d1629d4ce0a5c305d552390f9f5321f048272965..b8597755fd9f16bc05dc2c4be1fa65305fb2263d 100644 --- a/app/bookings/routes.py +++ b/app/bookings/routes.py @@ -386,7 +386,6 @@ def generate_ticket(id): return send_file(pdf, as_attachment=True, download_name='plane_ticket.pdf') - @bp.route('/get_user_bookings', methods=['GET']) @permission_required(user_permission) def get_user_bookings(): @@ -396,9 +395,11 @@ def get_user_bookings(): destination_location = request.args.get('destination_location') booking_date = request.args.get('booking_date') depart_date = request.args.get('depart_date') + exclude_cancelled = request.args.get('exclude_cancelled') + + if exclude_cancelled and exclude_cancelled.lower() == 'true': + query = query.filter(Bookings.cancelled == 0) - # Only get non-cancelled bookings - query = query.filter(Bookings.cancelled == 0) if depart_location: depart_locations = depart_location.split(',') query = query.filter(Listings.depart_location.in_(depart_locations)) @@ -418,8 +419,8 @@ def get_user_bookings(): 'booking_date': booking.booking_date.strftime("%a, %d %b %Y"), 'destination_location': booking.listing.destination_location, 'depart_date': booking.depart_date.strftime("%a, %d %b %Y"), + 'cancelled': 'Yes' if booking.cancelled else 'No' } for booking in filtered_data ] return jsonify(result) - diff --git a/app/errors/routes.py b/app/errors/routes.py index 3581bd0d52c884907176ff0cb22d7771402ce677..a918481d9c8e49745fc009c78e4505b743b313ad 100644 --- a/app/errors/routes.py +++ b/app/errors/routes.py @@ -3,7 +3,12 @@ from app.errors import bp @bp.route('/quandary') def quandary(): - error_message = 'Something unexpected occurred.' + error_message = 'Something went wrong, if this continues please contact support.' if 'error_message' in session: error_message = session.pop('error_message') return render_template("errors/quandary.html", error_message=error_message) + +@bp.route('/no_permission') +def no_permission(): + error_message = 'You do not have the required permission to view this page.' + return render_template("errors/quandary.html", error_message=error_message) diff --git a/app/main/routes.py b/app/main/routes.py index 14ec0e8fe689015cb4b816f940fe263a8ec0080f..4ff716adde4441120f9fa0e431772d5361cac069 100644 --- a/app/main/routes.py +++ b/app/main/routes.py @@ -52,3 +52,24 @@ def log_message(): return jsonify({'success': True, 'message': 'Log message recorded'}) except Exception as e: return jsonify({'success': False, 'error': str(e)}), 500 + + +@bp.route('/about_us') +def about_us(): + return render_template('main/about_us.html') + +@bp.route('/faq') +def faq(): + return render_template('main/faq.html') + +@bp.route('/privacy_policy') +def privacy_policy(): + return render_template('main/privacy_policy.html') + +@bp.route('/tos') +def tos(): + return render_template('main/tos.html') + +@bp.route('/contact_us') +def contact_us(): + return render_template('main/contact_us.html') \ No newline at end of file diff --git a/app/models/role.py b/app/models/role.py index 2b5f65b07612d05ed6b3a5f5e611570d296a9579..9495c715e759e02132146b16cc3ea317f3c4613e 100644 --- a/app/models/role.py +++ b/app/models/role.py @@ -8,3 +8,7 @@ class Role(RoleMixin, db.Model): name = db.Column(db.String(80), unique=True) description = db.Column(db.String(255)) + @classmethod + def get_all_roles(cls): + roles = cls.query.all() + return [{'id': role.id, 'name': role.name} for role in roles] \ No newline at end of file diff --git a/app/static/base.css b/app/static/base.css index 74a5bbfaa256f9ec80fe42d02f0d110c40083b7f..a7efb8f77a024081cd585dd499abf64702c5eff4 100644 --- a/app/static/base.css +++ b/app/static/base.css @@ -6,6 +6,15 @@ html, body { } /** FOOTER CSS */ +.footer_column a { + color: inherit; + text-decoration: none; + font-family: inherit; +} + +.footer_column a:hover { + text-decoration: underline; +} .footer { text-align: center; @@ -126,11 +135,6 @@ Form styling options color: #FBE9D0; } -.form-check-input:checked { - color: #FBE9D0; - background: #FBE9D0; -} - .form_header { text-align: center; text-transform: uppercase; diff --git a/app/templates/admin/edit_user.html b/app/templates/admin/edit_user.html index 52c83b35b79137290a915b8a388696a3ff9e56d4..c3260edd12c1c3928cb3fe1c1f0fa43898797e90 100644 --- a/app/templates/admin/edit_user.html +++ b/app/templates/admin/edit_user.html @@ -1,89 +1,147 @@ {% extends 'base.html' %} {% block content %} +<head> + <script src="{{ url_for('static', filename='generic.js') }}"></script> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"> +</head> <div class="container mt-5"> - <div class="card mb-4"> - <div class="card-body"> - <h2 class="card-title">User Information</h2> - <p><strong>User Name:</strong> - <span id="userName">{{user.username}}</span> - <button class="btn btn-primary" onclick="editField('userName', this)">Update</button> - </p> - <p><strong>Email:</strong> - <span id="userEmail">{{user.email}}</span> - <button class="btn btn-primary" onclick="editField('userEmail', this)">Update</button> - </p> - <p><strong>Role:</strong> - <span id="userRole">{{user.role.name}}</span> - <button class="btn btn-primary" onclick="editField('userRole', this)">Update</button> - </p> - </div> + <div class="card mb-4"> + <div class="card-body"> + <h2 class="card-title">User Information</h2> + <input type="hidden" id="userId" value="{{ user.id }}"> + <p><strong>User Name:</strong> <span id="userName">{{ user.username }}</span> + <button class="btn btn-primary" onclick="editField('userName', this)">Update</button> + </p> + <p><strong>Email:</strong> <span id="userEmail">{{ user.email }}</span> + <button class="btn btn-primary" onclick="editField('userEmail', this)">Update</button> + </p> + <p><strong>Role:</strong> <span id="userRole">{{ user.role.name }}</span> + <button class="btn btn-primary" onclick="editField('userRole', this)">Update</button> + </p> </div> + </div> </div> <div class="text-center"> - <button class="btn btn-secondary btn-lg"> - Save Changes - </button> + <a class="btn btn-warning btn-lg" href="{{ url_for('admin.manage_users') }}"> + <i class="fas fa-arrow-left"></i> Go Back + </a> </div> - <style> - .input-container { - display: flex; - align-items: center; - } - .input-container input { - flex: 1; - max-width: 500px; - } - .input-container button { - margin-left: 10px; - } + .input-container { + display: flex; + align-items: center; + } + .input-container input, .input-container select { + flex: 1; + max-width: 500px; + } + .input-container button { + margin-left: 10px; + } </style> - <script> -function editField(fieldId, button) { + function editField(fieldId, button) { const field = document.getElementById(fieldId); const originalValue = field.textContent; - const input = document.createElement("input"); - input.type = "text"; - input.value = originalValue; - input.classList.add("form-control"); - input.autofocus = true; + if (fieldId === 'userRole') { + const select = document.createElement("select"); + select.classList.add("form-control"); + const roles = JSON.parse('{{ roles|tojson|safe }}'); + const currentRoleId = '{{ user.role_id }}'; + roles.forEach(role => { + const option = document.createElement("option"); + option.value = role.id; + option.textContent = role.name; + if (role.id == currentRoleId) { + option.selected = true; + } + select.appendChild(option); + }); + const container = document.createElement("div"); + container.classList.add("input-container"); + container.appendChild(select); + field.parentNode.replaceChild(container, field); - const container = document.createElement("div"); - container.classList.add("input-container"); - container.appendChild(input); + select.onblur = function() { + if (!container.contains(select)) return; + saveField(select, fieldId, button); + }; + button.innerText = 'Apply'; + button.onclick = function() { + saveField(select, fieldId, button); + }; + container.appendChild(button); + } else { + const input = document.createElement("input"); + input.type = "text"; + input.value = originalValue; + input.classList.add("form-control"); + input.autofocus = true; - field.parentNode.replaceChild(container, field); + const container = document.createElement("div"); + container.classList.add("input-container"); + container.appendChild(input); + field.parentNode.replaceChild(container, field); - input.onblur = function() { + input.onblur = function() { if (!container.contains(input)) return; saveField(input, fieldId, button); - }; - - button.innerText = 'Apply'; - button.onclick = function() { + }; + button.innerText = 'Apply'; + button.onclick = function() { saveField(input, fieldId, button); - }; - - container.appendChild(button); -} - -function saveField(input, fieldId, button) -{ - const newValue = input.value; + }; + container.appendChild(button); + } + } + function saveField(input, fieldId, button) { + let newValue; + if (fieldId === 'userRole') { + newValue = input.value; // Get the selected role ID + var newSpanText = input.options[input.selectedIndex].text; // Get the selected role name + } else { + newValue = input.value; + var newSpanText = newValue; + } const newSpan = document.createElement("span"); newSpan.id = fieldId; - newSpan.textContent = newValue; - + newSpan.textContent = newSpanText; const container = input.parentNode; container.parentNode.replaceChild(newSpan, container); - button.innerText = 'Update'; button.onclick = editField.bind(null, fieldId, button); - newSpan.parentNode.appendChild(button); -} + // Send update to the Flask route for validation and saving + updateUserField(fieldId, newValue); + } + + function updateUserField(fieldId, newValue) { + const data = { + user_id: '{{ user.id }}', + field: fieldId, + value: newValue + }; + fetch("{{ url_for('admin.update_user_field') }}", { + method: "POST", + headers: { + "Content-Type": "application/json", + 'X-CSRFToken': '{{ csrf_token() }}' + }, + body: JSON.stringify(data) + }) + .then(response => response.json()) + .then(data => { + if (!data.success) { + alert('Error updating field: ' + data.message); + location.reload(); + } + }) + .catch(error => { + alert('Error updating field: ' + error); + location.reload(); + }); + } </script> {% endblock %} diff --git a/app/templates/base.html b/app/templates/base.html index d8ab657335f94f23ffeb6c26094f4aa419e11207..e3a1194adea1e5cf862628d6d65fd778b2047342 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -119,14 +119,15 @@ <div class="footer"> <div class="footer_row"> <div class="footer_column"> - <div>About Us</div> - <div>Contact Us</div> + <div><a href="{{ url_for('profile.index') }}">Profile</a></div> + <div><a href="{{ url_for('main.about_us') }}">About Us</a></div> + <div><a href="{{ url_for('main.contact_us') }}">Contact Us</div> </div> <div class="footer_column"> - <a>About Us</a> - <a>Contact Us</a> + <div><a href="{{ url_for('main.faq') }}">FAQ</a></div> + <div><a href="{{ url_for('main.privacy_policy') }}">Privacy Policy</a></div> + <div><a href="{{ url_for('main.tos') }}">Terms of Service</a></div> </div> - <div class="footer_column"><a>Login</a></div> </div> <div class="footer_row"> <div class="copyright"> @@ -134,7 +135,7 @@ </div> </div> </div> - </footer> + </footer> {% endblock %} </div> </body> diff --git a/app/templates/errors/quandary.html b/app/templates/errors/quandary.html index c699910b54fc50548d541b5d2a5051cb158140be..afa71393a9374ea052920fb06e81f87f707393ba 100644 --- a/app/templates/errors/quandary.html +++ b/app/templates/errors/quandary.html @@ -6,7 +6,7 @@ <div class="quandary-div"> <h1>Something went wrong</h1> <div class="container"> - <div><span>Something went wrong, if this continues please contact support</span></div> + <div><span>{{ error_message }}</span></div> <div class="button_2" onclick="history.back()"> <i class="fa-solid fa-circle-arrow-left"></i><span> Go back</span> </div> diff --git a/app/templates/main/about_us.html b/app/templates/main/about_us.html new file mode 100644 index 0000000000000000000000000000000000000000..c7129788a122566a571e649df49e02feb8cffed1 --- /dev/null +++ b/app/templates/main/about_us.html @@ -0,0 +1,23 @@ +{% extends 'base.html' %} +{% block content %} +<div class="container"> + <div class="jumbotron mt-5"> + <h1 class="display-4">About Us</h1> + <p class="lead">Learn more about Horizon Travels and our mission.</p> + <hr class="my-4"> + </div> + <div class="mt-4"> + <h2>Our Mission</h2> + <p>At Horizon Travels, our mission is to make travel planning seamless and enjoyable for everyone.</p> + + <h2>Our Story</h2> + <p>Founded in 2024, Horizon Travels was born out of a passion for exploration and a desire to make travel accessible to all. We've helped travelers find their perfect journey.</p> + + <h2>Our Team</h2> + <p>Our team is made up of dedicated travel enthusiasts who are here to assist you every step of the way. Whether you're planning a short trip or a long adventure, we're here to help.</p> + + <h2>Contact Us</h2> + <p>If you have any questions or need assistance, feel free to reach out to us.</p> + </div> +</div> +{% endblock %} diff --git a/app/templates/main/contact_us.html b/app/templates/main/contact_us.html new file mode 100644 index 0000000000000000000000000000000000000000..8a4de5ba9ba1eb667b4df8a0dc923af528a1a299 --- /dev/null +++ b/app/templates/main/contact_us.html @@ -0,0 +1,24 @@ +{% extends 'base.html' %} +{% block content %} +<div class="container"> + <div class="jumbotron mt-5"> + <h1 class="display-4">Contact Us</h1> + <p class="lead">We'd love to hear from you. Reach out to us with any questions or feedback.</p> + <hr class="my-4"> + </div> + <div class="mt-4"> + <h2>Email</h2> + <p>You can email us at <a href="mailto:support@horizontravels.com">support@horizontravels.com</a>.</p> + + <h2>Phone</h2> + <p>Feel free to call us at (123) 456-7890. Our support team is available 24/7 to assist with any queries.</p> + + <h2>Address</h2> + <p>Visit us at our office: + <br>123 Travel Street + <br>Bristol, BS1 1AA + <br>United Kingdom + </p> + </div> +</div> +{% endblock %} diff --git a/app/templates/main/faq.html b/app/templates/main/faq.html new file mode 100644 index 0000000000000000000000000000000000000000..003ffdd5324f42af6ce9adffc38bba7488036a75 --- /dev/null +++ b/app/templates/main/faq.html @@ -0,0 +1,26 @@ +{% extends 'base.html' %} +{% block content %} +<div class="container"> + <div class="jumbotron mt-5"> + <h1 class="display-4">Frequently Asked Questions</h1> + <p class="lead">Find answers to common questions about Horizon Travels.</p> + <hr class="my-4"> + </div> + <div class="mt-4"> + <h2>What services do you offer?</h2> + <p>We provide comprehensive travel planning services for flights.</p> + + <h2>How can I manage my bookings?</h2> + <p>You can manage your bookings by visiting the "Manage Bookings" section in your dashboard.</p> + + <h2>What is your cancellation policy?</h2> + <p>Our cancellation policy varies depending on the booking. Please reach out to our support team for more information.</p> + + <h2>How can I contact support?</h2> + <p>You can contact our support team via email at <a href="mailto:support@horizontravels.com">support@horizontravels.com</a> or by phone at (123) 456-7890.</p> + + <h2>Do you offer travel insurance?</h2> + <p>No, unfortunately at this time we do not offer insurance options.</p> + </div> +</div> +{% endblock %} diff --git a/app/templates/main/privacy_policy.html b/app/templates/main/privacy_policy.html new file mode 100644 index 0000000000000000000000000000000000000000..e8aca2c24761129f6cd2207912b28d3aa91452ec --- /dev/null +++ b/app/templates/main/privacy_policy.html @@ -0,0 +1,29 @@ +{% extends 'base.html' %} +{% block content %} +<div class="container"> + <div class="jumbotron mt-5"> + <h1 class="display-4">Privacy Policy</h1> + <p class="lead">Your privacy is important to us. Please read our policy carefully.</p> + <hr class="my-4"> + </div> + <div class="mt-4"> + <h2>1. Information Collection</h2> + <p>We collect personal information that you provide to us when you use our services.</p> + + <h2>2. Use of Information</h2> + <p>We use the information to provide and improve our services, communicate with you, and comply with legal obligations.</p> + + <h2>3. Information Sharing</h2> + <p>We do not share your personal information with third parties except as required by law.</p> + + <h2>4. Data Security</h2> + <p>We implement security measures to protect your information from unauthorized access and disclosure.</p> + + <h2>5. Your Rights</h2> + <p>You have the right to access, update, and delete your personal information. Contact us for any requests related to your data.</p> + + <h2>6. Changes to Policy</h2> + <p>We may update our privacy policy periodically. We will notify you of any changes by posting the updated policy on our site.</p> + </div> +</div> +{% endblock %} diff --git a/app/templates/main/tos.html b/app/templates/main/tos.html new file mode 100644 index 0000000000000000000000000000000000000000..d62699ed40ac1f888a581590fdcc80944548be14 --- /dev/null +++ b/app/templates/main/tos.html @@ -0,0 +1,29 @@ +{% extends 'base.html' %} +{% block content %} +<div class="container"> + <div class="jumbotron mt-5"> + <h1 class="display-4">Terms of Service</h1> + <p class="lead">Welcome to Horizon Travels. Please read these terms carefully.</p> + <hr class="my-4"> + </div> + <div class="mt-4"> + <h2>1. Acceptance of Terms</h2> + <p>By using our services, you agree to be bound by these terms.</p> + + <h2>2. Modification of Terms</h2> + <p>We reserve the right to change these terms at any time. We will notify users of any changes by posting the updated terms on our site.</p> + + <h2>3. User Responsibilities</h2> + <p>Users agree to use the site for lawful purposes and not to engage in any conduct that could harm the site or other users.</p> + + <h2>4. Limitation of Liability</h2> + <p>Horizon Travels is not liable for any damages that may occur as a result of using our services.</p> + + <h2>5. Privacy Policy</h2> + <p>Your privacy is important to us. Please review our privacy policy for more information.</p> + + <h2>6. Governing Law</h2> + <p>These terms are governed by the laws of the United Kingdom.</p> + </div> +</div> +{% endblock %} diff --git a/app/templates/profile/manage_bookings.html b/app/templates/profile/manage_bookings.html index 949b84ac32f4b7b276641e37cb2af327e50abd50..c2a0edc76a5cf68069dab3bc3560ec7be7d8e28e 100644 --- a/app/templates/profile/manage_bookings.html +++ b/app/templates/profile/manage_bookings.html @@ -19,6 +19,7 @@ <th>Departure Date</th> <th>Departure Location</th> <th>Destination Location</th> + <th>Cancelled</th> <th>Actions</th> </tr> </thead> @@ -30,6 +31,7 @@ <td>{{ booking.departure_date }}</td> <td>{{ booking.departure_location }}</td> <td>{{ booking.destination_location }}</td> + <td>{{ 'Yes' if booking.cancelled else 'No' }}</td> <td class="dt-center"> <div class="dropdown"> <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-bs-toggle="dropdown" aria-expanded="false"> @@ -92,7 +94,11 @@ <div class="mb-3"> <label for="booking_date" class="form-label">Booking Date:</label> <input type="date" class="form-control" id="booking_date" name="booking_date"> - </div> + </div> + <div class="mb-3"> + <label for="exclude_cancelled" class="form-label">Exclude Cancelled:</label> + <input type="checkbox" class="form-check-input" id="exclude_cancelled" name="exclude_cancelled"> + </div> </form> </div> <div class="modal-footer"> @@ -159,6 +165,7 @@ d.destination_location = $('#destination_location').val() ? $('#destination_location').val().join(',') : ''; d.depart_date = $('#depart_date').val(); d.booking_date = $('#booking_date').val(); + d.exclude_cancelled = $('#exclude_cancelled').is(':checked'); } }, columns: [ @@ -167,6 +174,7 @@ { data: 'depart_date' }, { data: 'depart_location' }, { data: 'destination_location' }, + { data: 'cancelled' }, { data: null, className: "dt-center", @@ -223,8 +231,6 @@ alert('An error occurred while cancelling the booking.'); } }); - } else { - alert("Please type 'CONFIRM' to proceed."); } }); }); diff --git a/app/templates/profile/view_booking.html b/app/templates/profile/view_booking.html index 23e9fe264efe7228ed65e417e3be6cdf2cc94ab8..65d71a8958194c356a876b023f3bdeaa3f73632f 100644 --- a/app/templates/profile/view_booking.html +++ b/app/templates/profile/view_booking.html @@ -29,14 +29,22 @@ <div class="card shadow-sm"> <div class="card-body text-center"> <h3 class="card-title mb-4">Re-Download Booking Details</h3> - <div class="d-flex justify-content-center"> - <form action="{{ url_for('bookings.generate_receipt', id=booking.id) }}" method="get" class="d-inline"> - <button type="submit" class="btn btn-success btn-lg mr-2" style="margin-right: 25px">Download Receipt</button> - </form> - <form action="{{ url_for('bookings.generate_ticket', id=booking.id) }}" method="get" class="d-inline"> - <button type="submit" class="btn btn-primary btn-lg">Download Plane Ticket</button> - </form> - </div> + {% if booking.cancelled %} + <div class="alert alert-danger" role="alert"> + This booking has been cancelled. Download options are no longer available + </div> + <button type="button" class="btn btn-success btn-lg mr-2" disabled>Download Receipt</button> + <button type="button" class="btn btn-primary btn-lg" disabled>Download Plane Ticket</button> + {% else %} + <div class="d-flex justify-content-center"> + <form action="{{ url_for('bookings.generate_receipt', id=booking.id) }}" method="get" class="d-inline"> + <button type="submit" class="btn btn-success btn-lg mr-2" style="margin-right: 25px">Download Receipt</button> + </form> + <form action="{{ url_for('bookings.generate_ticket', id=booking.id) }}" method="get" class="d-inline"> + <button type="submit" class="btn btn-primary btn-lg">Download Plane Ticket</button> + </form> + </div> + {% endif %} </div> </div> </div>