Skip to content

Conversation

@wesleylui
Copy link
Contributor

@wesleylui wesleylui commented Dec 9, 2025

The agency booking form now uses the trip creation API through a TanStack Query Mutation, this also takes care of loading/error states, and success notifications. Form values are mapped to the API format with proper validation, including Google Maps address verification and datetime conversion. The form automatically resets after successful submission.
Key change:

  • Connected form submission to api.trip.create endpoint

Summary by CodeRabbit

  • New Features

    • Google Places address autocomplete for bookings (CA-restricted).
    • Booking submission now uses mutation-driven loading and includes phone number support.
  • Bug Fixes

    • Expanded form validation with clearer string-length and time-range checks.
    • Improved date/time handling to use local-time ISO semantics and stricter date validation.
  • Chores

    • Removed an unused import.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 9, 2025

📝 Walkthrough

Walkthrough

Adds client-side booking creation via a mutation with success/error handlers, expanded form validation (including time range and phone), local-ISO date handling in the form, two validation helpers, a stricter DatePicker NaN check, and a server schema rename contactInfo→phoneNumber.

Changes

Cohort / File(s) Summary
Booking creation UI & logic
src/app/_components/agencycomponents/agency-interactive-area.tsx
Added createBookingMutation (success/error handling), replaced local loading with mutation state, centralized validation before mutate, address autocomplete validity checks, and payload mapping (times → ISO, destination from ref).
Form date/time handling
src/app/_components/agencycomponents/agency-form.tsx
Serialize date-time fields as local ISO strings for form state; added toLocalISOString; set DateTimePicker valueFormat, minDate, and disabled end-time until start-time set.
Validation helpers
src/types/validation.ts
Added and exported validateStringLength(value,min,max,fieldName) and validateTimeRange(startTime,endTime).
DatePicker NaN check
src/app/_components/common/datepicker/DatePicker.tsx
Replaced isNaN(...) with Number.isNaN(...) when validating Date.getTime().
Server schema update
src/server/api/routers/trip.ts
Renamed input contactInfophoneNumber; removed contactInfo from passengerInfo construction; persist phoneNumber on booking insert.
Debug cleanup
src/app/debug/bookings/page.tsx
Removed unused authClient import.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Form as Agency Form Component
    participant Validation as Validation Helpers
    participant GooglePlaces as Google Places Autocomplete
    participant Mutation as createBookingMutation
    participant API as Backend API
    participant Toast as Toast Notification

    User->>Form: Submit booking
    Form->>Validation: Validate fields (title, name, phone, times, addresses)
    alt Validation fails
        Validation-->>Form: errors
        Form->>Toast: show validation error
    else Validation passes
        Form->>GooglePlaces: ensure destination address valid
        alt Address invalid
            GooglePlaces-->>Form: invalid
            Form->>Toast: set address field error
        else Address valid
            Form->>Form: convert start/end to local ISO
            Form->>Mutation: submit payload
            Mutation->>API: create booking request
            alt API success
                API-->>Mutation: success
                Mutation->>Toast: show success
                Mutation->>Form: reset form & close modal
            else API error
                API-->>Mutation: error
                Mutation->>Toast: show error
            end
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • burtonjong
  • themaxboucher
  • JustinTan-1
  • Yemyam
  • Lujarios

Poem

🐇 I hopped to validate each field tonight,
ISO times tucked in snug and right,
Autocomplete hummed, mutation took flight,
Toasts popped—forms reset in moonlight,
Hooray, bookings bounded home — what a sight! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: connecting the agency booking form to the trip creation API endpoint.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e72ec1 and 0c03daf.

