Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,13 @@ After discovering the project's specific touchpoints, use a checklist like this

## Data Fetching with React Query

This project uses **@tanstack/react-query** (v5) for client-side data fetching. The `QueryClientProvider` is set up in `pages/_app.tsx`.
This project uses **@tanstack/react-query** (v5) for client-side data fetching.

> ⚠️ **Two separate provider trees.** This app mixes the **Pages Router** and the **App Router**, and they do **not** share a `QueryClientProvider`:
> - **Pages Router** (`pages/**`, customer-facing pages): the `QueryClientProvider` is set up in `pages/_app.tsx`. Components rendered here (e.g. `components/pickup/TimeslotSelector.tsx`) get a client automatically.
> - **App Router** (`app/(payload)/**`, Payload admin custom views): these render **outside** `pages/_app.tsx` and therefore have **no** QueryClient. Using `useQuery`/`useMutation` there without a provider throws `No QueryClient set, use QueryClientProvider to set one` at runtime.
>
> The Payload admin `app/(payload)/layout.tsx` is **auto-generated ("DO NOT MODIFY")**, so do not wrap it. Instead, any admin (App Router) component that uses React Query must wrap its own content in **`components/admin/AdminQueryProvider.tsx`**. The standard pattern is to split the view into an inner component (which holds the hooks) and a default export that wraps the inner component in `AdminQueryProvider` — see `components/admin/OrdersByTimeslotView.tsx`. Keep `AdminQueryProvider`'s defaults in sync with the Pages Router provider in `pages/_app.tsx`.

### Guidelines

Expand Down Expand Up @@ -99,18 +105,23 @@ This project uses **@tanstack/react-query** (v5) for client-side data fetching.

6. **Do not install `react-query`** (v3). The package is `@tanstack/react-query` (v5).

7. **Admin (App Router) views** must wrap their content in `components/admin/AdminQueryProvider.tsx` (see the "two separate provider trees" note above). Do not rely on the `pages/_app.tsx` provider for anything under `app/(payload)/**`.

### Refactoring roadmap

The following components still use raw `useEffect` + `fetch` and should be migrated to React Query as they are touched:

- `components/ordercontainer/OrderContainer.tsx` — order resumption & pending orders fetch
- `components/admin/PendingVerificationView.tsx`
- `components/admin/OrdersByTimeslotView.tsx`
- `components/admin/ScheduleCalendarView.tsx`
- `components/admin/NotifyTimeslotsView.tsx`
- `components/payment/StripePaymentForm.tsx`
- `components/payment/BankTransferForm.tsx`
- `pages/my-orders.tsx`
- `pages/order_complete.tsx`

When refactoring these components, follow the pattern established in `components/pickup/TimeslotSelector.tsx`.
Already migrated (use as references):

- ✅ `components/admin/OrdersByTimeslotView.tsx` — admin (App Router) view; wraps its default export in `AdminQueryProvider`.

When refactoring Pages Router components, follow the pattern established in `components/pickup/TimeslotSelector.tsx`. When refactoring **admin (App Router) views** (the remaining `components/admin/*` entries above), additionally wrap the view in `AdminQueryProvider` as shown in `components/admin/OrdersByTimeslotView.tsx`.
36 changes: 36 additions & 0 deletions components/admin/AdminQueryProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"use client";

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import type React from "react";
import { useState } from "react";

/**
* Provides a React Query client for Payload admin custom views.
*
* Payload admin views render inside the App Router (`app/(payload)`) tree,
* which is NOT wrapped by the `QueryClientProvider` in `pages/_app.tsx`
* (that only covers the Pages Router). Each admin view that uses React Query
* must therefore wrap its content in this provider so a QueryClient is
* available. Defaults are kept in sync with the Pages Router provider.
*/
export default function AdminQueryProvider({
children,
}: {
children: React.ReactNode;
}) {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 30 * 1000, // 30 seconds
retry: 1,
},
},
}),
);

return (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
}
18 changes: 17 additions & 1 deletion components/admin/OrdersByTimeslotView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useCallback, useRef, useState } from "react";
import type { OrderStatusValue } from "../../types/orderStatus";
import { OrderStatus, PAID_STATUSES } from "../../types/orderStatus";
import AdminQueryProvider from "./AdminQueryProvider";
import BackToDashboard from "./BackToDashboard";

interface TimeslotData {
Expand Down Expand Up @@ -83,7 +84,7 @@ async function updateOrderStatus(
* This is the view the admin uses in-person to manage pickups —
* see which orders are coming for each timeslot and mark them as picked up.
*/
export default function OrdersByTimeslotView() {
function OrdersByTimeslotViewInner() {
const [filter, setFilter] = useState<TimeslotFilter>("upcoming");
const queryClient = useQueryClient();

Expand Down Expand Up @@ -749,3 +750,18 @@ export default function OrdersByTimeslotView() {
</div>
);
}

/**
* Default export wraps the view in its own QueryClientProvider.
*
* Payload admin custom views render outside the Pages Router tree, so they
* don't inherit the QueryClient from `pages/_app.tsx`. Wrapping here makes
* the view self-sufficient.
*/
export default function OrdersByTimeslotView() {
return (
<AdminQueryProvider>
<OrdersByTimeslotViewInner />
</AdminQueryProvider>
);
}
Loading