diff --git a/Backend/alembic/__pycache__/env.cpython-312.pyc b/Backend/alembic/__pycache__/env.cpython-312.pyc index d39f425a..7bea133b 100644 Binary files a/Backend/alembic/__pycache__/env.cpython-312.pyc and b/Backend/alembic/__pycache__/env.cpython-312.pyc differ diff --git a/Backend/alembic/versions/0e2dc5df18c3_add_privacy_terms_refunds_to_page_type_.py b/Backend/alembic/versions/0e2dc5df18c3_add_privacy_terms_refunds_to_page_type_.py new file mode 100644 index 00000000..42136132 --- /dev/null +++ b/Backend/alembic/versions/0e2dc5df18c3_add_privacy_terms_refunds_to_page_type_.py @@ -0,0 +1,36 @@ +"""add_privacy_terms_refunds_to_page_type_enum + +Revision ID: 0e2dc5df18c3 +Revises: f2a3b4c5d6e7 +Create Date: 2025-11-21 10:25:07.463477 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '0e2dc5df18c3' +down_revision = 'f2a3b4c5d6e7' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # For MySQL/MariaDB, we need to alter the enum column to add new values + # First, modify the column to allow the new enum values + op.execute(""" + ALTER TABLE page_contents + MODIFY COLUMN page_type ENUM('home', 'contact', 'about', 'footer', 'seo', 'privacy', 'terms', 'refunds') + NOT NULL + """) + + +def downgrade() -> None: + # Remove the new enum values + op.execute(""" + ALTER TABLE page_contents + MODIFY COLUMN page_type ENUM('home', 'contact', 'about', 'footer', 'seo') + NOT NULL + """) + diff --git a/Backend/alembic/versions/9bb08492a382_add_cancellation_accessibility_faq_to_.py b/Backend/alembic/versions/9bb08492a382_add_cancellation_accessibility_faq_to_.py new file mode 100644 index 00000000..70dd4b72 --- /dev/null +++ b/Backend/alembic/versions/9bb08492a382_add_cancellation_accessibility_faq_to_.py @@ -0,0 +1,35 @@ +"""add_cancellation_accessibility_faq_to_page_type_enum + +Revision ID: 9bb08492a382 +Revises: 0e2dc5df18c3 +Create Date: 2025-11-21 10:39:56.040401 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '9bb08492a382' +down_revision = '0e2dc5df18c3' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # Add new enum values: cancellation, accessibility, faq + op.execute(""" + ALTER TABLE page_contents + MODIFY COLUMN page_type ENUM('home', 'contact', 'about', 'footer', 'seo', 'privacy', 'terms', 'refunds', 'cancellation', 'accessibility', 'faq') + NOT NULL + """) + + +def downgrade() -> None: + # Remove the new enum values + op.execute(""" + ALTER TABLE page_contents + MODIFY COLUMN page_type ENUM('home', 'contact', 'about', 'footer', 'seo', 'privacy', 'terms', 'refunds') + NOT NULL + """) + diff --git a/Backend/alembic/versions/__pycache__/08e2f866e131_add_mfa_fields_to_users.cpython-312.pyc b/Backend/alembic/versions/__pycache__/08e2f866e131_add_mfa_fields_to_users.cpython-312.pyc index be1ff3d8..cc807098 100644 Binary files a/Backend/alembic/versions/__pycache__/08e2f866e131_add_mfa_fields_to_users.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/08e2f866e131_add_mfa_fields_to_users.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/0e2dc5df18c3_add_privacy_terms_refunds_to_page_type_.cpython-312.pyc b/Backend/alembic/versions/__pycache__/0e2dc5df18c3_add_privacy_terms_refunds_to_page_type_.cpython-312.pyc new file mode 100644 index 00000000..cd5b9465 Binary files /dev/null and b/Backend/alembic/versions/__pycache__/0e2dc5df18c3_add_privacy_terms_refunds_to_page_type_.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/1444eb61188e_add_section_title_fields_to_page_content.cpython-312.pyc b/Backend/alembic/versions/__pycache__/1444eb61188e_add_section_title_fields_to_page_content.cpython-312.pyc index de4db895..0fa0dbbe 100644 Binary files a/Backend/alembic/versions/__pycache__/1444eb61188e_add_section_title_fields_to_page_content.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/1444eb61188e_add_section_title_fields_to_page_content.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/163657e72e93_add_page_content_table.cpython-312.pyc b/Backend/alembic/versions/__pycache__/163657e72e93_add_page_content_table.cpython-312.pyc index d09e68da..953fc19d 100644 Binary files a/Backend/alembic/versions/__pycache__/163657e72e93_add_page_content_table.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/163657e72e93_add_page_content_table.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/17efc6439cc3_add_luxury_section_fields_to_page_.cpython-312.pyc b/Backend/alembic/versions/__pycache__/17efc6439cc3_add_luxury_section_fields_to_page_.cpython-312.pyc index 9bde4caf..a0fdd478 100644 Binary files a/Backend/alembic/versions/__pycache__/17efc6439cc3_add_luxury_section_fields_to_page_.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/17efc6439cc3_add_luxury_section_fields_to_page_.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/59baf2338f8a_initial_migration_create_all_tables_.cpython-312.pyc b/Backend/alembic/versions/__pycache__/59baf2338f8a_initial_migration_create_all_tables_.cpython-312.pyc index 8852a425..c07faa24 100644 Binary files a/Backend/alembic/versions/__pycache__/59baf2338f8a_initial_migration_create_all_tables_.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/59baf2338f8a_initial_migration_create_all_tables_.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/6a126cc5b23c_add_capacity_room_size_view_to_rooms.cpython-312.pyc b/Backend/alembic/versions/__pycache__/6a126cc5b23c_add_capacity_room_size_view_to_rooms.cpython-312.pyc index 30060d3e..fadc4c16 100644 Binary files a/Backend/alembic/versions/__pycache__/6a126cc5b23c_add_capacity_room_size_view_to_rooms.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/6a126cc5b23c_add_capacity_room_size_view_to_rooms.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/96c23dad405d_add_system_settings_table.cpython-312.pyc b/Backend/alembic/versions/__pycache__/96c23dad405d_add_system_settings_table.cpython-312.pyc index e79eea68..caae0829 100644 Binary files a/Backend/alembic/versions/__pycache__/96c23dad405d_add_system_settings_table.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/96c23dad405d_add_system_settings_table.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/9bb08492a382_add_cancellation_accessibility_faq_to_.cpython-312.pyc b/Backend/alembic/versions/__pycache__/9bb08492a382_add_cancellation_accessibility_faq_to_.cpython-312.pyc new file mode 100644 index 00000000..ad15f7c7 Binary files /dev/null and b/Backend/alembic/versions/__pycache__/9bb08492a382_add_cancellation_accessibility_faq_to_.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/add_about_page_fields.cpython-312.pyc b/Backend/alembic/versions/__pycache__/add_about_page_fields.cpython-312.pyc index cc32c87c..02eb0391 100644 Binary files a/Backend/alembic/versions/__pycache__/add_about_page_fields.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/add_about_page_fields.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/add_badges_to_page_content.cpython-312.pyc b/Backend/alembic/versions/__pycache__/add_badges_to_page_content.cpython-312.pyc index 1c38df1e..8f8747bf 100644 Binary files a/Backend/alembic/versions/__pycache__/add_badges_to_page_content.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/add_badges_to_page_content.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/add_copyright_text_to_page_content.cpython-312.pyc b/Backend/alembic/versions/__pycache__/add_copyright_text_to_page_content.cpython-312.pyc index 16bb2ee4..8f3675ee 100644 Binary files a/Backend/alembic/versions/__pycache__/add_copyright_text_to_page_content.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/add_copyright_text_to_page_content.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/add_stripe_payment_method.cpython-312.pyc b/Backend/alembic/versions/__pycache__/add_stripe_payment_method.cpython-312.pyc index 30dfef5e..1df86a45 100644 Binary files a/Backend/alembic/versions/__pycache__/add_stripe_payment_method.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/add_stripe_payment_method.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/bd309b0742c1_add_promotion_fields_to_bookings.cpython-312.pyc b/Backend/alembic/versions/__pycache__/bd309b0742c1_add_promotion_fields_to_bookings.cpython-312.pyc index 6981abb1..61dc1af8 100644 Binary files a/Backend/alembic/versions/__pycache__/bd309b0742c1_add_promotion_fields_to_bookings.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/bd309b0742c1_add_promotion_fields_to_bookings.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/bfa74be4b256_add_luxury_content_fields_to_page_.cpython-312.pyc b/Backend/alembic/versions/__pycache__/bfa74be4b256_add_luxury_content_fields_to_page_.cpython-312.pyc index c11c964e..91f3f8ae 100644 Binary files a/Backend/alembic/versions/__pycache__/bfa74be4b256_add_luxury_content_fields_to_page_.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/bfa74be4b256_add_luxury_content_fields_to_page_.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/cce764ef7a50_add_map_url_to_page_content.cpython-312.pyc b/Backend/alembic/versions/__pycache__/cce764ef7a50_add_map_url_to_page_content.cpython-312.pyc index 0f094e2d..b0c5e145 100644 Binary files a/Backend/alembic/versions/__pycache__/cce764ef7a50_add_map_url_to_page_content.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/cce764ef7a50_add_map_url_to_page_content.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/d9aff6c5f0d4_add_paypal_payment_method.cpython-312.pyc b/Backend/alembic/versions/__pycache__/d9aff6c5f0d4_add_paypal_payment_method.cpython-312.pyc index a27ee628..624886ba 100644 Binary files a/Backend/alembic/versions/__pycache__/d9aff6c5f0d4_add_paypal_payment_method.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/d9aff6c5f0d4_add_paypal_payment_method.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/f1a2b3c4d5e6_add_is_proforma_to_invoices.cpython-312.pyc b/Backend/alembic/versions/__pycache__/f1a2b3c4d5e6_add_is_proforma_to_invoices.cpython-312.pyc index a7a4de87..e76ed124 100644 Binary files a/Backend/alembic/versions/__pycache__/f1a2b3c4d5e6_add_is_proforma_to_invoices.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/f1a2b3c4d5e6_add_is_proforma_to_invoices.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/ff515d77abbe_add_more_luxury_sections_to_page_content.cpython-312.pyc b/Backend/alembic/versions/__pycache__/ff515d77abbe_add_more_luxury_sections_to_page_content.cpython-312.pyc index 4f9e448f..3b8274e4 100644 Binary files a/Backend/alembic/versions/__pycache__/ff515d77abbe_add_more_luxury_sections_to_page_content.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/ff515d77abbe_add_more_luxury_sections_to_page_content.cpython-312.pyc differ diff --git a/Backend/seed_policy_pages.py b/Backend/seed_policy_pages.py new file mode 100755 index 00000000..0f6f4a93 --- /dev/null +++ b/Backend/seed_policy_pages.py @@ -0,0 +1,517 @@ +#!/usr/bin/env python3 + +import sys +import os +from pathlib import Path +import json + +sys.path.insert(0, str(Path(__file__).parent)) + +from sqlalchemy.orm import Session +from src.config.database import SessionLocal +from src.models.page_content import PageContent, PageType +from datetime import datetime + +def get_db(): + db = SessionLocal() + try: + return db + finally: + pass + +def seed_privacy_policy(db: Session): + print("=" * 80) + print("SEEDING PRIVACY POLICY PAGE CONTENT") + print("=" * 80) + + privacy_content = """ +
At our hotel, we are committed to protecting your privacy and ensuring the security of your personal information. This Privacy Policy explains how we collect, use, disclose, and safeguard your information when you visit our website or use our services.
+ +We collect information that you provide directly to us, including:
+We use the information we collect to:
+We implement appropriate technical and organizational measures to protect your personal information against unauthorized access, alteration, disclosure, or destruction.
+ +You have the right to:
+If you have any questions about this Privacy Policy, please contact us at privacy@hotel.com.
+ +Last updated: """ + datetime.now().strftime("%B %d, %Y") + """
+ """ + + privacy_data = { + "title": "Privacy Policy", + "subtitle": "Your privacy is important to us", + "description": "Learn how we collect, use, and protect your personal information.", + "content": privacy_content, + "meta_title": "Privacy Policy - Luxury Hotel | Data Protection & Privacy", + "meta_description": "Read our privacy policy to understand how we collect, use, and protect your personal information when you use our hotel booking services." + } + + existing = db.query(PageContent).filter(PageContent.page_type == PageType.PRIVACY).first() + + if existing: + for key, value in privacy_data.items(): + setattr(existing, key, value) + existing.updated_at = datetime.utcnow() + print("✓ Updated existing privacy policy page content") + else: + new_content = PageContent( + page_type=PageType.PRIVACY, + **privacy_data + ) + db.add(new_content) + print("✓ Created new privacy policy page content") + + db.commit() + print("\n✅ Privacy policy page content seeded successfully!") + print("=" * 80) + +def seed_terms_conditions(db: Session): + print("=" * 80) + print("SEEDING TERMS & CONDITIONS PAGE CONTENT") + print("=" * 80) + + terms_content = """ +By accessing and using our hotel's website and services, you accept and agree to be bound by the terms and provision of this agreement.
+ +When making a reservation with us, you agree to:
+Payment terms include:
+Our cancellation policy is as follows:
+Standard check-in time is 3:00 PM and check-out time is 11:00 AM. Early check-in and late check-out may be available upon request and subject to availability.
+ +Guests are responsible for:
+The hotel shall not be liable for any loss, damage, or injury to persons or property during your stay, except where such loss, damage, or injury is caused by our negligence.
+ +We reserve the right to modify these terms at any time. Changes will be effective immediately upon posting on our website.
+ +For questions about these terms, please contact us at legal@hotel.com.
+ +Last updated: """ + datetime.now().strftime("%B %d, %Y") + """
+ """ + + terms_data = { + "title": "Terms & Conditions", + "subtitle": "Please read these terms carefully", + "description": "Terms and conditions governing your use of our hotel booking services.", + "content": terms_content, + "meta_title": "Terms & Conditions - Luxury Hotel | Booking Terms & Policies", + "meta_description": "Read our terms and conditions to understand the rules and policies governing your bookings and stay at our luxury hotel." + } + + existing = db.query(PageContent).filter(PageContent.page_type == PageType.TERMS).first() + + if existing: + for key, value in terms_data.items(): + setattr(existing, key, value) + existing.updated_at = datetime.utcnow() + print("✓ Updated existing terms & conditions page content") + else: + new_content = PageContent( + page_type=PageType.TERMS, + **terms_data + ) + db.add(new_content) + print("✓ Created new terms & conditions page content") + + db.commit() + print("\n✅ Terms & conditions page content seeded successfully!") + print("=" * 80) + +def seed_refunds_policy(db: Session): + print("=" * 80) + print("SEEDING REFUNDS POLICY PAGE CONTENT") + print("=" * 80) + + refunds_content = """ +At our hotel, we understand that plans can change. This policy outlines our refund procedures and timelines for various scenarios.
+ +Refunds for cancelled bookings are processed as follows:
+If you check out earlier than your reserved departure date:
+If you experience service issues during your stay:
+Refunds are typically processed within:
+Refunds will be issued to the original payment method used for the booking. If the original payment method is no longer available, please contact us to arrange an alternative refund method.
+ +Some special offers or promotional rates may be non-refundable. This will be clearly stated at the time of booking.
+ +In cases of force majeure (natural disasters, pandemics, government restrictions, etc.), we will work with you to reschedule your booking or provide appropriate refunds based on the circumstances.
+ +If you have concerns about a refund decision, please contact our customer service team. We are committed to resolving all disputes fairly and promptly.
+ +For refund inquiries, please contact us at refunds@hotel.com or call our customer service line.
+ +Last updated: """ + datetime.now().strftime("%B %d, %Y") + """
+ """ + + refunds_data = { + "title": "Refunds Policy", + "subtitle": "Our commitment to fair refunds", + "description": "Learn about our refund policies and procedures for bookings and cancellations.", + "content": refunds_content, + "meta_title": "Refunds Policy - Luxury Hotel | Cancellation & Refund Terms", + "meta_description": "Understand our refund policy, including cancellation terms, processing times, and how to request refunds for your hotel bookings." + } + + existing = db.query(PageContent).filter(PageContent.page_type == PageType.REFUNDS).first() + + if existing: + for key, value in refunds_data.items(): + setattr(existing, key, value) + existing.updated_at = datetime.utcnow() + print("✓ Updated existing refunds policy page content") + else: + new_content = PageContent( + page_type=PageType.REFUNDS, + **refunds_data + ) + db.add(new_content) + print("✓ Created new refunds policy page content") + + db.commit() + print("\n✅ Refunds policy page content seeded successfully!") + print("=" * 80) + +def seed_cancellation_policy(db: Session): + print("=" * 80) + print("SEEDING CANCELLATION POLICY PAGE CONTENT") + print("=" * 80) + + cancellation_content = """ +We understand that plans can change. This policy outlines our cancellation procedures and fees.
+ +For standard bookings, the following cancellation terms apply:
+Some special rates or promotional offers may have different cancellation terms. Please review your booking confirmation for specific details.
+ +To cancel your booking:
+Refunds will be processed to the original payment method within 5-10 business days after cancellation confirmation.
+ +Last updated: """ + datetime.now().strftime("%B %d, %Y") + """
+ """ + + cancellation_data = { + "title": "Cancellation Policy", + "subtitle": "Flexible cancellation options for your peace of mind", + "description": "Learn about our cancellation policy, fees, and refund procedures.", + "content": cancellation_content, + "meta_title": "Cancellation Policy - Luxury Hotel | Booking Cancellation Terms", + "meta_description": "Review our cancellation policy to understand cancellation fees, refund procedures, and terms for modifying or canceling your hotel booking." + } + + existing = db.query(PageContent).filter(PageContent.page_type == PageType.CANCELLATION).first() + + if existing: + for key, value in cancellation_data.items(): + setattr(existing, key, value) + existing.updated_at = datetime.utcnow() + print("✓ Updated existing cancellation policy page content") + else: + new_content = PageContent( + page_type=PageType.CANCELLATION, + **cancellation_data + ) + db.add(new_content) + print("✓ Created new cancellation policy page content") + + db.commit() + print("\n✅ Cancellation policy page content seeded successfully!") + print("=" * 80) + +def seed_accessibility_policy(db: Session): + print("=" * 80) + print("SEEDING ACCESSIBILITY PAGE CONTENT") + print("=" * 80) + + accessibility_content = """ +We are committed to ensuring that our hotel and website are accessible to all guests, regardless of ability. We strive to provide an inclusive experience for everyone.
+ +Our hotel offers the following accessibility features:
+We are continuously working to improve the accessibility of our website. Our website includes:
+If you require specific accommodations during your stay, please contact us at least 48 hours before your arrival. We will do our best to accommodate your needs.
+ +We welcome feedback on how we can improve accessibility. Please contact us with your suggestions or concerns.
+ +Last updated: """ + datetime.now().strftime("%B %d, %Y") + """
+ """ + + accessibility_data = { + "title": "Accessibility", + "subtitle": "Committed to providing an inclusive experience for all guests", + "description": "Learn about our accessibility features and accommodations.", + "content": accessibility_content, + "meta_title": "Accessibility - Luxury Hotel | Accessible Accommodations", + "meta_description": "Discover our commitment to accessibility, including accessible rooms, facilities, and website features for guests with disabilities." + } + + existing = db.query(PageContent).filter(PageContent.page_type == PageType.ACCESSIBILITY).first() + + if existing: + for key, value in accessibility_data.items(): + setattr(existing, key, value) + existing.updated_at = datetime.utcnow() + print("✓ Updated existing accessibility page content") + else: + new_content = PageContent( + page_type=PageType.ACCESSIBILITY, + **accessibility_data + ) + db.add(new_content) + print("✓ Created new accessibility page content") + + db.commit() + print("\n✅ Accessibility page content seeded successfully!") + print("=" * 80) + +def seed_faq_page(db: Session): + print("=" * 80) + print("SEEDING FAQ PAGE CONTENT") + print("=" * 80) + + faq_content = """ +Find answers to common questions about our hotel, bookings, and services.
+ +You can make a reservation online through our website, by phone, or by email. Simply select your dates, choose your room, and complete the booking process.
+ +A 20% deposit is required to secure your booking. The remaining balance is due upon arrival at the hotel.
+ +Yes, you can modify your booking by logging into your account and visiting "My Bookings", or by contacting us directly. Changes are subject to availability and may incur fees.
+ +Check-in is from 3:00 PM, and check-out is by 11:00 AM. Early check-in and late check-out may be available upon request, subject to availability.
+ +Early check-in and late check-out are available upon request, subject to availability. Additional fees may apply.
+ +We accept major credit cards, debit cards, and bank transfers. Payment can be made online or at the hotel.
+ +For cancellations made more than 48 hours before check-in, the deposit is fully refundable. Cancellations made 48 hours or less before check-in are non-refundable. Please see our Cancellation Policy page for full details.
+ +Our hotel offers complimentary Wi-Fi, parking, fitness center access, and more. Please check the room details for specific amenities.
+ +Yes, we offer complimentary parking for all guests. Valet parking is also available for an additional fee.
+ +Yes, complimentary high-speed Wi-Fi is available throughout the hotel.
+ +Yes, you can make special requests when booking. We will do our best to accommodate your preferences, subject to availability.
+ +Yes, please inform us of any dietary restrictions or allergies when making your reservation, and we will do our best to accommodate your needs.
+ +You can contact us by phone, email, or through our website's contact form. Our team is available 24/7 to assist you.
+ +Last updated: """ + datetime.now().strftime("%B %d, %Y") + """
+ """ + + faq_data = { + "title": "Frequently Asked Questions", + "subtitle": "Find answers to common questions", + "description": "Get answers to frequently asked questions about bookings, services, and policies.", + "content": faq_content, + "meta_title": "FAQ - Luxury Hotel | Frequently Asked Questions", + "meta_description": "Find answers to common questions about hotel bookings, check-in, payment, cancellation, amenities, and more." + } + + existing = db.query(PageContent).filter(PageContent.page_type == PageType.FAQ).first() + + if existing: + for key, value in faq_data.items(): + setattr(existing, key, value) + existing.updated_at = datetime.utcnow() + print("✓ Updated existing FAQ page content") + else: + new_content = PageContent( + page_type=PageType.FAQ, + **faq_data + ) + db.add(new_content) + print("✓ Created new FAQ page content") + + db.commit() + print("\n✅ FAQ page content seeded successfully!") + print("=" * 80) + +def main(): + db = get_db() + try: + print("\n") + seed_privacy_policy(db) + print("\n") + seed_terms_conditions(db) + print("\n") + seed_refunds_policy(db) + print("\n") + seed_cancellation_policy(db) + print("\n") + seed_accessibility_policy(db) + print("\n") + seed_faq_page(db) + print("\n") + print("=" * 80) + print("✅ ALL POLICY PAGES SEEDED SUCCESSFULLY!") + print("=" * 80) + print("\n") + except Exception as e: + print(f"\n❌ Error: {e}") + import traceback + traceback.print_exc() + db.rollback() + finally: + db.close() + +if __name__ == "__main__": + main() + diff --git a/Backend/src/__pycache__/main.cpython-312.pyc b/Backend/src/__pycache__/main.cpython-312.pyc index 4f965054..5b261f5c 100644 Binary files a/Backend/src/__pycache__/main.cpython-312.pyc and b/Backend/src/__pycache__/main.cpython-312.pyc differ diff --git a/Backend/src/main.py b/Backend/src/main.py index 23ee2837..18a043f5 100644 --- a/Backend/src/main.py +++ b/Backend/src/main.py @@ -91,10 +91,8 @@ async def health_check(db: Session=Depends(get_db)): async def metrics(): return {'status': 'success', 'service': settings.APP_NAME, 'version': settings.APP_VERSION, 'environment': settings.ENVIRONMENT, 'timestamp': datetime.utcnow().isoformat()} app.include_router(auth_routes.router, prefix='/api') -app.include_router(privacy_routes.router, prefix='/api') app.include_router(auth_routes.router, prefix=settings.API_V1_PREFIX) -app.include_router(privacy_routes.router, prefix=settings.API_V1_PREFIX) -from .routes import room_routes, booking_routes, payment_routes, invoice_routes, banner_routes, favorite_routes, service_routes, service_booking_routes, promotion_routes, report_routes, review_routes, user_routes, audit_routes, admin_privacy_routes, system_settings_routes, contact_routes, page_content_routes, home_routes, about_routes, contact_content_routes, footer_routes, chat_routes +from .routes import room_routes, booking_routes, payment_routes, invoice_routes, banner_routes, favorite_routes, service_routes, service_booking_routes, promotion_routes, report_routes, review_routes, user_routes, audit_routes, admin_privacy_routes, system_settings_routes, contact_routes, page_content_routes, home_routes, about_routes, contact_content_routes, footer_routes, chat_routes, privacy_routes, terms_routes, refunds_routes, cancellation_routes, accessibility_routes, faq_routes app.include_router(room_routes.router, prefix='/api') app.include_router(booking_routes.router, prefix='/api') app.include_router(payment_routes.router, prefix='/api') @@ -115,6 +113,12 @@ app.include_router(home_routes.router, prefix='/api') app.include_router(about_routes.router, prefix='/api') app.include_router(contact_content_routes.router, prefix='/api') app.include_router(footer_routes.router, prefix='/api') +app.include_router(privacy_routes.router, prefix='/api') +app.include_router(terms_routes.router, prefix='/api') +app.include_router(refunds_routes.router, prefix='/api') +app.include_router(cancellation_routes.router, prefix='/api') +app.include_router(accessibility_routes.router, prefix='/api') +app.include_router(faq_routes.router, prefix='/api') app.include_router(chat_routes.router, prefix='/api') app.include_router(room_routes.router, prefix=settings.API_V1_PREFIX) app.include_router(booking_routes.router, prefix=settings.API_V1_PREFIX) @@ -136,6 +140,12 @@ app.include_router(home_routes.router, prefix=settings.API_V1_PREFIX) app.include_router(about_routes.router, prefix=settings.API_V1_PREFIX) app.include_router(contact_content_routes.router, prefix=settings.API_V1_PREFIX) app.include_router(footer_routes.router, prefix=settings.API_V1_PREFIX) +app.include_router(privacy_routes.router, prefix=settings.API_V1_PREFIX) +app.include_router(terms_routes.router, prefix=settings.API_V1_PREFIX) +app.include_router(refunds_routes.router, prefix=settings.API_V1_PREFIX) +app.include_router(cancellation_routes.router, prefix=settings.API_V1_PREFIX) +app.include_router(accessibility_routes.router, prefix=settings.API_V1_PREFIX) +app.include_router(faq_routes.router, prefix=settings.API_V1_PREFIX) app.include_router(chat_routes.router, prefix=settings.API_V1_PREFIX) app.include_router(page_content_routes.router, prefix='/api') app.include_router(page_content_routes.router, prefix=settings.API_V1_PREFIX) diff --git a/Backend/src/models/__pycache__/page_content.cpython-312.pyc b/Backend/src/models/__pycache__/page_content.cpython-312.pyc index 80f81e00..7424f90b 100644 Binary files a/Backend/src/models/__pycache__/page_content.cpython-312.pyc and b/Backend/src/models/__pycache__/page_content.cpython-312.pyc differ diff --git a/Backend/src/models/page_content.py b/Backend/src/models/page_content.py index e093ac6b..7fc18016 100644 --- a/Backend/src/models/page_content.py +++ b/Backend/src/models/page_content.py @@ -10,11 +10,17 @@ class PageType(str, enum.Enum): ABOUT = 'about' FOOTER = 'footer' SEO = 'seo' + PRIVACY = 'privacy' + TERMS = 'terms' + REFUNDS = 'refunds' + CANCELLATION = 'cancellation' + ACCESSIBILITY = 'accessibility' + FAQ = 'faq' class PageContent(Base): __tablename__ = 'page_contents' id = Column(Integer, primary_key=True, index=True) - page_type = Column(SQLEnum(PageType), nullable=False, unique=True, index=True) + page_type = Column(SQLEnum(PageType, values_callable=lambda x: [e.value for e in x], native_enum=False, length=50), nullable=False, unique=True, index=True) title = Column(String(500), nullable=True) subtitle = Column(String(1000), nullable=True) description = Column(Text, nullable=True) diff --git a/Backend/src/routes/__pycache__/accessibility_routes.cpython-312.pyc b/Backend/src/routes/__pycache__/accessibility_routes.cpython-312.pyc new file mode 100644 index 00000000..b400e32f Binary files /dev/null and b/Backend/src/routes/__pycache__/accessibility_routes.cpython-312.pyc differ diff --git a/Backend/src/routes/__pycache__/cancellation_routes.cpython-312.pyc b/Backend/src/routes/__pycache__/cancellation_routes.cpython-312.pyc new file mode 100644 index 00000000..01b707ee Binary files /dev/null and b/Backend/src/routes/__pycache__/cancellation_routes.cpython-312.pyc differ diff --git a/Backend/src/routes/__pycache__/faq_routes.cpython-312.pyc b/Backend/src/routes/__pycache__/faq_routes.cpython-312.pyc new file mode 100644 index 00000000..470c55a2 Binary files /dev/null and b/Backend/src/routes/__pycache__/faq_routes.cpython-312.pyc differ diff --git a/Backend/src/routes/__pycache__/privacy_routes.cpython-312.pyc b/Backend/src/routes/__pycache__/privacy_routes.cpython-312.pyc index 5a851c3c..e57f8ea2 100644 Binary files a/Backend/src/routes/__pycache__/privacy_routes.cpython-312.pyc and b/Backend/src/routes/__pycache__/privacy_routes.cpython-312.pyc differ diff --git a/Backend/src/routes/__pycache__/refunds_routes.cpython-312.pyc b/Backend/src/routes/__pycache__/refunds_routes.cpython-312.pyc new file mode 100644 index 00000000..3f7f7d99 Binary files /dev/null and b/Backend/src/routes/__pycache__/refunds_routes.cpython-312.pyc differ diff --git a/Backend/src/routes/__pycache__/terms_routes.cpython-312.pyc b/Backend/src/routes/__pycache__/terms_routes.cpython-312.pyc new file mode 100644 index 00000000..a7756169 Binary files /dev/null and b/Backend/src/routes/__pycache__/terms_routes.cpython-312.pyc differ diff --git a/Backend/src/routes/accessibility_routes.py b/Backend/src/routes/accessibility_routes.py new file mode 100644 index 00000000..ecaeeef3 --- /dev/null +++ b/Backend/src/routes/accessibility_routes.py @@ -0,0 +1,44 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.orm import Session +from ..config.database import get_db +from ..config.logging_config import get_logger +from ..models.page_content import PageContent, PageType +logger = get_logger(__name__) +router = APIRouter(prefix='/accessibility', tags=['accessibility']) + +def serialize_page_content(content: PageContent) -> dict: + return { + 'id': content.id, + 'page_type': content.page_type.value, + 'title': content.title, + 'subtitle': content.subtitle, + 'description': content.description, + 'content': content.content, + 'meta_title': content.meta_title, + 'meta_description': content.meta_description, + 'meta_keywords': content.meta_keywords, + 'og_title': content.og_title, + 'og_description': content.og_description, + 'og_image': content.og_image, + 'canonical_url': content.canonical_url, + 'is_active': content.is_active, + 'created_at': content.created_at.isoformat() if content.created_at else None, + 'updated_at': content.updated_at.isoformat() if content.updated_at else None + } + +@router.get('/') +async def get_accessibility_content(db: Session=Depends(get_db)): + try: + content = db.query(PageContent).filter(PageContent.page_type == PageType.ACCESSIBILITY).first() + if not content: + return {'status': 'success', 'data': {'page_content': None, 'is_active': False}} + if not content.is_active: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Accessibility page is currently disabled') + content_dict = serialize_page_content(content) + return {'status': 'success', 'data': {'page_content': content_dict}} + except HTTPException: + raise + except Exception as e: + logger.error(f'Error fetching accessibility content: {str(e)}', exc_info=True) + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f'Error fetching accessibility content: {str(e)}') + diff --git a/Backend/src/routes/cancellation_routes.py b/Backend/src/routes/cancellation_routes.py new file mode 100644 index 00000000..816b4940 --- /dev/null +++ b/Backend/src/routes/cancellation_routes.py @@ -0,0 +1,44 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.orm import Session +from ..config.database import get_db +from ..config.logging_config import get_logger +from ..models.page_content import PageContent, PageType +logger = get_logger(__name__) +router = APIRouter(prefix='/cancellation', tags=['cancellation']) + +def serialize_page_content(content: PageContent) -> dict: + return { + 'id': content.id, + 'page_type': content.page_type.value, + 'title': content.title, + 'subtitle': content.subtitle, + 'description': content.description, + 'content': content.content, + 'meta_title': content.meta_title, + 'meta_description': content.meta_description, + 'meta_keywords': content.meta_keywords, + 'og_title': content.og_title, + 'og_description': content.og_description, + 'og_image': content.og_image, + 'canonical_url': content.canonical_url, + 'is_active': content.is_active, + 'created_at': content.created_at.isoformat() if content.created_at else None, + 'updated_at': content.updated_at.isoformat() if content.updated_at else None + } + +@router.get('/') +async def get_cancellation_content(db: Session=Depends(get_db)): + try: + content = db.query(PageContent).filter(PageContent.page_type == PageType.CANCELLATION).first() + if not content: + return {'status': 'success', 'data': {'page_content': None, 'is_active': False}} + if not content.is_active: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Cancellation policy page is currently disabled') + content_dict = serialize_page_content(content) + return {'status': 'success', 'data': {'page_content': content_dict}} + except HTTPException: + raise + except Exception as e: + logger.error(f'Error fetching cancellation content: {str(e)}', exc_info=True) + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f'Error fetching cancellation content: {str(e)}') + diff --git a/Backend/src/routes/faq_routes.py b/Backend/src/routes/faq_routes.py new file mode 100644 index 00000000..cc54bd2d --- /dev/null +++ b/Backend/src/routes/faq_routes.py @@ -0,0 +1,44 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.orm import Session +from ..config.database import get_db +from ..config.logging_config import get_logger +from ..models.page_content import PageContent, PageType +logger = get_logger(__name__) +router = APIRouter(prefix='/faq', tags=['faq']) + +def serialize_page_content(content: PageContent) -> dict: + return { + 'id': content.id, + 'page_type': content.page_type.value, + 'title': content.title, + 'subtitle': content.subtitle, + 'description': content.description, + 'content': content.content, + 'meta_title': content.meta_title, + 'meta_description': content.meta_description, + 'meta_keywords': content.meta_keywords, + 'og_title': content.og_title, + 'og_description': content.og_description, + 'og_image': content.og_image, + 'canonical_url': content.canonical_url, + 'is_active': content.is_active, + 'created_at': content.created_at.isoformat() if content.created_at else None, + 'updated_at': content.updated_at.isoformat() if content.updated_at else None + } + +@router.get('/') +async def get_faq_content(db: Session=Depends(get_db)): + try: + content = db.query(PageContent).filter(PageContent.page_type == PageType.FAQ).first() + if not content: + return {'status': 'success', 'data': {'page_content': None, 'is_active': False}} + if not content.is_active: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='FAQ page is currently disabled') + content_dict = serialize_page_content(content) + return {'status': 'success', 'data': {'page_content': content_dict}} + except HTTPException: + raise + except Exception as e: + logger.error(f'Error fetching FAQ content: {str(e)}', exc_info=True) + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f'Error fetching FAQ content: {str(e)}') + diff --git a/Backend/src/routes/privacy_routes.py b/Backend/src/routes/privacy_routes.py index 96f2db44..70a233af 100644 --- a/Backend/src/routes/privacy_routes.py +++ b/Backend/src/routes/privacy_routes.py @@ -1,39 +1,110 @@ -from fastapi import APIRouter, Depends, Request, Response, status +from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session +import json +from datetime import datetime from ..config.database import get_db from ..config.logging_config import get_logger -from ..config.settings import settings -from ..middleware.cookie_consent import COOKIE_CONSENT_COOKIE_NAME, _parse_consent_cookie -from ..schemas.admin_privacy import PublicPrivacyConfigResponse -from ..schemas.privacy import CookieCategoryPreferences, CookieConsent, CookieConsentResponse, UpdateCookieConsentRequest +from ..models.page_content import PageContent, PageType from ..services.privacy_admin_service import privacy_admin_service +from ..schemas.privacy import CookieConsent, CookieConsentResponse, UpdateCookieConsentRequest, CookieCategoryPreferences +from ..schemas.admin_privacy import PublicPrivacyConfigResponse logger = get_logger(__name__) router = APIRouter(prefix='/privacy', tags=['privacy']) -@router.get('/cookie-consent', response_model=CookieConsentResponse, status_code=status.HTTP_200_OK) -async def get_cookie_consent(request: Request) -> CookieConsentResponse: - raw_cookie = request.cookies.get(COOKIE_CONSENT_COOKIE_NAME) - consent = _parse_consent_cookie(raw_cookie) - consent.categories.necessary = True - return CookieConsentResponse(data=consent) +def serialize_page_content(content: PageContent) -> dict: + return { + 'id': content.id, + 'page_type': content.page_type.value, + 'title': content.title, + 'subtitle': content.subtitle, + 'description': content.description, + 'content': content.content, + 'meta_title': content.meta_title, + 'meta_description': content.meta_description, + 'meta_keywords': content.meta_keywords, + 'og_title': content.og_title, + 'og_description': content.og_description, + 'og_image': content.og_image, + 'canonical_url': content.canonical_url, + 'is_active': content.is_active, + 'created_at': content.created_at.isoformat() if content.created_at else None, + 'updated_at': content.updated_at.isoformat() if content.updated_at else None + } -@router.post('/cookie-consent', response_model=CookieConsentResponse, status_code=status.HTTP_200_OK) -async def update_cookie_consent(request: UpdateCookieConsentRequest, response: Response) -> CookieConsentResponse: - existing_raw = response.headers.get('cookie') - categories = CookieCategoryPreferences() - if request.analytics is not None: - categories.analytics = request.analytics - if request.marketing is not None: - categories.marketing = request.marketing - if request.preferences is not None: - categories.preferences = request.preferences - categories.necessary = True - consent = CookieConsent(categories=categories, has_decided=True) - response.set_cookie(key=COOKIE_CONSENT_COOKIE_NAME, value=consent.model_dump_json(), httponly=True, secure=settings.is_production, samesite='lax', max_age=365 * 24 * 60 * 60, path='/') - logger.info('Cookie consent updated: analytics=%s, marketing=%s, preferences=%s', consent.categories.analytics, consent.categories.marketing, consent.categories.preferences) - return CookieConsentResponse(data=consent) +@router.get('/') +async def get_privacy_content(db: Session=Depends(get_db)): + try: + content = db.query(PageContent).filter(PageContent.page_type == PageType.PRIVACY).first() + if not content: + return {'status': 'success', 'data': {'page_content': None, 'is_active': False}} + if not content.is_active: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Privacy policy page is currently disabled') + content_dict = serialize_page_content(content) + return {'status': 'success', 'data': {'page_content': content_dict}} + except HTTPException: + raise + except Exception as e: + logger.error(f'Error fetching privacy content: {str(e)}', exc_info=True) + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f'Error fetching privacy content: {str(e)}') -@router.get('/config', response_model=PublicPrivacyConfigResponse, status_code=status.HTTP_200_OK) -async def get_public_privacy_config(db: Session=Depends(get_db)) -> PublicPrivacyConfigResponse: - config = privacy_admin_service.get_public_privacy_config(db) - return PublicPrivacyConfigResponse(data=config) \ No newline at end of file +@router.get('/cookie-consent', response_model=CookieConsentResponse) +async def get_cookie_consent(db: Session=Depends(get_db)): + """ + Get the default cookie consent structure. + Note: Actual consent is stored client-side (localStorage), this endpoint provides the default structure. + """ + try: + # Return default consent structure + # The actual consent state is managed client-side + consent = CookieConsent( + version=1, + updated_at=datetime.utcnow(), + has_decided=False, + categories=CookieCategoryPreferences( + necessary=True, # Always true + analytics=False, + marketing=False, + preferences=False + ) + ) + return CookieConsentResponse(status='success', data=consent) + except Exception as e: + logger.error(f'Error fetching cookie consent: {str(e)}', exc_info=True) + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f'Error fetching cookie consent: {str(e)}') + +@router.post('/cookie-consent', response_model=CookieConsentResponse) +async def update_cookie_consent(payload: UpdateCookieConsentRequest, db: Session=Depends(get_db)): + """ + Update cookie consent preferences. + Note: This endpoint acknowledges the consent update. Actual consent is stored client-side. + """ + try: + # Create updated consent structure + consent = CookieConsent( + version=1, + updated_at=datetime.utcnow(), + has_decided=True, + categories=CookieCategoryPreferences( + necessary=True, # Always true + analytics=payload.analytics if payload.analytics is not None else False, + marketing=payload.marketing if payload.marketing is not None else False, + preferences=payload.preferences if payload.preferences is not None else False + ) + ) + return CookieConsentResponse(status='success', data=consent) + except Exception as e: + logger.error(f'Error updating cookie consent: {str(e)}', exc_info=True) + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f'Error updating cookie consent: {str(e)}') + +@router.get('/config', response_model=PublicPrivacyConfigResponse) +async def get_public_privacy_config(db: Session=Depends(get_db)): + """ + Get public privacy configuration including cookie policy settings and integration configs. + This endpoint is public and does not require authentication. + """ + try: + config = privacy_admin_service.get_public_privacy_config(db) + return PublicPrivacyConfigResponse(status='success', data=config) + except Exception as e: + logger.error(f'Error fetching public privacy config: {str(e)}', exc_info=True) + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f'Error fetching public privacy config: {str(e)}') diff --git a/Backend/src/routes/refunds_routes.py b/Backend/src/routes/refunds_routes.py new file mode 100644 index 00000000..3f3109bf --- /dev/null +++ b/Backend/src/routes/refunds_routes.py @@ -0,0 +1,45 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.orm import Session +import json +from ..config.database import get_db +from ..config.logging_config import get_logger +from ..models.page_content import PageContent, PageType +logger = get_logger(__name__) +router = APIRouter(prefix='/refunds', tags=['refunds']) + +def serialize_page_content(content: PageContent) -> dict: + return { + 'id': content.id, + 'page_type': content.page_type.value, + 'title': content.title, + 'subtitle': content.subtitle, + 'description': content.description, + 'content': content.content, + 'meta_title': content.meta_title, + 'meta_description': content.meta_description, + 'meta_keywords': content.meta_keywords, + 'og_title': content.og_title, + 'og_description': content.og_description, + 'og_image': content.og_image, + 'canonical_url': content.canonical_url, + 'is_active': content.is_active, + 'created_at': content.created_at.isoformat() if content.created_at else None, + 'updated_at': content.updated_at.isoformat() if content.updated_at else None + } + +@router.get('/') +async def get_refunds_content(db: Session=Depends(get_db)): + try: + content = db.query(PageContent).filter(PageContent.page_type == PageType.REFUNDS).first() + if not content: + return {'status': 'success', 'data': {'page_content': None, 'is_active': False}} + if not content.is_active: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Refunds policy page is currently disabled') + content_dict = serialize_page_content(content) + return {'status': 'success', 'data': {'page_content': content_dict}} + except HTTPException: + raise + except Exception as e: + logger.error(f'Error fetching refunds content: {str(e)}', exc_info=True) + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f'Error fetching refunds content: {str(e)}') + diff --git a/Backend/src/routes/terms_routes.py b/Backend/src/routes/terms_routes.py new file mode 100644 index 00000000..f82d58e8 --- /dev/null +++ b/Backend/src/routes/terms_routes.py @@ -0,0 +1,45 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.orm import Session +import json +from ..config.database import get_db +from ..config.logging_config import get_logger +from ..models.page_content import PageContent, PageType +logger = get_logger(__name__) +router = APIRouter(prefix='/terms', tags=['terms']) + +def serialize_page_content(content: PageContent) -> dict: + return { + 'id': content.id, + 'page_type': content.page_type.value, + 'title': content.title, + 'subtitle': content.subtitle, + 'description': content.description, + 'content': content.content, + 'meta_title': content.meta_title, + 'meta_description': content.meta_description, + 'meta_keywords': content.meta_keywords, + 'og_title': content.og_title, + 'og_description': content.og_description, + 'og_image': content.og_image, + 'canonical_url': content.canonical_url, + 'is_active': content.is_active, + 'created_at': content.created_at.isoformat() if content.created_at else None, + 'updated_at': content.updated_at.isoformat() if content.updated_at else None + } + +@router.get('/') +async def get_terms_content(db: Session=Depends(get_db)): + try: + content = db.query(PageContent).filter(PageContent.page_type == PageType.TERMS).first() + if not content: + return {'status': 'success', 'data': {'page_content': None, 'is_active': False}} + if not content.is_active: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Terms & Conditions page is currently disabled') + content_dict = serialize_page_content(content) + return {'status': 'success', 'data': {'page_content': content_dict}} + except HTTPException: + raise + except Exception as e: + logger.error(f'Error fetching terms content: {str(e)}', exc_info=True) + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f'Error fetching terms content: {str(e)}') + diff --git a/Frontend/src/App.tsx b/Frontend/src/App.tsx index d472f0f8..f8f7cc68 100644 --- a/Frontend/src/App.tsx +++ b/Frontend/src/App.tsx @@ -14,6 +14,7 @@ import { CompanySettingsProvider } from './contexts/CompanySettingsContext'; import { AuthModalProvider } from './contexts/AuthModalContext'; import OfflineIndicator from './components/common/OfflineIndicator'; import CookieConsentBanner from './components/common/CookieConsentBanner'; +import CookiePreferencesModal from './components/common/CookiePreferencesModal'; import AnalyticsLoader from './components/common/AnalyticsLoader'; import Loading from './components/common/Loading'; import ScrollToTop from './components/common/ScrollToTop'; @@ -53,6 +54,12 @@ const InvoicePage = lazy(() => import('./pages/customer/InvoicePage')); const ProfilePage = lazy(() => import('./pages/customer/ProfilePage')); const AboutPage = lazy(() => import('./pages/AboutPage')); const ContactPage = lazy(() => import('./pages/ContactPage')); +const PrivacyPolicyPage = lazy(() => import('./pages/PrivacyPolicyPage')); +const TermsPage = lazy(() => import('./pages/TermsPage')); +const RefundsPolicyPage = lazy(() => import('./pages/RefundsPolicyPage')); +const CancellationPolicyPage = lazy(() => import('./pages/CancellationPolicyPage')); +const AccessibilityPage = lazy(() => import('./pages/AccessibilityPage')); +const FAQPage = lazy(() => import('./pages/FAQPage')); const AdminDashboardPage = lazy(() => import('./pages/admin/DashboardPage')); const InvoiceManagementPage = lazy(() => import('./pages/admin/InvoiceManagementPage')); @@ -188,6 +195,30 @@ function App() { path="contact" element={+ Manage your cookie preferences. You can enable or disable different types of cookies below. +
+Strictly necessary
++ Essential for security, authentication, and core booking flows. These are always enabled. +
+This page is currently unavailable.
+ ++ {pageContent.subtitle} +
+ )} ++ For accessibility inquiries, contact us at{' '} + + {settings.company_email} + +
+This page is currently unavailable.
+ ++ {pageContent.subtitle} +
+ )} ++ For questions about cancellations, contact us at{' '} + + {settings.company_email} + +
+This page is currently unavailable.
+ ++ {pageContent.subtitle} +
+ )} ++ Still have questions? Contact us at{' '} + + {settings.company_email} + +
+This page is currently unavailable.
+ ++ {pageContent.subtitle} +
+ )} ++ For questions about this policy, contact us at{' '} + + {settings.company_email} + +
+This page is currently unavailable.
+ ++ {pageContent.subtitle} +
+ )} ++ For refund inquiries, contact us at{' '} + + {settings.company_email} + +
+This page is currently unavailable.
+ ++ {pageContent.subtitle} +
+ )} ++ For questions about these terms, contact us at{' '} + + {settings.company_email} + +
++
20% deposit required to secure your booking. Pay the remaining balance ({formatPrice(totalPrice * 0.8)}) on arrival at the hotel. -
-