📒 Files selected for processing (1)
  • src/app/_components/agencycomponents/agency-interactive-area.tsx (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/app/_components/agencycomponents/agency-interactive-area.tsx (2)
src/trpc/react.tsx (1)
  • api (25-25)
src/lib/notifications.ts (1)
  • notify (13-58)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci
🔇 Additional comments (3)
src/app/_components/agencycomponents/agency-interactive-area.tsx (3)

12-12: LGTM! TRPC integration properly imported.

The import of the TRPC API client follows the standard pattern and is correctly used for the mutation hook below.


30-40: Well-structured mutation with comprehensive state management.

The mutation setup correctly handles both success and error cases with appropriate notifications and cleanup. The defensive error handling with the fallback message is a good practice.


196-196: Excellent use of mutation pending state.

Connecting the modal's loading state directly to createBookingMutation.isPending ensures the UI accurately reflects the mutation status without additional state management overhead.

Copy link
Contributor

@burtonjong burtonjong left a comment

Choose a reason for hiding this comment

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

@wesleylui I think as part of this ticket I wouldn't mind cleaning up some of the validation on the frontend for this.

Things like:

  • residentName shouldn't be crazy long (or any of the fields for that matter, maybe except the purpose of the trip)
  • endTime shouldn't be less that startTime

If you think there's some validation function that is reusable, you can also put that in validation.ts or whatever the shared validation file is called (has the email validation there as well)

@wesleylui wesleylui force-pushed the SANC-43-Connect-agency-booking-form-to-trip-creation-API branch from 0c03daf to be4a77f Compare December 28, 2025 19:51
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: 0

♻️ Duplicate comments (1)
src/app/_components/agencycomponents/agency-interactive-area.tsx (1)

142-152: Add date validation before ISO string conversion.

Lines 149-150 directly convert form.values.startTime and form.values.endTime to ISO strings without validation. If these values are invalid date strings, new Date().toISOString() will throw a RangeError: Invalid time value. The current form validation (lines 72-73) only checks for non-empty values, not date validity. This synchronous error occurs before the mutation call and won't be caught by the mutation's onError handler, resulting in an uncaught exception.

🔎 Recommended fix to validate dates before conversion
   if (!validationAddressGood) {
     form.setFieldError("destinationAddress", "Please select a valid address from the dropdown");
     return;
   }
 
+  const startDate = new Date(form.values.startTime);
+  const endDate = new Date(form.values.endTime);
+  
+  if (isNaN(startDate.getTime())) {
+    form.setFieldError("startTime", "Invalid date format");
+    return;
+  }
+  
+  if (isNaN(endDate.getTime())) {
+    form.setFieldError("endTime", "Invalid date format");
+    return;
+  }
+
   createBookingMutation.mutate({
     title: form.values.title,
     residentName: form.values.residentName,
     contactInfo: form.values.contactInfo,
     additionalInfo: form.values.additionalInfo,
     pickupAddress: form.values.pickupAddress,
     destinationAddress: inputElement.current?.value || "",
-    startTime: new Date(form.values.startTime).toISOString(),
-    endTime: new Date(form.values.endTime).toISOString(),
+    startTime: startDate.toISOString(),
+    endTime: endDate.toISOString(),
     purpose: form.values.purpose,
   });
🧹 Nitpick comments (1)
src/app/_components/agencycomponents/agency-interactive-area.tsx (1)

31-41: Consider invalidating queries on successful booking creation.

The mutation's onSuccess handler properly resets the form and shows a notification, but it doesn't invalidate any TRPC queries. If the calendar or table views fetch bookings via TRPC queries, they won't automatically reflect the newly created booking until a manual refresh or navigation.

🔎 Suggested enhancement to invalidate queries
+  const utils = api.useUtils();
+
   const createBookingMutation = api.trip.create.useMutation({
     onSuccess: () => {
       notify.success("Booking successfully created");
       form.reset();
       setShowBookingModal(false);
       setValidationAddressGood(false);
+      void utils.trip.invalidate();
     },
     onError: (error) => {
       notify.error(error.message || "Failed to create a booking");
     },
   });

Note: Adjust the invalidation target based on which specific query endpoint is used to fetch the trip list (e.g., utils.trip.getAll.invalidate() if that's the specific endpoint).

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c03daf and be4a77f.

📒 Files selected for processing (1)
  • src/app/_components/agencycomponents/agency-interactive-area.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/app/_components/agencycomponents/agency-interactive-area.tsx (2)
src/trpc/react.tsx (1)
  • api (25-25)
src/lib/notifications.ts (1)
  • notify (13-58)
🔇 Additional comments (3)
src/app/_components/agencycomponents/agency-interactive-area.tsx (3)

12-12: LGTM!

The TRPC API client import is correct and necessary for the mutation implementation.


137-140: LGTM!

The Google Maps address validation check properly prevents submission of unvalidated addresses and provides clear user feedback.


197-197: LGTM!

Using createBookingMutation.isPending for the modal loading state is the correct approach. This properly reflects the mutation status and prevents duplicate submissions during the API call.

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: 1

♻️ Duplicate comments (1)
src/app/_components/agencycomponents/agency-interactive-area.tsx (1)

152-162: CRITICAL: Add date validation before ISO string conversion (duplicate concern).

The direct conversion to ISO strings on lines 159-160 will throw a RangeError if form.values.startTime or form.values.endTime contain invalid date strings. While the form validation checks if the fields are non-empty and if the time range is valid, it doesn't validate the actual date format for startTime. If an Invalid Date object is created, calling .toISOString() will throw a synchronous error that won't be caught by the mutation's onError handler, potentially crashing the component.

This is the same concern raised in the previous review but has not been addressed yet.

🔎 Proposed fix to add date validation
   if (!validationAddressGood) {
     form.setFieldError("destinationAddress", "Please select a valid address from the dropdown");
     return;
   }
+
+  const startDate = new Date(form.values.startTime);
+  const endDate = new Date(form.values.endTime);
+  
+  if (isNaN(startDate.getTime())) {
+    form.setFieldError("startTime", "Invalid date format");
+    return;
+  }
+  
+  if (isNaN(endDate.getTime())) {
+    form.setFieldError("endTime", "Invalid date format");
+    return;
+  }
 
   createBookingMutation.mutate({
     title: form.values.title,
     residentName: form.values.residentName,
     contactInfo: form.values.contactInfo,
     additionalInfo: form.values.additionalInfo,
     pickupAddress: form.values.pickupAddress,
     destinationAddress: inputElement.current?.value || "",
-    startTime: new Date(form.values.startTime).toISOString(),
-    endTime: new Date(form.values.endTime).toISOString(),
+    startTime: startDate.toISOString(),
+    endTime: endDate.toISOString(),
     purpose: form.values.purpose,
   });
🧹 Nitpick comments (2)
src/types/validation.ts (1)

20-37: LGTM with minor documentation note.

The validation logic is correct and handles trimming appropriately. However, the JSDoc comment on line 15 mentions "default: 1" for minLength, but the function signature doesn't include a default value. Consider either adding the default to the signature or removing it from the documentation for consistency.

src/app/_components/agencycomponents/agency-interactive-area.tsx (1)

69-88: Consider adding date format validation.

The form validation correctly uses the new validation helpers and properly chains the endTime validation (required check + time range). However, the startTime validation on line 78 only checks if the value is non-empty, not whether it's a valid date string. While the Mantine DateTimePicker component likely enforces valid dates, adding defensive validation would prevent potential runtime errors.

🔎 Proposed enhancement for startTime validation
-      startTime: (value) => (value.trim().length > 0 ? null : "Date and time is required"),
+      startTime: (value) => {
+        if (value.trim().length === 0) return "Date and time is required";
+        const date = new Date(value);
+        if (isNaN(date.getTime())) return "Invalid date format";
+        return null;
+      },
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be4a77f and ee91731.

📒 Files selected for processing (2)
  • src/app/_components/agencycomponents/agency-interactive-area.tsx
  • src/types/validation.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/app/_components/agencycomponents/agency-interactive-area.tsx (3)
src/trpc/react.tsx (1)
  • api (25-25)
src/lib/notifications.ts (1)
  • notify (13-58)
src/types/validation.ts (2)
  • validateStringLength (20-37)
  • validateTimeRange (45-59)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci
🔇 Additional comments (3)
src/app/_components/agencycomponents/agency-interactive-area.tsx (3)

12-14: LGTM!

The imports are properly structured and typed according to the TRPC and validation module setup.


32-42: LGTM!

The mutation setup follows best practices with proper success and error handling. The cleanup in onSuccess correctly resets all relevant state including the form, modal visibility, and address validation.


207-207: LGTM!

Using createBookingMutation.isPending for the loading state is the correct approach and follows TanStack Query best practices. This ensures the loading state is automatically synchronized with the mutation status.

@wesleylui
Copy link
Contributor Author

Added reusable validation utilities to validation.ts:

  • validateStringLength(): General-purpose string validation with min/max length constraints and custom field names for error messages
  • validateTimeRange(): Validates that end time is after start time for datetime fields

These validators are used in the agency booking form but can be reused in other forms for validating general strings and dates

Copy link
Contributor

@promatty promatty left a comment

Choose a reason for hiding this comment

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

also creation doesnt work rn since the datepicker doesnt work. this is likely an issue with iso strings not converting to a Date properly, but may also be how we no longer use this transportDateTime field

Screen.Recording.2025-12-28.at.7.09.03.PM.mov

Comment on lines 77 to 98
},
startTime: (value) => (value.trim().length > 0 ? null : "Date and time is required"),
endTime: (value) => (value.trim().length > 0 ? null : "Date and time is required"),
purpose: (value) => (value.trim().length > 0 ? null : "Purpose is required"),
pickupAddress: (value) => (value.trim().length > 0 ? null : "Pickup address is required"),
destinationAddress: (value) =>
value.trim().length > 0 ? null : "Destination address is required",
endTime: (value, values) => {
// First check if required
if (value.trim().length === 0) return "Date and time is required";
// Then validate time range
return validateTimeRange(values.startTime, value);
},
Copy link
Contributor

Choose a reason for hiding this comment

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

the agency-form only displays a single datepicker for date and time of transport. have it show a datepicker for both the start and end times instead of a transportDateTime

contactInfo: form.values.contactInfo,
additionalInfo: form.values.additionalInfo,
pickupAddress: form.values.pickupAddress,
destinationAddress: inputElement.current?.value || "",
Copy link
Contributor

Choose a reason for hiding this comment

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

why do we use the ref value instead of form value?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe we have to use the ref value for destinationAddress because of the Google autocomplete. I believe it'll autocomplete the value directly in the HTML element so we will get the full address from there and not the original input on the form.

notify.success("Booking successfully created");
form.reset();
setShowBookingModal(false);
setValidationAddressGood(false);
Copy link
Contributor

Choose a reason for hiding this comment

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

after creating a new booking, the table and calendar wont refresh. perhaps we should invalidate the bookings query to cause a refresh

Suggested change
setValidationAddressGood(false);
setValidationAddressGood(false);
void utils.bookings.invalidate();

@wesleylui wesleylui force-pushed the SANC-43-Connect-agency-booking-form-to-trip-creation-API branch from ee91731 to fa49840 Compare January 3, 2026 22:51
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: 1

♻️ Duplicate comments (2)
src/app/_components/agencycomponents/agency-interactive-area.tsx (2)

171-181: Add date validation before ISO string conversion.

Lines 178-179 directly convert date strings to ISO format without validation. If form.values.startTime or form.values.endTime contain invalid date strings, new Date().toISOString() will throw a TypeError. The current form validation only checks for non-empty values (line 87), not date validity. This synchronous error won't be caught by the mutation's onError handler.

🔎 Proposed fix to validate dates before conversion
   if (!validationAddressGood) {
     form.setFieldError(
       "destinationAddress",
       "Please select a valid address from the dropdown"
     );
     return;
   }

+  const startDate = new Date(form.values.startTime);
+  const endDate = new Date(form.values.endTime);
+  
+  if (isNaN(startDate.getTime())) {
+    form.setFieldError("startTime", "Invalid date format");
+    return;
+  }
+  
+  if (isNaN(endDate.getTime())) {
+    form.setFieldError("endTime", "Invalid date format");
+    return;
+  }

   createBookingMutation.mutate({
     title: form.values.title,
     residentName: form.values.residentName,
     contactInfo: form.values.contactInfo,
     additionalInfo: form.values.additionalInfo,
     pickupAddress: form.values.pickupAddress,
     destinationAddress: inputElement.current?.value || "",
-    startTime: new Date(form.values.startTime).toISOString(),
-    endTime: new Date(form.values.endTime).toISOString(),
+    startTime: startDate.toISOString(),
+    endTime: endDate.toISOString(),
     purpose: form.values.purpose,
   });

This issue was previously flagged in an earlier review but remains unaddressed.


41-51: Add query invalidation to refresh the bookings list after creation.

After a successful booking creation, the table and calendar views won't automatically display the new booking. Users must manually refresh to see the update.

🔎 Recommended fix to invalidate the bookings query

Add the useUtils() hook and invalidate the bookings query in the onSuccess handler:

+  const utils = api.useUtils();
   const createBookingMutation = api.trip.create.useMutation({
     onSuccess: () => {
       notify.success("Booking successfully created");
       form.reset();
       setShowBookingModal(false);
       setValidationAddressGood(false);
+      void utils.bookings.invalidate();
     },
     onError: (error) => {
       notify.error(error.message || "Failed to create a booking");
     },
   });

Based on past review comment from promatty on line 46.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ee91731 and fa49840.

📒 Files selected for processing (2)
  • src/app/_components/agencycomponents/agency-interactive-area.tsx
  • src/types/validation.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/types/validation.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/app/_components/agencycomponents/agency-interactive-area.tsx (3)
src/trpc/react.tsx (1)
  • api (25-25)
src/lib/notifications.ts (1)
  • notify (13-58)
src/types/validation.ts (2)
  • validateStringLength (20-37)
  • validateTimeRange (45-59)
🪛 GitHub Actions: CI
src/app/_components/agencycomponents/agency-interactive-area.tsx

[error] 24-24: File content differs from formatting output.

🔇 Additional comments (5)
src/app/_components/agencycomponents/agency-interactive-area.tsx (5)

14-14: LGTM!

The validation utility imports are correctly added and will be used for form validation throughout the component.


78-97: LGTM!

The form validation is comprehensive and correctly implements:

  • String length validation for all required text fields with appropriate max lengths
  • Optional field handling for additionalInfo
  • Time range validation ensuring endTime is after startTime
  • Clear, user-friendly field names in error messages

104-118: LGTM!

The enhanced null checks prevent potential runtime errors when setting up Google Places Autocomplete. The code now properly validates that the API is loaded, the places library is available, and the input element reference exists before initialization.


177-177: Explanation: Using ref value for Google Places integration.

The code uses inputElement.current?.value instead of form.values.destinationAddress because Google Places Autocomplete directly updates the DOM input element's value when a user selects an address from the dropdown. This happens outside of Mantine's form state management, so the ref provides access to the actual selected value. This is a standard integration pattern for Google Places with React forms.

In response to promatty's question on line 177.


240-240: LGTM!

Using createBookingMutation.isPending for the modal loading state is the correct pattern. This ensures the loading indicator accurately reflects the mutation's pending state during form submission.

Copy link
Contributor

@burtonjong burtonjong left a comment

Choose a reason for hiding this comment

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

@wesleylui

You'll have to take another look at https://github.com/Code-the-Change-YYC/salvationarmy/pull/40/changes#diff-09231f59b104026943ace40134be0e25e8f2821eb96983bf8b5e02721e2834dd

once it's merged.

You might have to merge that into your branch and fix some things.

You have to be careful with the start/end times, as there are many things that can go wrong with timestamps

Just for example:

  • Drizzle declares them in booking schema as below, but what does that really mean?
  startTime: timestamp("start_time", {
    mode: "string",
    withTimezone: true,
  }).notNull(),
  • Supabase stores timestamps as timestamptz
  • Date.prototype.toISOString() always converts the date to UTC in the standard ISO 8601 format, so that could cause problems
    • basically when the agency books the booking, we have to make sure when it's reflected on the schedule that it is in LOCAL TIME, not UTC

@burtonjong
Copy link
Contributor

Here's what I mean: {
"title": "Test",
"residentName": "Test",
"phoneNumber": "Test",
"additionalInfo": "Test",
"startTime": "2026-01-08 12:43:00",
"endTime": "2026-01-08 20:42:00",
"purpose": "Hi!",
"pickupAddress": "asdf",
"destinationAddress": "asdf"
}

