Skip to content

Conversation

Drushya-jolly
Copy link

@Drushya-jolly Drushya-jolly commented Feb 8, 2025

Summary by CodeRabbit

  • Documentation
    • Project branding updated from a placeholder to DestINo.
  • New Features
    • Launched an AI Travel Itinerary Planner that allows users to select a destination, input their budget and travel dates, and generate a personalized itinerary.
    • Enabled the option to export generated itineraries as PDF documents.
  • Style
    • Introduced a refreshed, modern design with enhanced layouts and user-friendly styling for an improved experience.

Copy link

coderabbitai bot commented Feb 8, 2025

Walkthrough

The pull request updates the project’s README by changing the project name from “[Project Name]” to “DestINo” and introduces an AI Travel Itinerary Planner feature. The new functionality includes an HTML interface (index.html) with a travel inputs form, a JavaScript file (script.js) that defines destination data and implements functions to generate itineraries and export them as PDF files using the jsPDF library, and a CSS file (styles.css) to style the interface elements. The changes focus on integrating itinerary planning logic and a structured, styled user interface while preserving existing content.

Changes

File(s) Change Summary
README.md Updated project name from “[Project Name]” to “DestINo”.
index.html, script.js, styles.css Introduced new AI Travel Itinerary Planner feature with a user interface for travel inputs, itinerary generation using predefined destination data, PDF export functionality via jsPDF, and corresponding styling.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant F as Form (index.html)
    participant G as generateItinerary()
    participant D as destinationData
    participant S as Itinerary Display
    U->>F: Enter travel details & click "Generate"
    F->>G: Pass inputs to generateItinerary()
    G->>D: Request destination info
    D-->>G: Return destination data
    G->>S: Render itinerary summary
Loading
sequenceDiagram
    participant U as User
    participant E as exportItinerary()
    participant PDF as jsPDF Library
    participant S as Itinerary Summary (Document)
    U->>S: Click "Export as PDF"
    S->>E: Trigger exportItinerary()
    E->>PDF: Send itinerary content for PDF generation
    PDF-->>E: Return generated PDF
    E->>U: Provide itinerary.pdf download
Loading

Poem

Hopping through code on a sunny day,
I found new paths in a stylish display.
DestINo leaps with travel plans so bright,
Itineraries born in digital light.
PDF files dance with a joyful cheer,
A rabbit’s rhyme for changes dear!

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
index.html (2)

38-41: Add placeholder content and loading state for itinerary view.

The itinerary view should show a placeholder when empty and a loading state during generation.

     <div id="itinerary-view">
         <h2>Your AI-Generated Itinerary</h2>
-        <div id="itinerary-summary"></div>
+        <div id="itinerary-summary">
+            <p class="placeholder">Your itinerary will appear here after generation.</p>
+        </div>
     </div>

7-8: Add integrity checks for external resources.

Add integrity checks for the external CDN resources to prevent supply chain attacks.

-    <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.4.0/jspdf.umd.min.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.4.0/jspdf.umd.min.js" 
+            integrity="sha384-..." 
+            crossorigin="anonymous"></script>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ac8915 and ce8b866.

