Skip to content

Conversation

juanpprieto
Copy link
Contributor

@juanpprieto juanpprieto commented Aug 27, 2025

WHY are these changes introduced?

Fixes #3079

Screenshot 2025-08-27 at 8 50 24 AM Screenshot 2025-08-27 at 8 50 16 AM Screenshot 2025-08-27 at 9 28 40 AM

The Customer Account API 2025-07 release added support for filtering orders using a query parameter. This allows merchants and customers to search through their order history more efficiently. Previously, customers had to manually scroll through paginated results to find specific orders.

WHAT is this pull request doing?

This PR implements order filtering functionality in the skeleton template's customer account orders page:

  • Added query parameter support to the CustomerOrdersQuery GraphQL query
  • Created utility functions for building and parsing order filter parameters:
    • buildOrderSearchQuery: Constructs the query string for the GraphQL parameter
    • parseOrderFilters: Parses URL search parameters into filter objects
  • Added a search form UI with inputs for:
    • Order number (automatically strips # symbol if entered)
    • Confirmation number
  • Implemented URL parameter persistence for filters
  • Added appropriate empty states for filtered vs unfiltered scenarios
  • Added proper TypeScript types for the loader data

HOW to test your changes?

  1. Start the dev server with Customer Account Push enabled:

    cd templates/skeleton
    SHOPIFY_HYDROGEN_FLAG_CUSTOMER_ACCOUNT_PUSH=true npm run dev
  2. Use a .tryhydrogen.dev URL for OAuth to work properly

  3. Navigate to the customer account orders page (/account/orders)

  4. Test filtering:

  5. Verify:

    • Filtered results display correctly
    • URL parameters persist on navigation
    • Empty state shows when no orders match
    • Clear filters returns to full order list

⚠️ Note: This feature requires Customer Account API 2025-07. OAuth authentication is required for testing.

Post-merge steps

None required.

Checklist

  • I've read the Contributing Guidelines
  • I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • I've added a changeset if this PR contains user-facing or noteworthy changes
  • I've added tests to cover my changes
  • I've added or updated the documentation

Additional Notes

  • Styling: The search form currently uses inline styles for basic layout. This should be updated to match the template's design system in a follow-up.
  • Testing needed: This implementation should be tested with subscription orders and orders with discounts to ensure filtering works correctly in all scenarios.
  • Future improvements: Consider adding debouncing to the search inputs to reduce unnecessary re-renders.

Copy link
Contributor

shopify bot commented Aug 27, 2025

Oxygen deployed a preview of your feat-caapi-order-filtering branch. Details:

Storefront Status Preview link Deployment details Last update (UTC)
custom-cart-method ✅ Successful (Logs) Preview deployment Inspect deployment August 28, 202511:10 PM
third-party-queries-caching ✅ Successful (Logs) Preview deployment Inspect deployment August 28, 202511:10 PM
Skeleton (skeleton.hydrogen.shop) ✅ Successful (Logs) Preview deployment Inspect deployment August 28, 202511:10 PM
metaobjects ✅ Successful (Logs) Preview deployment Inspect deployment August 28, 202511:10 PM

Learn more about Hydrogen's GitHub integration.

@juanpprieto juanpprieto changed the title [2025-07] Add order filtering support for Customer Account API [2025-07] Add order filtering support to Customer Account API /account/orders route Aug 27, 2025
@juanpprieto juanpprieto requested a review from Copilot August 27, 2025 15:59
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR implements order filtering functionality for the Customer Account API using the 2025-07 version. It allows customers to filter their order history by order number and confirmation number through a search form interface.

  • Added GraphQL query parameter support for order filtering
  • Implemented order filter utility functions for parsing and building search queries
  • Created a responsive search form UI with order number and confirmation number inputs

Reviewed Changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 3 comments.

File Description
templates/skeleton/app/routes/account.orders._index.tsx Added search form component and integrated filter functionality with URL parameter persistence
templates/skeleton/app/lib/orderFilters.ts Created utility functions for parsing URL parameters and building GraphQL query strings
templates/skeleton/app/graphql/customer-account/CustomerOrdersQuery.ts Added query parameter to the GraphQL orders query
templates/skeleton/app/styles/app.css Added CSS styles for the order search form with responsive grid layout

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@juanpprieto juanpprieto marked this pull request as ready for review August 27, 2025 16:30
@juanpprieto juanpprieto requested a review from kdaviduik August 27, 2025 16:31
Base automatically changed from hydrogen-customer-account-flow-debug to 2025-07-sfapi-caapi-update August 28, 2025 19:59
@kdaviduik kdaviduik requested a review from a team as a code owner August 28, 2025 19:59
Implement new order filtering capabilities using the query parameter
introduced in Customer Account API version 2025-07. Customers can now
filter orders by name (order number) and confirmation number.

Changes:
- Add query parameter to CustomerOrdersQuery GraphQL fragments
- Create orderFilters utility functions for building search queries
- Add search form UI to orders page with inputs for filters
- Update empty state to handle filtered vs unfiltered scenarios
- Persist filter state in URL parameters for bookmarking

The implementation follows the new filtering syntax:
- name:1001 for order number filtering
- confirmation_number:ABC123 for confirmation number filtering
- Combined filters using AND operator
- Simplified HTML structure using semantic fieldset/legend elements
- Removed inline styles in favor of CSS classes
- Added responsive grid layout (single column mobile, two column desktop)
- Improved accessibility with proper ARIA labels and search input types
- Reduced visual complexity while maintaining full functionality
- Minimal CSS that allows easy customization by developers
- Fix duplicate empty state logic by passing filters to OrdersTable
- Add input sanitization to prevent injection attacks
- Extract field name constants for maintainability
- Remove hardcoded string repetition throughout component
@kdaviduik kdaviduik force-pushed the feat-caapi-order-filtering branch from 5b89689 to ed0c8b5 Compare August 28, 2025 23:08
/**
* Field name constants for order filtering
*/
export const ORDER_FILTER_FIELDS = {
Copy link
Contributor

Choose a reason for hiding this comment

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

FYI there are WAY more filter fields than just these 2 https://shopify.dev/docs/api/customer/latest/queries/customer#returns-Customer.fields.orders.arguments.query. We don't need to add support for all filters in this PR though (or for this release).

* @param value - The input string to sanitize
* @returns The sanitized string
*/
function sanitizeFilterValue(value: string): string {
Copy link
Contributor

Choose a reason for hiding this comment

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

Imo we should definitely have unit tests for all of this stuff as part of this PR.

In terms of the end state, this is something that's a pain to manually test and these will very objectively be "working" or "not working" with the API (which simpler tests can't guarantee), so I think we absolutely should add E2E tests for stuff like this. Doesn't need to happen as part of this PR/release though

function sanitizeFilterValue(value: string): string {
// Only allow alphanumeric, underscore, and dash
// Remove anything else to prevent injection
return value.replace(/[^a-zA-Z0-9_\-]/g, '');
Copy link
Contributor

Choose a reason for hiding this comment

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

This specific regex for sanitation seems 👍🏻

Claude's verification

Verification Results: Current Sanitization is SAFE ✅

Based on my comprehensive research and testing:

Valid Characters in Shopify Fields:

  1. Order Names: #1001, Store1001

    • Uses: letters, numbers, hash (which is stripped separately)
  2. Confirmation Numbers: XPAV284CT, R50KELTJP, 35PKUN0UJ

    • Uses: uppercase letters and numbers only
  3. Order Numbers: Pure integers like 1001

Current Sanitization (/[^a-zA-Z0-9_\-]/g):

Preserves all valid Shopify formats

  • Keeps alphanumeric (covers all real examples)
  • Keeps underscore and dash (for extended formats like ORD-2024-001)
  • Hash (#) is handled separately by stripping before sanitization

Blocks ALL injection attempts

  • Removes quotes, backslashes, brackets
  • Removes SQL/GraphQL operators (:, >, <, =)
  • Removes template syntax (${}, {{}})
  • Removes all XSS vectors

Key Insight:

The sanitization appears "too restrictive" because it removes colons, BUT that's actually correct! The colons in name:1001 are part of the query syntax, not the user input. The code correctly:

  1. Sanitizes user input: "1001""1001"
  2. Then builds query: "name:" + "1001""name:1001"

Conclusion:

The current implementation is production-ready and secure. It:

  • ✅ Works with all documented Shopify formats
  • ✅ Prevents GraphQL injection effectively
  • ✅ Follows defense-in-depth principles

The only minor limitation is removing spaces from order names, but Shopify's actual order formats don't use spaces in the examples, so this is acceptable.

Filter Orders
{hasFilters && (
<small className="order-search-active">
({[currentFilters.name, currentFilters.confirmationNumber].filter(Boolean).length} active)
Copy link
Contributor

Choose a reason for hiding this comment

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

This "<number> active" UX is really confusing imo - even if I delete the content from both filters, it doesn't update. I would personally just delete this

Image

</div>
);
}

function OrdersTable({orders}: Pick<CustomerOrdersFragment, 'orders'>) {
function OrdersTable({orders, filters}: {orders: CustomerOrdersFragment['orders']; filters: OrderFilterParams}) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We should definitely have an aria-live: polite on this for accessibility

<div className="order-search-buttons">
<button type="submit">Search</button>
{hasFilters && (
<button
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this button should also clear the field inputs (in addition to or instead of clearing the URL query params), otherwise the UX is confusing

Image

</div>

<div className="order-search-buttons">
<button type="submit">Search</button>
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't want to be too picky about the UX but I think very basic "loading" state functionality should be here as part of this PR. I like the loading UX for the "Profile" form where it simply disables the button + adds "ing" to it while loading. I think we should copy that here

/** Order name or number (e.g., "#1001" or "1001") */
name?: string;
/** Order confirmation number */
confirmationNumber?: string;
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't seem to be querying or, therefore, displaying the order confirmation number. As a result, it's confusing that we are offering the option to filter by this.

Personally I think we should delete this option and just have name as a filter param for now. I'd also be fine with replacing this with a different filter param that we're rendering (or start querying + rendering the order confirmation number and keep this filter param), but deleting this is the simplest option for now. Up to you.

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.

2 participants