it is sent like this. I don't even think this can be parsed into a valid format. You might have to change how this date is stored!

@burtonjong
Copy link
Contributor

You should be able to format it the way you want to change it to an isostring with the valueFormat prop:

https://mantine.dev/dates/date-time-picker/?t=props
image

@wesleylui wesleylui force-pushed the SANC-43-Connect-agency-booking-form-to-trip-creation-API branch from 442a47f to 92bfb7d Compare January 8, 2026 21:41
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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/_components/agencycomponents/agency-interactive-area.tsx (1)

1-17: Fix import sorting to resolve pipeline failure.

The pipeline reports that imports are not properly sorted. Run your formatter to auto-fix this issue.

#!/bin/bash
# Description: Run Biome to fix import sorting

npx @biomejs/biome check --apply src/app/_components/agencycomponents/agency-interactive-area.tsx
🤖 Fix all issues with AI agents
In @src/app/_components/agencycomponents/agency-interactive-area.tsx:
- Around line 166-177: The redundant date parsing/validation block that creates
startDate/endDate and calls form.setFieldError for "startTime" and "endTime"
should be removed because the same validation is already performed in the form's
validate function (the logic using Number.isNaN(date.getTime()) around
form.validate()). Delete the lines that construct startDate and endDate and the
subsequent if checks that call form.setFieldError("startTime", ...) and
form.setFieldError("endTime", ...), leaving any code that relies on validated
values but relying on the existing validate() path instead.
🧹 Nitpick comments (1)
src/types/validation.ts (1)

20-37: Consider more flexible error messaging for varying minLength values.

The function always returns "${fieldName} is required" when length < minLength, which works for the current usage where minLength=1 throughout the codebase. However, since this is an exported utility, future callers might use minLength > 1 and expect an error message like "${fieldName} must be at least ${minLength} characters" instead of "is required".

♻️ Proposed enhancement for more descriptive error messages
  if (trimmedValue.length < minLength) {
-   return `${fieldName} is required`;
+   return minLength === 1 
+     ? `${fieldName} is required`
+     : `${fieldName} must be at least ${minLength} characters`;
  }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 442a47f and 92bfb7d.

📒 Files selected for processing (4)
  • src/app/_components/agencycomponents/agency-interactive-area.tsx
  • src/app/_components/common/datepicker/DatePicker.tsx
  • src/app/debug/bookings/page.tsx
  • src/types/validation.ts
💤 Files with no reviewable changes (1)
  • src/app/debug/bookings/page.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/app/_components/agencycomponents/agency-interactive-area.tsx (3)
src/trpc/react.tsx (1)
  • api (25-25)
src/lib/notifications.ts (1)
  • notify (13-58)
src/types/validation.ts (2)
  • validateStringLength (20-37)
  • validateTimeRange (45-68)
🪛 GitHub Actions: CI
src/app/_components/agencycomponents/agency-interactive-area.tsx