📒 Files selected for processing (4)
  • README.md (1 hunks)
  • index.html (1 hunks)
  • script.js (1 hunks)
  • styles.css (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • README.md
  • styles.css

Comment on lines +1 to +38
const destinationData = {
"Paris": {
activities: [
{ name: "Eiffel Tower Visit", cost: 30 },
{ name: "Louvre Museum", cost: 20 },
{ name: "Seine River Cruise", cost: 50 }
],
accommodations: [
{ name: "Budget Hotel", costPerNight: 100 },
{ name: "Mid-range Hotel", costPerNight: 200 },
{ name: "Luxury Hotel", costPerNight: 400 }
]
},
"New York": {
activities: [
{ name: "Statue of Liberty", cost: 25 },
{ name: "Central Park", cost: 0 },
{ name: "Broadway Show", cost: 100 }
],
accommodations: [
{ name: "Budget Hostel", costPerNight: 50 },
{ name: "Mid-range Hotel", costPerNight: 150 },
{ name: "Luxury Hotel", costPerNight: 350 }
]
},
"Tokyo": {
activities: [
{ name: "Shibuya Crossing", cost: 0 },
{ name: "Tokyo Tower", cost: 15 },
{ name: "Senso-ji Temple", cost: 0 }
],
accommodations: [
{ name: "Capsule Hotel", costPerNight: 40 },
{ name: "Business Hotel", costPerNight: 100 },
{ name: "Luxury Hotel", costPerNight: 300 }
]
}
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add missing destination data to match UI options.

The destinationData object is missing data for London, Rome, Sydney, and Dubai, which are available as options in the UI. This mismatch will result in "Sorry, we don't have data for this destination" errors when users select these destinations.

Consider internationalizing costs.

The costs are hardcoded without currency specification. Consider:

  1. Adding currency information to support international destinations
  2. Implementing currency conversion based on user preferences

Comment on lines +83 to +91
// Export function to PDF
function exportItinerary() {
const { jsPDF } = window.jspdf;
const doc = new jsPDF();

const itineraryContent = document.getElementById('itinerary-summary').innerText;
doc.text(itineraryContent, 10, 10);
doc.save('itinerary.pdf');
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve PDF export functionality.

The current implementation has several limitations:

  1. No error handling if itinerary is not generated
  2. Basic PDF formatting with all text at position (10,10)
  3. No handling of content overflow or page breaks

Here's a suggested improvement:

 function exportItinerary() {
+    const itineraryElement = document.getElementById('itinerary-summary');
+    if (!itineraryElement || !itineraryElement.innerText.trim()) {
+        alert("Please generate an itinerary first.");
+        return;
+    }
+
     const { jsPDF } = window.jspdf;
     const doc = new jsPDF();
 
-    const itineraryContent = document.getElementById('itinerary-summary').innerText;
-    doc.text(itineraryContent, 10, 10);
+    // Set font size and line height
+    doc.setFontSize(12);
+    const lineHeight = 8;
+
+    // Split text into lines that fit the page width
+    const lines = doc.splitTextToSize(itineraryElement.innerText, 190);
+
+    // Add lines to PDF with proper spacing
+    let y = 20;
+    lines.forEach(line => {
+        if (y > 280) { // Check if we need a new page
+            doc.addPage();
+            y = 20;
+        }
+        doc.text(line, 10, y);
+        y += lineHeight;
+    });
+
     doc.save('itinerary.pdf');
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Export function to PDF
function exportItinerary() {
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
const itineraryContent = document.getElementById('itinerary-summary').innerText;
doc.text(itineraryContent, 10, 10);
doc.save('itinerary.pdf');
}
// Export function to PDF
function exportItinerary() {
const itineraryElement = document.getElementById('itinerary-summary');
if (!itineraryElement || !itineraryElement.innerText.trim()) {
alert("Please generate an itinerary first.");
return;
}
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
// Set font size and line height
doc.setFontSize(12);
const lineHeight = 8;
// Split text into lines that fit the page width
const lines = doc.splitTextToSize(itineraryElement.innerText, 190);
// Add lines to PDF with proper spacing
let y = 20;
lines.forEach(line => {
if (y > 280) { // Check if we need a new page
doc.addPage();
y = 20;
}
doc.text(line, 10, y);
y += lineHeight;
});
doc.save('itinerary.pdf');
}

Comment on lines +40 to +81
// Function to generate the AI-driven itinerary
function generateItinerary() {
const destination = document.getElementById('destination').value;
const budget = parseFloat(document.getElementById('budget').value);

if (!destination || isNaN(budget) || budget <= 0) {
alert("Please enter a valid destination and budget.");
return;
}

// Check if the destination is in the predefined data
const data = destinationData[destination];
if (!data) {
alert("Sorry, we don't have data for this destination.");
return;
}

// Suggest activities based on the budget
let activities = data.activities.filter(activity => activity.cost <= budget);
let accommodations = data.accommodations.filter(acc => acc.costPerNight <= budget / 3); // Assume a 3-night stay

// Generate itinerary summary
let itinerarySummary = `
<h3>Destination: ${destination}</h3>
<h4>Activities (within your budget):</h4>
<ul>
${activities.map(activity => `<li>${activity.name} - $${activity.cost}</li>`).join('')}
</ul>
<h4>Accommodation Options (within your budget):</h4>
<ul>
${accommodations.map(acc => `<li>${acc.name} - $${acc.costPerNight} per night</li>`).join('')}
</ul>
<h4>Suggested Itinerary:</h4>
<ul>
<li>Day 1: Arrive and check into your accommodation</li>
${activities.slice(0, 2).map((activity, index) => `<li>Day ${index + 2}: ${activity.name}</li>`).join('')}
</ul>
`;

// Display the generated itinerary
document.getElementById('itinerary-summary').innerHTML = itinerarySummary;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Several improvements needed for the itinerary generation logic.

  1. Trip dates from the UI are not used in the itinerary generation
  2. The 3-night stay assumption for accommodation budget is hardcoded
  3. Missing error handling for cases where no activities/accommodations fit within budget
  4. HTML content is not sanitized against XSS attacks
  5. No consideration for seasonal availability or operating hours of activities

Here's a suggested improvement:

 function generateItinerary() {
     const destination = document.getElementById('destination').value;
     const budget = parseFloat(document.getElementById('budget').value);
+    const startDate = new Date(document.getElementById('start-date').value);
+    const endDate = new Date(document.getElementById('end-date').value);
+
+    // Validate dates
+    if (isNaN(startDate.getTime()) || isNaN(endDate.getTime()) || startDate >= endDate) {
+        alert("Please enter valid trip dates.");
+        return;
+    }
+
+    // Calculate number of nights
+    const nights = Math.ceil((endDate - startDate) / (1000 * 60 * 60 * 24));

     if (!destination || isNaN(budget) || budget <= 0) {
         alert("Please enter a valid destination and budget.");
         return;
     }

     // Check if the destination is in the predefined data
     const data = destinationData[destination];
     if (!data) {
         alert("Sorry, we don't have data for this destination.");
         return;
     }

     // Suggest activities based on the budget
     let activities = data.activities.filter(activity => activity.cost <= budget);
-    let accommodations = data.accommodations.filter(acc => acc.costPerNight <= budget / 3); // Assume a 3-night stay
+    let accommodations = data.accommodations.filter(acc => acc.costPerNight <= budget / nights);
+
+    // Check if we found any suitable options
+    if (activities.length === 0 || accommodations.length === 0) {
+        alert("Sorry, we couldn't find any suitable activities or accommodations within your budget.");
+        return;
+    }

     // Generate itinerary summary
     let itinerarySummary = `
         <h3>Destination: ${destination}</h3>
+        <h4>Trip Duration: ${nights} nights (${startDate.toLocaleDateString()} - ${endDate.toLocaleDateString()})</h4>
         <h4>Activities (within your budget):</h4>
         <ul>
-            ${activities.map(activity => `<li>${activity.name} - $${activity.cost}</li>`).join('')}
+            ${activities.map(activity => `<li>${escapeHtml(activity.name)} - $${activity.cost}</li>`).join('')}
         </ul>
         <h4>Accommodation Options (within your budget):</h4>
         <ul>
-            ${accommodations.map(acc => `<li>${acc.name} - $${acc.costPerNight} per night</li>`).join('')}
+            ${accommodations.map(acc => `<li>${escapeHtml(acc.name)} - $${acc.costPerNight} per night</li>`).join('')}
         </ul>
         <h4>Suggested Itinerary:</h4>
         <ul>
             <li>Day 1: Arrive and check into your accommodation</li>
-            ${activities.slice(0, 2).map((activity, index) => `<li>Day ${index + 2}: ${activity.name}</li>`).join('')}
+            ${activities.slice(0, nights).map((activity, index) => 
+                `<li>Day ${index + 2}: ${escapeHtml(activity.name)}</li>`
+            ).join('')}
         </ul>
     `;

     // Display the generated itinerary
     document.getElementById('itinerary-summary').innerHTML = itinerarySummary;
 }

+// Helper function to escape HTML and prevent XSS
+function escapeHtml(unsafe) {
+    return unsafe
+        .replace(/&/g, "&amp;")
+        .replace(/</g, "&lt;")
+        .replace(/>/g, "&gt;")
+        .replace(/"/g, "&quot;")
+        .replace(/'/g, "&#039;");
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Function to generate the AI-driven itinerary
function generateItinerary() {
const destination = document.getElementById('destination').value;
const budget = parseFloat(document.getElementById('budget').value);
if (!destination || isNaN(budget) || budget <= 0) {
alert("Please enter a valid destination and budget.");
return;
}
// Check if the destination is in the predefined data
const data = destinationData[destination];
if (!data) {
alert("Sorry, we don't have data for this destination.");
return;
}
// Suggest activities based on the budget
let activities = data.activities.filter(activity => activity.cost <= budget);
let accommodations = data.accommodations.filter(acc => acc.costPerNight <= budget / 3); // Assume a 3-night stay
// Generate itinerary summary
let itinerarySummary = `
<h3>Destination: ${destination}</h3>
<h4>Activities (within your budget):</h4>
<ul>
${activities.map(activity => `<li>${activity.name} - $${activity.cost}</li>`).join('')}
</ul>
<h4>Accommodation Options (within your budget):</h4>
<ul>
${accommodations.map(acc => `<li>${acc.name} - $${acc.costPerNight} per night</li>`).join('')}
</ul>
<h4>Suggested Itinerary:</h4>
<ul>
<li>Day 1: Arrive and check into your accommodation</li>
${activities.slice(0, 2).map((activity, index) => `<li>Day ${index + 2}: ${activity.name}</li>`).join('')}
</ul>
`;
// Display the generated itinerary
document.getElementById('itinerary-summary').innerHTML = itinerarySummary;
}
// Function to generate the AI-driven itinerary
function generateItinerary() {
const destination = document.getElementById('destination').value;
const budget = parseFloat(document.getElementById('budget').value);
const startDate = new Date(document.getElementById('start-date').value);
const endDate = new Date(document.getElementById('end-date').value);
// Validate dates
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime()) || startDate >= endDate) {
alert("Please enter valid trip dates.");
return;
}
// Calculate number of nights
const nights = Math.ceil((endDate - startDate) / (1000 * 60 * 60 * 24));
if (!destination || isNaN(budget) || budget <= 0) {
alert("Please enter a valid destination and budget.");
return;
}
// Check if the destination is in the predefined data
const data = destinationData[destination];
if (!data) {
alert("Sorry, we don't have data for this destination.");
return;
}
// Suggest activities based on the budget
let activities = data.activities.filter(activity => activity.cost <= budget);
let accommodations = data.accommodations.filter(acc => acc.costPerNight <= budget / nights);
// Check if we found any suitable options
if (activities.length === 0 || accommodations.length === 0) {
alert("Sorry, we couldn't find any suitable activities or accommodations within your budget.");
return;
}
// Generate itinerary summary
let itinerarySummary = `
<h3>Destination: ${destination}</h3>
<h4>Trip Duration: ${nights} nights (${startDate.toLocaleDateString()} - ${endDate.toLocaleDateString()})</h4>
<h4>Activities (within your budget):</h4>
<ul>
${activities.map(activity => `<li>${escapeHtml(activity.name)} - $${activity.cost}</li>`).join('')}
</ul>
<h4>Accommodation Options (within your budget):</h4>
<ul>
${accommodations.map(acc => `<li>${escapeHtml(acc.name)} - $${acc.costPerNight} per night</li>`).join('')}
</ul>
<h4>Suggested Itinerary:</h4>
<ul>
<li>Day 1: Arrive and check into your accommodation</li>
${activities.slice(0, nights).map((activity, index) =>
`<li>Day ${index + 2}: ${escapeHtml(activity.name)}</li>`
).join('')}
</ul>
`;
// Display the generated itinerary
document.getElementById('itinerary-summary').innerHTML = itinerarySummary;
}
// Helper function to escape HTML and prevent XSS
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}

Comment on lines +15 to +36
<div class="form-container">
<h2>Select Your Destination</h2>
<select id="destination">
<option value="" disabled selected>Select a destination</option>
<option value="Paris">Paris</option>
<option value="New York">New York</option>
<option value="Tokyo">Tokyo</option>
<option value="London">London</option>
<option value="Rome">Rome</option>
<option value="Sydney">Sydney</option>
<option value="Dubai">Dubai</option>
</select>

<h2>Enter Your Budget</h2>
<input type="number" id="budget" placeholder="Enter Budget">

<h2>Select Your Trip Dates</h2>
<input type="date" id="start-date">
<input type="date" id="end-date">

<button onclick="generateItinerary()">Generate Itinerary</button>
</div>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve form validation and accessibility.

The form lacks proper validation attributes and accessibility features:

  1. Add form validation attributes
  2. Add ARIA labels for screen readers
  3. Add an error message container
  4. Add a loading indicator for feedback during generation

Here's a suggested improvement:

-    <div class="form-container">
+    <form class="form-container" onsubmit="event.preventDefault(); generateItinerary();">
         <h2>Select Your Destination</h2>
-        <select id="destination">
+        <select id="destination" required aria-label="Travel destination">
             <option value="" disabled selected>Select a destination</option>
             <option value="Paris">Paris</option>
             <option value="New York">New York</option>
             <option value="Tokyo">Tokyo</option>
-            <option value="London">London</option>
-            <option value="Rome">Rome</option>
-            <option value="Sydney">Sydney</option>
-            <option value="Dubai">Dubai</option>
         </select>

         <h2>Enter Your Budget</h2>
-        <input type="number" id="budget" placeholder="Enter Budget">
+        <input type="number" id="budget" placeholder="Enter Budget" required min="0" aria-label="Travel budget">

         <h2>Select Your Trip Dates</h2>
-        <input type="date" id="start-date">
-        <input type="date" id="end-date">
+        <input type="date" id="start-date" required aria-label="Trip start date">
+        <input type="date" id="end-date" required aria-label="Trip end date">

-        <button onclick="generateItinerary()">Generate Itinerary</button>
+        <button type="submit">Generate Itinerary</button>
+        <div id="loading" class="hidden">Generating your itinerary...</div>
+        <div id="error-message" class="hidden"></div>
-    </div>
+    </form>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div class="form-container">
<h2>Select Your Destination</h2>
<select id="destination">
<option value="" disabled selected>Select a destination</option>
<option value="Paris">Paris</option>
<option value="New York">New York</option>
<option value="Tokyo">Tokyo</option>
<option value="London">London</option>
<option value="Rome">Rome</option>
<option value="Sydney">Sydney</option>
<option value="Dubai">Dubai</option>
</select>
<h2>Enter Your Budget</h2>
<input type="number" id="budget" placeholder="Enter Budget">
<h2>Select Your Trip Dates</h2>
<input type="date" id="start-date">
<input type="date" id="end-date">
<button onclick="generateItinerary()">Generate Itinerary</button>
</div>
<form class="form-container" onsubmit="event.preventDefault(); generateItinerary();">
<h2>Select Your Destination</h2>
<select id="destination" required aria-label="Travel destination">
<option value="" disabled selected>Select a destination</option>
<option value="Paris">Paris</option>
<option value="New York">New York</option>
<option value="Tokyo">Tokyo</option>
</select>
<h2>Enter Your Budget</h2>
<input type="number" id="budget" placeholder="Enter Budget" required min="0" aria-label="Travel budget">
<h2>Select Your Trip Dates</h2>
<input type="date" id="start-date" required aria-label="Trip start date">
<input type="date" id="end-date" required aria-label="Trip end date">
<button type="submit">Generate Itinerary</button>
<div id="loading" class="hidden">Generating your itinerary...</div>
<div id="error-message" class="hidden"></div>
</form>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant