split export

This commit is contained in:
Marshal
2026-07-18 09:12:52 +00:00
parent 6100a121d2
commit 406fbf6c45
14 changed files with 1092 additions and 41 deletions

View File

@@ -15,10 +15,23 @@ import {
} from "./cookies";
import type { AuthTokens } from "./types";
declare module "axios" {
export interface AxiosRequestConfig {
/**
* When true, the response interceptor does NOT raise the global error modal
* for this request's failure. For calls the caller handles itself — e.g. a
* probe that is expected to 404 before falling back (GL clearance detail
* tries /contracts/:id then /bookings/:id). The rejection still propagates.
*/
suppressErrorModal?: boolean;
}
}
type RetriableRequest = {
_retry?: boolean;
headers?: Record<string, string>;
url?: string;
suppressErrorModal?: boolean;
};
const api = axios.create({
@@ -100,8 +113,13 @@ api.interceptors.response.use(
originalRequest.url?.includes("/auth/refresh-token")
) {
// Surface the server's actual error message in the global error modal
// (401s are handled by the session-refresh flow, so skip them).
if (error.response && error.response.status !== 401) {
// (401s are handled by the session-refresh flow, so skip them). A request
// may opt out via `suppressErrorModal` when it handles the failure itself.
if (
error.response &&
error.response.status !== 401 &&
!originalRequest?.suppressErrorModal
) {
const payload = extractApiErrorPayload(error);
if (payload) emitApiError(payload);
}

View File

@@ -1,6 +1,6 @@
import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { useNavigate, useParams } from "react-router-dom";
import { useParams } from "react-router-dom";
import {
Alert,
Badge,
@@ -18,7 +18,6 @@ import {
AlertCircle,
ClipboardList,
FileText,
PackagePlus,
Upload,
} from "lucide-react";
import type { Freight } from "@edr/types";
@@ -61,9 +60,12 @@ type GlClearanceDetail =
async function loadGlClearanceDetail(id: string): Promise<GlClearanceDetail> {
try {
// Probe the contract endpoints first; a booking-id row 404s here by design
// and falls back to the booking lookup below. Suppress the global error
// modal so that expected 404 never surfaces to the user.
const [clearance, contract] = await Promise.all([
contractsService.getClearance(id),
contractsService.getById(id),
contractsService.getClearance(id, { suppressErrorModal: true }),
contractsService.getById(id, { suppressErrorModal: true }),
]);
return {
kind: "contract",
@@ -89,7 +91,6 @@ async function loadGlClearanceDetail(id: string): Promise<GlClearanceDetail> {
/** Djibouti GL clearance detail — RO/DO upload and read-only upstream context. */
export default function GlClearanceDetailPage() {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const { user } = useAuth();
const { view, viewer } = useFileViewer();
const [uploadKind, setUploadKind] = useState<GlClearanceUploadKind | null>(null);
@@ -197,19 +198,6 @@ export default function GlClearanceDetailPage() {
{hasRo ? "Replace RO" : "Upload RO"}
</Button>
)}
{canCompleteBooking && shipmentBooking ? (
<Button
color="edr-green"
leftSection={<PackagePlus size={16} />}
onClick={() =>
navigate(
`/dashboard/contracts/${shipmentBooking.contractId}/bookings/${id}/complete`,
)
}
>
Create booking
</Button>
) : null}
</Group>
}
/>

View File

@@ -164,8 +164,11 @@ export const contractsService = {
};
},
getById: async (id: string): Promise<Freight.IContract> => {
const response = await client.get<Freight.IContract>(C.BY_ID(id));
getById: async (
id: string,
opts?: { suppressErrorModal?: boolean },
): Promise<Freight.IContract> => {
const response = await client.get<Freight.IContract>(C.BY_ID(id), opts);
return unwrap(response.data) as Freight.IContract;
},
@@ -258,8 +261,11 @@ export const contractsService = {
};
},
getClearance: async (id: string): Promise<Freight.ContractClearanceView> => {
const response = await client.get(C.CLEARANCE(id));
getClearance: async (
id: string,
opts?: { suppressErrorModal?: boolean },
): Promise<Freight.ContractClearanceView> => {
const response = await client.get(C.CLEARANCE(id), opts);
return unwrap(response.data) as Freight.ContractClearanceView;
},