[warning] 1-1: Imports/exports are not sorted. Organize imports.

🔇 Additional comments (5)
src/app/_components/common/datepicker/DatePicker.tsx (1)

45-45: LGTM: Stricter NaN validation.

Using Number.isNaN() instead of isNaN() is correct here since it avoids type coercion and provides stricter validation for numeric NaN values.

src/app/_components/agencycomponents/agency-interactive-area.tsx (4)

33-50: LGTM: Well-structured mutation with proper cleanup.

The mutation setup correctly handles:

  • Success notification and form reset
  • Cache invalidation to refresh the bookings list
  • Error handling with user-friendly notifications
  • Validation state cleanup

76-102: LGTM: Comprehensive form validation.

The form validation properly handles:

  • Required fields with appropriate length constraints
  • Optional fields (additionalInfo) with conditional validation
  • Date format validation before time range comparison
  • Reusable validation helpers for consistency

186-187: Datetime format appears correct; verify if PR comment concern is resolved.

The code uses toISOString() which produces ISO 8601 format (e.g., "2025-01-08T12:43:00.000Z"). However, a PR comment from @burtonjong mentioned the payload contains datetime values like "2026-01-08 12:43:00" (non-ISO format).

If the concern from that comment is still valid, there may be a mismatch elsewhere in the data flow (e.g., server-side formatting or database storage). Otherwise, this implementation is correct.


248-248: LGTM: Proper loading state integration.

Using createBookingMutation.isPending correctly ties the modal's loading indicator to the mutation state, providing user feedback during API calls.

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: 1

🤖 Fix all issues with AI agents
In @src/app/_components/agencycomponents/agency-form.tsx:
- Around line 28-40: Update the toLocalISOString helper to accept Date | string
| null (instead of Date | null) and normalize string inputs: if value is a
string, parse it into a Date (e.g., new Date(value)) or return "" for
invalid/nullable values; then format the resulting Date as before. Make sure to
update the function signature (toLocalISOString) and its internal checks so
calls from the DateTimePicker onChange handlers (where value can be Date |
string | null) no longer cause TypeScript errors.
🧹 Nitpick comments (1)
src/app/_components/agencycomponents/agency-interactive-area.tsx (1)

79-79: Minor: Inconsistent field label in validation.

The validation uses the label "Contact information" but the field is named phoneNumber. Consider using "Phone number" for consistency with the field name and UI label.

📝 Suggested change
-      phoneNumber: (value) => validateStringLength(value, 1, 150, "Contact information"),
+      phoneNumber: (value) => validateStringLength(value, 1, 150, "Phone number"),
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 92bfb7d and 3f53ba5.

📒 Files selected for processing (3)
  • src/app/_components/agencycomponents/agency-form.tsx
  • src/app/_components/agencycomponents/agency-interactive-area.tsx
  • src/server/api/routers/trip.ts
🧰 Additional context used
🧬 Code graph analysis (3)
src/server/api/routers/trip.ts (2)
src/app/debug/bookings/page.tsx (1)
  • v (32-32)
src/app/_components/agencypage/table-view.tsx (2)
  • booking (60-68)
  • transformBookingsToScheduleInfo (59-69)
src/app/_components/agencycomponents/agency-interactive-area.tsx (3)
src/trpc/react.tsx (1)
  • api (25-25)
src/types/validation.ts (2)
  • validateStringLength (20-37)
  • validateTimeRange (45-68)
src/app/debug/bookings/page.tsx (1)
  • v (30-30)
src/app/_components/agencycomponents/agency-form.tsx (1)
src/app/_components/common/datepicker/DatePicker.tsx (3)
  • DatePicker (31-83)
  • mantineValue (51-63)
  • DatePickerProps (7-14)
🪛 GitHub Actions: CI
src/app/_components/agencycomponents/agency-form.tsx

[error] 105-105: TS2345: Argument of type 'string | null' is not assignable to parameter of type 'Date | null'. (while running 'yarn tsc --noEmit')

🪛 GitHub Check: ci
src/app/_components/agencycomponents/agency-form.tsx

[failure] 105-105:
Argument of type 'string | null' is not assignable to parameter of type 'Date | null'.


[failure] 131-131:
Argument of type 'string | null' is not assignable to parameter of type 'Date | null'.

🔇 Additional comments (8)
src/server/api/routers/trip.ts (2)

12-12: LGTM! Good field separation.

Renaming contactInfo to phoneNumber provides better semantic clarity and aligns with the database schema changes.


27-28: LGTM! Cleaner data structure.

Storing phoneNumber as a separate field rather than concatenating it into passengerInfo improves data organization and makes it easier to query and display phone numbers independently.

src/app/_components/agencycomponents/agency-form.tsx (2)

102-112: Good datetime handling approach.

The valueFormat prop and local ISO string conversion avoid UTC offset issues. The logic to invalidate endTime when startTime changes is correct. This will work properly once the type issue in toLocalISOString is resolved.


127-141: LGTM! Consistent datetime handling.

The end time picker correctly enforces that it's disabled until a start time is selected and uses minDate to prevent selecting an end time before the start time. Implementation is consistent with the start time handling.

src/app/_components/agencycomponents/agency-interactive-area.tsx (4)

33-50: LGTM! Well-structured mutation with proper cleanup.

The mutation setup follows best practices:

  • Cache invalidation ensures the booking list refreshes after creation
  • Form reset and modal closure provide good UX
  • Error handling with user-friendly notifications
  • All cleanup actions (form reset, validation flag, modal state) are properly sequenced

85-98: LGTM! Comprehensive datetime validation.

The validation properly checks:

  1. Required fields
  2. Valid date format using Number.isNaN(date.getTime())
  3. End time is after start time via validateTimeRange

The use of Number.isNaN (not the global isNaN) is correct for date validation.


179-189: Verify destinationAddress source is reliable.

The mutation uses inputElement.current?.value || "" for destinationAddress (line 185). Since validationAddressGood is checked earlier (line 161), the ref should have a value. However, the fallback to empty string could silently pass an invalid address to the API.

Consider making the fallback more explicit:

🔍 Suggested defensive check
+    const destinationAddress = inputElement.current?.value;
+    if (!destinationAddress) {
+      form.setFieldError("destinationAddress", "Destination address is required");
+      return;
+    }
+
     createBookingMutation.mutate({
       title: form.values.title,
       residentName: form.values.residentName,
       phoneNumber: form.values.phoneNumber,
       additionalInfo: form.values.additionalInfo,
       pickupAddress: form.values.pickupAddress,
-      destinationAddress: inputElement.current?.value || "",
+      destinationAddress: destinationAddress,
       startTime: form.values.startTime,
       endTime: form.values.endTime,
       purpose: form.values.purpose,
     });

248-248: LGTM! Proper mutation loading state.

Using createBookingMutation.isPending directly from the mutation is cleaner than managing local loading state and automatically reflects the mutation's status.

@wesleylui
Copy link
Contributor Author

Changes:

  • Removed an extra .toISOString() conversion in the handleconfirm() of the agency-interactive-area extra that was causing the local times to convert to UTC.
  • Dates are now passed directly from form values (already in local ISO format)
  • Also added a value format of "YYYY-MM-DDTHH:mm:ss" to the DateTimePicker start and end times

Lmk if this makes sense, I am a bit confused on the Date stuff

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: 0

🧹 Nitpick comments (1)
src/app/_components/agencycomponents/agency-form.tsx (1)

28-50: Consider validating Date objects passed directly.

The function validates strings after parsing but uses Date objects directly without checking validity. If an invalid Date is passed (e.g., from external sources), it would produce "NaN-NaN-NaNTNaN:NaN:NaN".

Since DateTimePicker returns valid Date or null, this is low risk in current usage, but adding a guard improves robustness.

🔧 Suggested improvement
     } else {
       date = value;
+      if (Number.isNaN(date.getTime())) return "";
     }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f53ba5 and 62fff79.

📒 Files selected for processing (1)
  • src/app/_components/agencycomponents/agency-form.tsx
🔇 Additional comments (3)
src/app/_components/agencycomponents/agency-form.tsx (3)

137-143: minDate may not prevent selecting an earlier time on the same day.

The minDate prop restricts the calendar date selection but typically doesn't prevent picking a time earlier than startTime on that same day. Ensure form-level validation (e.g., validateTimeRange mentioned in related files) catches this case before submission.


119-121: Good use of string comparison for ISO datetime validation.

The lexicographic comparison of ISO-formatted strings (endTime <= localISOString) works correctly because the format maintains chronological order. The reset logic properly handles the edge case where a user changes startTime to be after the existing endTime.


113-113: This concern is not applicable to modern browser environments.

Modern ECMAScript (ES2016+) consistently interprets ISO date-time strings without timezone (e.g., "YYYY-MM-DDTHH:mm:ss") as local time. The code intentionally preserves local time semantics throughout—the toLocalISOString() helper function explicitly constructs the ISO string using local date components and includes a comment confirming this design. The parsing behavior on line 113 is correct and predictable across current browsers.

Likely an incorrect or invalid review comment.

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.

4 participants