@voyant-travel/bookings-react 0.199.0 → 0.200.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/booking-documents-table.js +9 -62
- package/dist/admin/index.d.ts +4 -5
- package/dist/hooks/index.d.ts +0 -1
- package/dist/hooks/index.js +0 -1
- package/dist/schemas.d.ts +0 -24
- package/dist/schemas.js +0 -15
- package/dist/storefront/resolve-contract-variables.d.ts +4 -5
- package/dist/storefront/resolve-contract-variables.js +2 -3
- package/dist/storefront/storefront-checkout-bodies.d.ts +1 -1
- package/package.json +38 -38
- package/dist/admin/booking-contract-dialog.d.ts +0 -21
- package/dist/admin/booking-contract-dialog.js +0 -171
- package/dist/hooks/use-booking-contract-generation.d.ts +0 -30
- package/dist/hooks/use-booking-contract-generation.js +0 -36
|
@@ -1,41 +1,16 @@
|
|
|
1
1
|
// agent-quality: file-size exception -- owner: bookings-react; existing UI surface stays co-located until a dedicated split preserves behavior and tests.
|
|
2
2
|
"use client";
|
|
3
3
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
4
|
-
import { useQueryClient } from "@tanstack/react-query";
|
|
5
4
|
import { useAdminNavigate, useLocale, useOperatorAdminMessages } from "@voyant-travel/admin";
|
|
6
|
-
import {
|
|
5
|
+
import { useLegalContractAttachments, useLegalContracts, useVoyantLegalContext, } from "@voyant-travel/legal-react";
|
|
7
6
|
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Badge, Button, } from "@voyant-travel/ui/components";
|
|
8
7
|
import { DataTable } from "@voyant-travel/ui/components/data-table";
|
|
9
|
-
import { ArrowUpRight, Download, FileText, Loader2, Plus,
|
|
8
|
+
import { ArrowUpRight, Download, FileText, Loader2, Plus, Trash2 } from "lucide-react";
|
|
10
9
|
import { useMemo, useState } from "react";
|
|
11
10
|
import { BookingDocumentDialog } from "../components/booking-document-dialog.js";
|
|
12
11
|
import { IconActionButton } from "../components/icon-action-button.js";
|
|
13
12
|
import { StatusBadge } from "../components/status-badge.js";
|
|
14
|
-
import {
|
|
15
|
-
import { BookingContractDialog } from "./booking-contract-dialog.js";
|
|
16
|
-
const CONTRACT_GENERATION_FAILURE_LABELS = {
|
|
17
|
-
render_unavailable: "contractGenerationTemplateError",
|
|
18
|
-
generator_failed: "contractGenerationGeneratorFailed",
|
|
19
|
-
};
|
|
20
|
-
function resolveContractGenerationFailure(contract) {
|
|
21
|
-
const metadata = contract.metadata;
|
|
22
|
-
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
const status = metadata.lastGenerationStatus;
|
|
26
|
-
if (typeof status !== "string" || status === "generated") {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
return {
|
|
30
|
-
status,
|
|
31
|
-
error: typeof metadata.lastGenerationError === "string" && metadata.lastGenerationError.trim()
|
|
32
|
-
? metadata.lastGenerationError
|
|
33
|
-
: null,
|
|
34
|
-
attemptedAt: typeof metadata.lastGenerationAttemptedAt === "string"
|
|
35
|
-
? metadata.lastGenerationAttemptedAt
|
|
36
|
-
: null,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
13
|
+
import { useBookingTravelerDocumentMutation, useBookingTravelerDocuments, useTravelers, } from "../index.js";
|
|
39
14
|
/**
|
|
40
15
|
* Unified Documents tab for a booking — flattens auto-generated legal
|
|
41
16
|
* contracts and per-traveler documents (passport, visa, insurance…) into
|
|
@@ -47,11 +22,8 @@ function resolveContractGenerationFailure(contract) {
|
|
|
47
22
|
export function BookingDocumentsTable({ bookingId, }) {
|
|
48
23
|
const t = useOperatorAdminMessages().bookings.detail.documentsTable;
|
|
49
24
|
const [uploadOpen, setUploadOpen] = useState(false);
|
|
50
|
-
const [contractDialogOpen, setContractDialogOpen] = useState(false);
|
|
51
25
|
const [deleteTarget, setDeleteTarget] = useState(null);
|
|
52
26
|
const [deletePending, setDeletePending] = useState(false);
|
|
53
|
-
const bookingQuery = useBooking(bookingId);
|
|
54
|
-
const booking = bookingQuery.data?.data ?? null;
|
|
55
27
|
const contractsQuery = useLegalContracts({ bookingId, limit: 25 });
|
|
56
28
|
const contracts = contractsQuery.data?.data ?? [];
|
|
57
29
|
const travelerDocsQuery = useBookingTravelerDocuments(bookingId);
|
|
@@ -59,7 +31,7 @@ export function BookingDocumentsTable({ bookingId, }) {
|
|
|
59
31
|
const travelersQuery = useTravelers(bookingId);
|
|
60
32
|
const travelersById = useMemo(() => new Map((travelersQuery.data?.data ?? []).map((tr) => [tr.id, tr])), [travelersQuery.data]);
|
|
61
33
|
const removeTravelerDoc = useBookingTravelerDocumentMutation(bookingId).remove;
|
|
62
|
-
const isLoading =
|
|
34
|
+
const isLoading = contractsQuery.isLoading || travelerDocsQuery.isLoading;
|
|
63
35
|
const rows = useMemo(() => [
|
|
64
36
|
...contracts.map((contract) => ({
|
|
65
37
|
kind: "contract",
|
|
@@ -132,7 +104,7 @@ export function BookingDocumentsTable({ bookingId, }) {
|
|
|
132
104
|
setDeletePending(false);
|
|
133
105
|
}
|
|
134
106
|
};
|
|
135
|
-
return (_jsxs("div", { "data-slot": "booking-documents-list", className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("h2", { className: "flex items-center gap-2 text-base font-semibold", children: [_jsx(FileText, { className: "h-4 w-4 text-muted-foreground" }), t.title] }),
|
|
107
|
+
return (_jsxs("div", { "data-slot": "booking-documents-list", className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("h2", { className: "flex items-center gap-2 text-base font-semibold", children: [_jsx(FileText, { className: "h-4 w-4 text-muted-foreground" }), t.title] }), _jsx("div", { className: "flex items-center gap-2", children: _jsxs(Button, { size: "sm", variant: "outline", onClick: () => setUploadOpen(true), children: [_jsx(Plus, { className: "mr-1 h-3.5 w-3.5" }), t.uploadDocument] }) })] }), isLoading ? (_jsxs("div", { className: "flex items-center justify-center gap-2 py-6 text-muted-foreground text-sm", children: [_jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }), t.loading] })) : (_jsx(DataTable, { columns: columns, data: rows, emptyMessage: t.empty, showPagination: false })), _jsx(BookingDocumentDialog, { open: uploadOpen, onOpenChange: setUploadOpen, bookingId: bookingId }), _jsx(AlertDialog, { open: Boolean(deleteTarget), onOpenChange: (next) => {
|
|
136
108
|
if (!next && !deletePending)
|
|
137
109
|
setDeleteTarget(null);
|
|
138
110
|
}, children: _jsxs(AlertDialogContent, { size: "sm", children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: t.deleteConfirm }), deleteTarget ? (_jsx(AlertDialogDescription, { children: deleteTarget.fileName })) : null] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { disabled: deletePending, children: t.deleteCancel }), _jsx(AlertDialogAction, { variant: "destructive", disabled: deletePending, onClick: () => void handleDeleteConfirm(), children: t.deleteConfirmAction })] })] }) })] }));
|
|
@@ -159,15 +131,7 @@ function ContractDocumentCell({ contract }) {
|
|
|
159
131
|
function TravelerDocumentCell({ doc }) {
|
|
160
132
|
return (_jsxs("a", { href: doc.fileUrl, target: "_blank", rel: "noopener noreferrer", className: "inline-flex items-center gap-1.5 text-primary hover:underline", children: [_jsx(FileText, { className: "h-3.5 w-3.5 shrink-0 opacity-60" }), _jsx("span", { className: "truncate", children: doc.fileName }), _jsx(ArrowUpRight, { className: "h-3 w-3" })] }));
|
|
161
133
|
}
|
|
162
|
-
function ContractStatusCell({ contract,
|
|
163
|
-
const generationFailure = resolveContractGenerationFailure(contract);
|
|
164
|
-
if (generationFailure) {
|
|
165
|
-
const failureLabelKey = CONTRACT_GENERATION_FAILURE_LABELS[generationFailure.status];
|
|
166
|
-
const failureLabel = failureLabelKey
|
|
167
|
-
? messages[failureLabelKey]
|
|
168
|
-
: messages.contractGenerationFailed;
|
|
169
|
-
return (_jsxs("div", { className: "max-w-80 space-y-1", children: [_jsx(StatusBadge, { status: "failed", children: failureLabel }), _jsx("p", { className: "text-muted-foreground text-xs", children: generationFailure.error ?? messages.contractGenerationErrorFallback })] }));
|
|
170
|
-
}
|
|
134
|
+
function ContractStatusCell({ contract, }) {
|
|
171
135
|
return _jsx(StatusBadge, { status: contract.status, children: contract.status.replace(/_/g, " ") });
|
|
172
136
|
}
|
|
173
137
|
function TravelerStatusCell({ doc, messages, }) {
|
|
@@ -178,16 +142,11 @@ function TravelerStatusCell({ doc, messages, }) {
|
|
|
178
142
|
}
|
|
179
143
|
function ContractDateCell({ contract, messages, }) {
|
|
180
144
|
const { resolvedLocale } = useLocale();
|
|
181
|
-
const generationFailure = resolveContractGenerationFailure(contract);
|
|
182
145
|
const attachmentsQuery = useLegalContractAttachments({ contractId: contract.id });
|
|
183
146
|
const attachments = (attachmentsQuery.data ?? []).filter((a) => a.kind === "document");
|
|
184
147
|
const hasDocument = attachments.length > 0;
|
|
185
|
-
const dateIso =
|
|
186
|
-
const dateLabel =
|
|
187
|
-
? messages.contractGenerationAttemptedLabel
|
|
188
|
-
: hasDocument
|
|
189
|
-
? messages.contractIssuedLabel
|
|
190
|
-
: messages.contractPendingSinceLabel;
|
|
148
|
+
const dateIso = contract.issuedAt ?? contract.createdAt ?? null;
|
|
149
|
+
const dateLabel = hasDocument ? messages.contractIssuedLabel : messages.contractPendingSinceLabel;
|
|
191
150
|
if (!dateIso)
|
|
192
151
|
return _jsx("span", { className: "text-muted-foreground text-xs", children: "\u2014" });
|
|
193
152
|
return (_jsxs("span", { className: "text-muted-foreground text-xs", children: [_jsxs("span", { className: "opacity-60", children: [dateLabel, " "] }), formatDate(dateIso, resolvedLocale)] }));
|
|
@@ -201,30 +160,18 @@ function TravelerDateCell({ doc, messages, }) {
|
|
|
201
160
|
return (_jsxs("span", { className: "text-muted-foreground text-xs", children: [_jsxs("span", { className: "opacity-60", children: [dateLabel, " "] }), formatDate(dateIso, resolvedLocale)] }));
|
|
202
161
|
}
|
|
203
162
|
function ContractActionsCell({ contract, messages, }) {
|
|
204
|
-
const queryClient = useQueryClient();
|
|
205
163
|
const navigateTo = useAdminNavigate();
|
|
206
164
|
const attachmentsQuery = useLegalContractAttachments({ contractId: contract.id });
|
|
207
165
|
const attachments = (attachmentsQuery.data ?? []).filter((a) => a.kind === "document");
|
|
208
166
|
const latest = attachments[0] ?? null;
|
|
209
|
-
const hasDocument = latest !== null;
|
|
210
167
|
const downloadHref = useContractAttachmentDownloadHref(latest);
|
|
211
|
-
const { generate } = useBookingContractGenerationMutation(contract.bookingId ?? "");
|
|
212
168
|
return (_jsxs("div", { className: "flex items-center justify-end gap-1", children: [_jsx(IconActionButton, { label: messages.contractOpenTooltip, icon: _jsx(ArrowUpRight, { className: "h-3.5 w-3.5" }), onClick: (e) => {
|
|
213
169
|
e.stopPropagation();
|
|
214
170
|
navigateTo("contract.detail", { contractId: contract.id });
|
|
215
171
|
} }), downloadHref ? (_jsx(IconActionButton, { label: messages.downloadDocumentAria, icon: _jsx(Download, { className: "h-3.5 w-3.5" }), onClick: (e) => {
|
|
216
172
|
e.stopPropagation();
|
|
217
173
|
window.open(downloadHref, "_blank", "noopener,noreferrer");
|
|
218
|
-
} })) : null
|
|
219
|
-
e.stopPropagation();
|
|
220
|
-
if (!contract.bookingId)
|
|
221
|
-
return;
|
|
222
|
-
generate.mutate({ force: hasDocument }, {
|
|
223
|
-
onSuccess: () => {
|
|
224
|
-
void queryClient.invalidateQueries({ queryKey: legalQueryKeys.contracts() });
|
|
225
|
-
},
|
|
226
|
-
});
|
|
227
|
-
} })] }));
|
|
174
|
+
} })) : null] }));
|
|
228
175
|
}
|
|
229
176
|
function TravelerActionsCell({ doc, messages, onDelete, }) {
|
|
230
177
|
return (_jsxs("div", { className: "flex items-center justify-end gap-1", children: [_jsx(IconActionButton, { label: messages.downloadDocumentAria, icon: _jsx(Download, { className: "h-3.5 w-3.5" }), onClick: (e) => {
|
package/dist/admin/index.d.ts
CHANGED
|
@@ -73,7 +73,6 @@ declare module "@voyant-travel/admin" {
|
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
-
export type { BookingContractDialogProps } from "./booking-contract-dialog.js";
|
|
77
76
|
export type { BookingDetailHostProps, BookingDetailHostSlot, BookingDetailHostSlotContext, BookingDetailHostSlots, BookingDetailPaymentActions, BookingDetailPaymentControllerSlotContext, } from "./booking-detail-host.js";
|
|
78
77
|
export { BookingDetailSkeleton } from "./booking-detail-skeleton.js";
|
|
79
78
|
export type { BookingDocumentsTableProps } from "./booking-documents-table.js";
|
|
@@ -135,9 +134,9 @@ export declare const bookingDetailTabSchema: z.ZodEnum<{
|
|
|
135
134
|
travelers: "travelers";
|
|
136
135
|
activity: "activity";
|
|
137
136
|
documents: "documents";
|
|
138
|
-
suppliers: "suppliers";
|
|
139
|
-
invoices: "invoices";
|
|
140
137
|
finance: "finance";
|
|
138
|
+
invoices: "invoices";
|
|
139
|
+
suppliers: "suppliers";
|
|
141
140
|
}>;
|
|
142
141
|
/**
|
|
143
142
|
* Search contract for the booking detail page. `productId`/`slotId` only
|
|
@@ -153,9 +152,9 @@ export declare const bookingDetailSearchSchema: z.ZodObject<{
|
|
|
153
152
|
travelers: "travelers";
|
|
154
153
|
activity: "activity";
|
|
155
154
|
documents: "documents";
|
|
156
|
-
suppliers: "suppliers";
|
|
157
|
-
invoices: "invoices";
|
|
158
155
|
finance: "finance";
|
|
156
|
+
invoices: "invoices";
|
|
157
|
+
suppliers: "suppliers";
|
|
159
158
|
}>>;
|
|
160
159
|
}, z.core.$strip>;
|
|
161
160
|
export type BookingDetailSearchParams = z.infer<typeof bookingDetailSearchSchema>;
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ export { type UseBookingOptions, useBooking } from "./use-booking.js";
|
|
|
2
2
|
export { type UseBookingActionLedgerOptions, useBookingActionLedger, } from "./use-booking-action-ledger.js";
|
|
3
3
|
export { type UseBookingActivityOptions, useBookingActivity } from "./use-booking-activity.js";
|
|
4
4
|
export { type CancelBookingInput, useBookingCancelMutation, } from "./use-booking-cancel-mutation.js";
|
|
5
|
-
export { type GenerateBookingContractInput, useBookingContractGenerationMutation, } from "./use-booking-contract-generation.js";
|
|
6
5
|
export { type ConvertProductToBookingInput, useBookingConvertMutation, } from "./use-booking-convert-mutation.js";
|
|
7
6
|
export { type BookingCreateExtraLineInput, type BookingCreateGroupMembershipInput, type BookingCreateInput, type BookingCreateItemLineInput, type BookingCreatePaymentScheduleInput, type BookingCreateResult, type BookingCreateTravelCreditRedemptionInput, type BookingCreateTravelerInput, useBookingCreateMutation, } from "./use-booking-create-mutation.js";
|
|
8
7
|
export { type CreateBookingTravelerDocumentInput, type UseBookingTravelerDocumentsOptions, useBookingTravelerDocumentMutation, useBookingTravelerDocuments, } from "./use-booking-documents.js";
|
package/dist/hooks/index.js
CHANGED
|
@@ -2,7 +2,6 @@ export { useBooking } from "./use-booking.js";
|
|
|
2
2
|
export { useBookingActionLedger, } from "./use-booking-action-ledger.js";
|
|
3
3
|
export { useBookingActivity } from "./use-booking-activity.js";
|
|
4
4
|
export { useBookingCancelMutation, } from "./use-booking-cancel-mutation.js";
|
|
5
|
-
export { useBookingContractGenerationMutation, } from "./use-booking-contract-generation.js";
|
|
6
5
|
export { useBookingConvertMutation, } from "./use-booking-convert-mutation.js";
|
|
7
6
|
export { useBookingCreateMutation, } from "./use-booking-create-mutation.js";
|
|
8
7
|
export { useBookingTravelerDocumentMutation, useBookingTravelerDocuments, } from "./use-booking-documents.js";
|
package/dist/schemas.d.ts
CHANGED
|
@@ -2436,27 +2436,3 @@ export declare const bookingActionLedgerListResponse: z.ZodObject<{
|
|
|
2436
2436
|
}, z.core.$strip>;
|
|
2437
2437
|
}, z.core.$strip>;
|
|
2438
2438
|
export type BookingActionLedgerListResult = z.infer<typeof bookingActionLedgerListResponse>;
|
|
2439
|
-
export declare const bookingContractPreviewSchema: z.ZodObject<{
|
|
2440
|
-
html: z.ZodString;
|
|
2441
|
-
templateName: z.ZodOptional<z.ZodString>;
|
|
2442
|
-
templateLanguage: z.ZodOptional<z.ZodString>;
|
|
2443
|
-
}, z.core.$strip>;
|
|
2444
|
-
export type BookingContractPreview = z.infer<typeof bookingContractPreviewSchema>;
|
|
2445
|
-
export declare const bookingContractPreviewResponse: z.ZodObject<{
|
|
2446
|
-
data: z.ZodObject<{
|
|
2447
|
-
html: z.ZodString;
|
|
2448
|
-
templateName: z.ZodOptional<z.ZodString>;
|
|
2449
|
-
templateLanguage: z.ZodOptional<z.ZodString>;
|
|
2450
|
-
}, z.core.$strip>;
|
|
2451
|
-
}, z.core.$strip>;
|
|
2452
|
-
export declare const bookingGenerateContractResultSchema: z.ZodObject<{
|
|
2453
|
-
contractId: z.ZodString;
|
|
2454
|
-
attachmentId: z.ZodString;
|
|
2455
|
-
}, z.core.$strip>;
|
|
2456
|
-
export type BookingGenerateContractResult = z.infer<typeof bookingGenerateContractResultSchema>;
|
|
2457
|
-
export declare const bookingGenerateContractResponse: z.ZodObject<{
|
|
2458
|
-
data: z.ZodObject<{
|
|
2459
|
-
contractId: z.ZodString;
|
|
2460
|
-
attachmentId: z.ZodString;
|
|
2461
|
-
}, z.core.$strip>;
|
|
2462
|
-
}, z.core.$strip>;
|
package/dist/schemas.js
CHANGED
|
@@ -513,18 +513,3 @@ export const bookingActionLedgerListResponse = z.object({
|
|
|
513
513
|
nextCursor: bookingActionLedgerCursorSchema.nullable(),
|
|
514
514
|
}),
|
|
515
515
|
});
|
|
516
|
-
// Contract generation — `POST /v1/admin/bookings/:id/generate-contract`.
|
|
517
|
-
// The route renders the booking's contract template; `{ preview: true }`
|
|
518
|
-
// returns the rendered HTML without persisting anything, the default
|
|
519
|
-
// call creates the legal contract row + persists the PDF attachment.
|
|
520
|
-
export const bookingContractPreviewSchema = z.object({
|
|
521
|
-
html: z.string(),
|
|
522
|
-
templateName: z.string().optional(),
|
|
523
|
-
templateLanguage: z.string().optional(),
|
|
524
|
-
});
|
|
525
|
-
export const bookingContractPreviewResponse = singleEnvelope(bookingContractPreviewSchema);
|
|
526
|
-
export const bookingGenerateContractResultSchema = z.object({
|
|
527
|
-
contractId: z.string(),
|
|
528
|
-
attachmentId: z.string(),
|
|
529
|
-
});
|
|
530
|
-
export const bookingGenerateContractResponse = singleEnvelope(bookingGenerateContractResultSchema);
|
|
@@ -27,9 +27,8 @@
|
|
|
27
27
|
* - The booking row has not been created yet — `booking.bookingId`,
|
|
28
28
|
* `booking.bookingNumber`, the persisted status, and
|
|
29
29
|
* `contract.contractNumber` / `contract.signedAt` are empty. The
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* filled in.
|
|
30
|
+
* a later Legal-owned document command uses the contract's persisted,
|
|
31
|
+
* immutable rendered body.
|
|
33
32
|
* - `acceptance.ipAddress` / `userAgent` are captured server-side
|
|
34
33
|
* at `/checkout/start` from request headers. Empty during preview.
|
|
35
34
|
* - `operator.*` reads from the `operatorInfo` block injected by the
|
|
@@ -107,8 +106,8 @@ export interface ResolveContractVariablesContext {
|
|
|
107
106
|
/** Operator profile — fetched from `/v1/public/operator-profile` by
|
|
108
107
|
* the storefront wrapper. Anything missing renders as empty. */
|
|
109
108
|
operatorInfo?: OperatorInfoVariables;
|
|
110
|
-
/** Acceptance fingerprint — populated only on server-side
|
|
111
|
-
*
|
|
109
|
+
/** Acceptance fingerprint — populated only on server-side persistence.
|
|
110
|
+
* At preview time the
|
|
112
111
|
* storefront leaves this undefined and the variables render
|
|
113
112
|
* empty. */
|
|
114
113
|
acceptance?: AcceptanceContextVariables;
|
|
@@ -28,9 +28,8 @@
|
|
|
28
28
|
* - The booking row has not been created yet — `booking.bookingId`,
|
|
29
29
|
* `booking.bookingNumber`, the persisted status, and
|
|
30
30
|
* `contract.contractNumber` / `contract.signedAt` are empty. The
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* filled in.
|
|
31
|
+
* a later Legal-owned document command uses the contract's persisted,
|
|
32
|
+
* immutable rendered body.
|
|
34
33
|
* - `acceptance.ipAddress` / `userAgent` are captured server-side
|
|
35
34
|
* at `/checkout/start` from request headers. Empty during preview.
|
|
36
35
|
* - `operator.*` reads from the `operatorInfo` block injected by the
|
|
@@ -31,6 +31,6 @@ export declare function buildStorefrontCheckoutStartBody(input: {
|
|
|
31
31
|
renderedHtml: string;
|
|
32
32
|
} | undefined;
|
|
33
33
|
bookingId: string;
|
|
34
|
-
paymentIntent: "hold" | "
|
|
34
|
+
paymentIntent: "hold" | "bank_transfer" | "card" | "inquiry";
|
|
35
35
|
};
|
|
36
36
|
export declare function buildStorefrontCommitParty(draft: Draft): Record<string, unknown>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voyant-travel/bookings-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.200.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -151,23 +151,23 @@
|
|
|
151
151
|
"react-hook-form": "^7.80.0",
|
|
152
152
|
"zod": "^4.0.0",
|
|
153
153
|
"@voyant-travel/admin": "^0.129.1",
|
|
154
|
-
"@voyant-travel/
|
|
155
|
-
"@voyant-travel/
|
|
156
|
-
"@voyant-travel/catalog
|
|
157
|
-
"@voyant-travel/
|
|
158
|
-
"@voyant-travel/
|
|
159
|
-
"@voyant-travel/
|
|
160
|
-
"@voyant-travel/
|
|
161
|
-
"@voyant-travel/finance-react": "^0.
|
|
162
|
-
"@voyant-travel/identity-react": "^0.
|
|
163
|
-
"@voyant-travel/
|
|
164
|
-
"@voyant-travel/
|
|
165
|
-
"@voyant-travel/
|
|
166
|
-
"@voyant-travel/
|
|
167
|
-
"@voyant-travel/inventory": "^0.19.
|
|
168
|
-
"@voyant-travel/storefront-react": "^0.
|
|
169
|
-
"@voyant-travel/ui": "^0.109.
|
|
170
|
-
"@voyant-travel/operations-react": "^0.
|
|
154
|
+
"@voyant-travel/accommodations": "^0.160.0",
|
|
155
|
+
"@voyant-travel/bookings": "^0.200.0",
|
|
156
|
+
"@voyant-travel/catalog": "^0.198.0",
|
|
157
|
+
"@voyant-travel/catalog-react": "^0.198.0",
|
|
158
|
+
"@voyant-travel/relationships-react": "^0.200.0",
|
|
159
|
+
"@voyant-travel/distribution-react": "^0.190.0",
|
|
160
|
+
"@voyant-travel/finance": "^0.200.0",
|
|
161
|
+
"@voyant-travel/finance-react": "^0.200.0",
|
|
162
|
+
"@voyant-travel/identity-react": "^0.200.0",
|
|
163
|
+
"@voyant-travel/legal-react": "^0.200.0",
|
|
164
|
+
"@voyant-travel/commerce-react": "^0.82.0",
|
|
165
|
+
"@voyant-travel/cruises": "^0.199.0",
|
|
166
|
+
"@voyant-travel/inventory-react": "^0.82.0",
|
|
167
|
+
"@voyant-travel/inventory": "^0.19.3",
|
|
168
|
+
"@voyant-travel/storefront-react": "^0.202.0",
|
|
169
|
+
"@voyant-travel/ui": "^0.109.6",
|
|
170
|
+
"@voyant-travel/operations-react": "^0.81.0"
|
|
171
171
|
},
|
|
172
172
|
"peerDependenciesMeta": {
|
|
173
173
|
"@tanstack/react-table": {
|
|
@@ -233,8 +233,8 @@
|
|
|
233
233
|
},
|
|
234
234
|
"dependencies": {
|
|
235
235
|
"sonner": "^2.0.7",
|
|
236
|
-
"@voyant-travel/catalog-contracts": "^0.112.1",
|
|
237
236
|
"@voyant-travel/i18n": "^0.117.2",
|
|
237
|
+
"@voyant-travel/catalog-contracts": "^0.112.1",
|
|
238
238
|
"@voyant-travel/react": "^0.104.2",
|
|
239
239
|
"@voyant-travel/types": "^0.109.9"
|
|
240
240
|
},
|
|
@@ -251,26 +251,26 @@
|
|
|
251
251
|
"typescript": "^6.0.3",
|
|
252
252
|
"vitest": "^4.1.9",
|
|
253
253
|
"zod": "^4.4.3",
|
|
254
|
-
"@voyant-travel/accommodations": "^0.
|
|
255
|
-
"@voyant-travel/catalog": "^0.197.0",
|
|
256
|
-
"@voyant-travel/bookings": "^0.199.0",
|
|
257
|
-
"@voyant-travel/catalog-react": "^0.197.0",
|
|
258
|
-
"@voyant-travel/distribution-react": "^0.189.0",
|
|
259
|
-
"@voyant-travel/relationships-react": "^0.199.0",
|
|
260
|
-
"@voyant-travel/finance": "^0.199.0",
|
|
261
|
-
"@voyant-travel/finance-react": "^0.199.0",
|
|
262
|
-
"@voyant-travel/identity-react": "^0.199.0",
|
|
263
|
-
"@voyant-travel/legal-react": "^0.199.0",
|
|
264
|
-
"@voyant-travel/commerce-react": "^0.81.0",
|
|
265
|
-
"@voyant-travel/inventory-react": "^0.81.0",
|
|
266
|
-
"@voyant-travel/inventory": "^0.19.2",
|
|
267
|
-
"@voyant-travel/react": "^0.104.2",
|
|
268
|
-
"@voyant-travel/storefront-react": "^0.201.0",
|
|
269
|
-
"@voyant-travel/ui": "^0.109.5",
|
|
270
|
-
"@voyant-travel/voyant-typescript-config": "^0.1.0",
|
|
271
|
-
"@voyant-travel/operations-react": "^0.80.0",
|
|
254
|
+
"@voyant-travel/accommodations": "^0.160.0",
|
|
272
255
|
"@voyant-travel/admin": "^0.129.1",
|
|
273
|
-
"@voyant-travel/
|
|
256
|
+
"@voyant-travel/catalog-react": "^0.198.0",
|
|
257
|
+
"@voyant-travel/distribution-react": "^0.190.0",
|
|
258
|
+
"@voyant-travel/bookings": "^0.200.0",
|
|
259
|
+
"@voyant-travel/relationships-react": "^0.200.0",
|
|
260
|
+
"@voyant-travel/finance": "^0.200.0",
|
|
261
|
+
"@voyant-travel/finance-react": "^0.200.0",
|
|
262
|
+
"@voyant-travel/identity-react": "^0.200.0",
|
|
263
|
+
"@voyant-travel/legal-react": "^0.200.0",
|
|
264
|
+
"@voyant-travel/commerce-react": "^0.82.0",
|
|
265
|
+
"@voyant-travel/cruises": "^0.199.0",
|
|
266
|
+
"@voyant-travel/inventory-react": "^0.82.0",
|
|
267
|
+
"@voyant-travel/inventory": "^0.19.3",
|
|
268
|
+
"@voyant-travel/react": "^0.104.2",
|
|
269
|
+
"@voyant-travel/catalog": "^0.198.0",
|
|
270
|
+
"@voyant-travel/storefront-react": "^0.202.0",
|
|
271
|
+
"@voyant-travel/ui": "^0.109.6",
|
|
272
|
+
"@voyant-travel/operations-react": "^0.81.0",
|
|
273
|
+
"@voyant-travel/voyant-typescript-config": "^0.1.0"
|
|
274
274
|
},
|
|
275
275
|
"files": [
|
|
276
276
|
"dist",
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export interface BookingContractDialogProps {
|
|
2
|
-
open: boolean;
|
|
3
|
-
onOpenChange: (open: boolean) => void;
|
|
4
|
-
bookingId: string;
|
|
5
|
-
bookingNumber?: string | null;
|
|
6
|
-
onSuccess?: () => void;
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* "Add contract" dialog for a booking. Two modes:
|
|
10
|
-
*
|
|
11
|
-
* - **Generate** (default): hits the server-side preview branch of
|
|
12
|
-
* `/v1/admin/bookings/:id/generate-contract` (which runs the same
|
|
13
|
-
* template + variable build the customer would see at checkout)
|
|
14
|
-
* and renders the HTML in a sandboxed iframe. Confirm fires the
|
|
15
|
-
* full generate, which creates the contract row + persists the PDF.
|
|
16
|
-
*
|
|
17
|
-
* - **Upload**: operator picks a pre-signed PDF (e.g. countersigned
|
|
18
|
-
* copy). The dialog creates a `signed`-status contract row and
|
|
19
|
-
* attaches the uploaded file via the legal attachment upload route.
|
|
20
|
-
*/
|
|
21
|
-
export declare function BookingContractDialog({ open, onOpenChange, bookingId, bookingNumber, onSuccess, }: BookingContractDialogProps): import("react").JSX.Element;
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useQueryClient } from "@tanstack/react-query";
|
|
4
|
-
import { useOperatorAdminMessages } from "@voyant-travel/admin";
|
|
5
|
-
import { legalQueryKeys, useLegalContractAttachmentMutation, useLegalContractMutation, } from "@voyant-travel/legal-react";
|
|
6
|
-
import { Button, Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, Input, Label, Skeleton, } from "@voyant-travel/ui/components";
|
|
7
|
-
import { FileText, Loader2, Paperclip, X } from "lucide-react";
|
|
8
|
-
import { useEffect, useState } from "react";
|
|
9
|
-
import { VoyantApiError } from "../client.js";
|
|
10
|
-
import { useBookingContractGenerationMutation } from "../index.js";
|
|
11
|
-
/**
|
|
12
|
-
* "Add contract" dialog for a booking. Two modes:
|
|
13
|
-
*
|
|
14
|
-
* - **Generate** (default): hits the server-side preview branch of
|
|
15
|
-
* `/v1/admin/bookings/:id/generate-contract` (which runs the same
|
|
16
|
-
* template + variable build the customer would see at checkout)
|
|
17
|
-
* and renders the HTML in a sandboxed iframe. Confirm fires the
|
|
18
|
-
* full generate, which creates the contract row + persists the PDF.
|
|
19
|
-
*
|
|
20
|
-
* - **Upload**: operator picks a pre-signed PDF (e.g. countersigned
|
|
21
|
-
* copy). The dialog creates a `signed`-status contract row and
|
|
22
|
-
* attaches the uploaded file via the legal attachment upload route.
|
|
23
|
-
*/
|
|
24
|
-
export function BookingContractDialog({ open, onOpenChange, bookingId, bookingNumber, onSuccess, }) {
|
|
25
|
-
const t = useOperatorAdminMessages().bookings.detail.contractDialog;
|
|
26
|
-
const queryClient = useQueryClient();
|
|
27
|
-
const { create: createContract } = useLegalContractMutation();
|
|
28
|
-
const { upload: uploadAttachment } = useLegalContractAttachmentMutation();
|
|
29
|
-
const { preview, generate } = useBookingContractGenerationMutation(bookingId);
|
|
30
|
-
const [mode, setMode] = useState("generate");
|
|
31
|
-
const [uploading, setUploading] = useState(false);
|
|
32
|
-
const [error, setError] = useState(null);
|
|
33
|
-
const [previewRequested, setPreviewRequested] = useState(false);
|
|
34
|
-
// Upload form state
|
|
35
|
-
const [title, setTitle] = useState("");
|
|
36
|
-
const [file, setFile] = useState(null);
|
|
37
|
-
// Reset on open. Generate is the leading mode so the preview fetch
|
|
38
|
-
// kicks off immediately.
|
|
39
|
-
const resetPreview = preview.reset;
|
|
40
|
-
useEffect(() => {
|
|
41
|
-
if (!open)
|
|
42
|
-
return;
|
|
43
|
-
setMode("generate");
|
|
44
|
-
setTitle("");
|
|
45
|
-
setFile(null);
|
|
46
|
-
setError(null);
|
|
47
|
-
setUploading(false);
|
|
48
|
-
setPreviewRequested(false);
|
|
49
|
-
resetPreview();
|
|
50
|
-
}, [open, resetPreview]);
|
|
51
|
-
// Fetch preview HTML every time the dialog opens (or the mode flips
|
|
52
|
-
// back to Generate). The preview reflects current booking + template
|
|
53
|
-
// state so re-fetching is intentional.
|
|
54
|
-
const fetchPreview = preview.mutate;
|
|
55
|
-
useEffect(() => {
|
|
56
|
-
if (!open || mode !== "generate")
|
|
57
|
-
return;
|
|
58
|
-
setPreviewRequested(true);
|
|
59
|
-
fetchPreview();
|
|
60
|
-
}, [open, mode, fetchPreview]);
|
|
61
|
-
const handleGenerate = async () => {
|
|
62
|
-
setError(null);
|
|
63
|
-
try {
|
|
64
|
-
await generate.mutateAsync({});
|
|
65
|
-
await queryClient.invalidateQueries({ queryKey: legalQueryKeys.contracts() });
|
|
66
|
-
onSuccess?.();
|
|
67
|
-
onOpenChange(false);
|
|
68
|
-
}
|
|
69
|
-
catch (err) {
|
|
70
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
const handleUpload = async () => {
|
|
74
|
-
if (!file) {
|
|
75
|
-
setError(t.uploadFileRequired);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
setUploading(true);
|
|
79
|
-
setError(null);
|
|
80
|
-
try {
|
|
81
|
-
const fallbackTitle = title.trim() ||
|
|
82
|
-
(bookingNumber
|
|
83
|
-
? `Contract ${bookingNumber}`
|
|
84
|
-
: `Contract for booking ${bookingId.slice(-8)}`);
|
|
85
|
-
const created = await createContract.mutateAsync({
|
|
86
|
-
scope: "customer",
|
|
87
|
-
status: "signed",
|
|
88
|
-
title: fallbackTitle,
|
|
89
|
-
bookingId,
|
|
90
|
-
metadata: { uploadedByOperator: true },
|
|
91
|
-
});
|
|
92
|
-
await uploadAttachment.mutateAsync({
|
|
93
|
-
contractId: created.id,
|
|
94
|
-
input: { file, kind: "document", name: file.name },
|
|
95
|
-
});
|
|
96
|
-
onSuccess?.();
|
|
97
|
-
onOpenChange(false);
|
|
98
|
-
}
|
|
99
|
-
catch (err) {
|
|
100
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
101
|
-
}
|
|
102
|
-
finally {
|
|
103
|
-
setUploading(false);
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
const submitting = generate.isPending || uploading;
|
|
107
|
-
const previewReady = preview.data != null;
|
|
108
|
-
const canSubmit = mode === "generate" ? previewReady && !submitting : file != null && !submitting;
|
|
109
|
-
const previewSetupMissing = preview.error instanceof VoyantApiError && preview.error.status === 404;
|
|
110
|
-
const showPreviewSetupHint = mode === "generate" &&
|
|
111
|
-
previewRequested &&
|
|
112
|
-
!preview.isPending &&
|
|
113
|
-
!preview.data &&
|
|
114
|
-
(!preview.isError || previewSetupMissing);
|
|
115
|
-
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "w-full! max-w-4xl! gap-0 p-0", children: [_jsxs(DialogHeader, { className: "shrink-0 border-b px-6 py-4", children: [_jsx(DialogTitle, { children: t.title }), _jsx(DialogDescription, { children: t.description })] }), _jsx("div", { className: "max-h-[70vh] overflow-y-auto", children: _jsxs("div", { className: "flex flex-col gap-4 px-6 py-5", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.modeLabel }), _jsx(SegmentedChoice, { value: mode, onChange: setMode, options: [
|
|
116
|
-
{ value: "generate", label: t.modeGenerate },
|
|
117
|
-
{ value: "upload", label: t.modeUpload },
|
|
118
|
-
] })] }), mode === "generate" ? (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.previewLabel }), _jsx("div", { className: "overflow-hidden rounded-md border bg-muted/30", children: preview.isPending ? (_jsxs("div", { className: "flex flex-col gap-3 p-6", children: [_jsx(Skeleton, { className: "h-6 w-1/2" }), _jsx(Skeleton, { className: "h-4 w-full" }), _jsx(Skeleton, { className: "h-4 w-5/6" }), _jsx(Skeleton, { className: "h-4 w-4/5" }), _jsx(Skeleton, { className: "h-4 w-full" })] })) : preview.isError && !previewSetupMissing ? (_jsxs("p", { className: "p-6 text-destructive text-sm", children: [t.previewErrorPrefix, " ", preview.error instanceof Error ? preview.error.message : t.previewFailed] })) : preview.data ? (_jsx("iframe", { title: preview.data.templateName || t.previewIframeFallback, srcDoc: wrapPreviewHtml(preview.data.html), sandbox: "", className: "h-[60vh] w-full border-0 bg-white" })) : (_jsx("div", { className: "p-6 text-muted-foreground text-sm", children: t.previewUnavailable })) }), preview.data?.templateName ? (_jsxs("p", { className: "text-muted-foreground text-xs", children: [t.previewTemplateLabel, " ", preview.data.templateName] })) : null, showPreviewSetupHint ? (_jsx("p", { className: "text-muted-foreground text-xs", children: t.previewSetupHint })) : null] })) : (_jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.uploadTitleLabel }), _jsx(Input, { value: title, onChange: (e) => setTitle(e.target.value), placeholder: bookingNumber ? `Contract ${bookingNumber}` : t.uploadTitlePlaceholder }), _jsx("p", { className: "text-muted-foreground text-xs", children: t.uploadTitleHint })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.uploadFileLabel }), _jsx("input", { type: "file", accept: "application/pdf", onChange: (e) => {
|
|
119
|
-
const next = e.target.files?.[0] ?? null;
|
|
120
|
-
setFile(next);
|
|
121
|
-
}, className: "block w-full text-sm file:mr-3 file:rounded-md file:border file:bg-muted file:px-3 file:py-1.5 file:text-sm file:font-medium hover:file:bg-muted/70" }), file ? (_jsxs("div", { className: "flex items-center justify-between gap-2 rounded-md border bg-background px-3 py-1.5 text-sm", children: [_jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [_jsx(Paperclip, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }), _jsx("span", { className: "truncate", children: file.name })] }), _jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", onClick: () => setFile(null), children: _jsx(X, { className: "h-3.5 w-3.5" }) })] })) : null] })] })), error ? _jsx("p", { className: "text-sm text-destructive", children: error }) : null] }) }), _jsxs("div", { className: "flex shrink-0 items-center justify-end gap-2 border-t px-6 py-4", children: [_jsx(Button, { type: "button", variant: "outline", onClick: () => onOpenChange(false), disabled: submitting, children: t.cancel }), _jsxs(Button, { type: "button", disabled: !canSubmit, onClick: () => {
|
|
122
|
-
if (mode === "generate") {
|
|
123
|
-
void handleGenerate();
|
|
124
|
-
}
|
|
125
|
-
else {
|
|
126
|
-
void handleUpload();
|
|
127
|
-
}
|
|
128
|
-
}, children: [submitting ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : null, mode === "generate" ? (_jsxs(_Fragment, { children: [_jsx(FileText, { className: "mr-1.5 h-3.5 w-3.5" }), t.generateAction] })) : (t.uploadAction)] })] })] }) }));
|
|
129
|
-
}
|
|
130
|
-
function SegmentedChoice({ value, onChange, options, }) {
|
|
131
|
-
return (_jsx("div", { className: "flex w-full rounded-md border bg-background p-0.5", children: options.map((opt) => {
|
|
132
|
-
const active = opt.value === value;
|
|
133
|
-
return (_jsx("button", { type: "button", onClick: () => onChange(opt.value), className: "flex-1 rounded-sm px-3 py-1.5 text-sm font-medium transition-colors " +
|
|
134
|
-
(active ? "bg-muted text-foreground" : "text-muted-foreground hover:text-foreground"), children: opt.label }, opt.value));
|
|
135
|
-
}) }));
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Wrap the rendered template body in a light-theme HTML document so
|
|
139
|
-
* the iframe doesn't inherit the dark dashboard background. Mirrors
|
|
140
|
-
* the storefront's contract-preview wrapper to keep the WYSIWYG
|
|
141
|
-
* promise honest.
|
|
142
|
-
*/
|
|
143
|
-
function wrapPreviewHtml(body) {
|
|
144
|
-
return `<!DOCTYPE html>
|
|
145
|
-
<html lang="en">
|
|
146
|
-
<head>
|
|
147
|
-
<meta charset="utf-8" />
|
|
148
|
-
<style>
|
|
149
|
-
:root { color-scheme: light; }
|
|
150
|
-
html, body { margin: 0; background: #ffffff; color: #111827; }
|
|
151
|
-
body {
|
|
152
|
-
padding: 1.5rem 2rem;
|
|
153
|
-
font-family: ui-serif, Georgia, "Times New Roman", serif;
|
|
154
|
-
font-size: 15px;
|
|
155
|
-
line-height: 1.6;
|
|
156
|
-
}
|
|
157
|
-
h1, h2, h3 { font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; color: #0f172a; }
|
|
158
|
-
h1 { font-size: 1.5rem; margin: 0 0 1rem; }
|
|
159
|
-
h2 { font-size: 1.15rem; margin: 1.5rem 0 0.5rem; }
|
|
160
|
-
p { margin: 0.5rem 0; }
|
|
161
|
-
ul, ol { padding-left: 1.5rem; }
|
|
162
|
-
strong { color: #0f172a; }
|
|
163
|
-
a { color: #2563eb; }
|
|
164
|
-
table { border-collapse: collapse; width: 100%; margin: 0.75rem 0; }
|
|
165
|
-
th, td { border: 1px solid #e5e7eb; padding: 0.5rem 0.75rem; text-align: left; }
|
|
166
|
-
th { background: #f9fafb; }
|
|
167
|
-
</style>
|
|
168
|
-
</head>
|
|
169
|
-
<body>${body}</body>
|
|
170
|
-
</html>`;
|
|
171
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
export interface GenerateBookingContractInput {
|
|
2
|
-
/** Regenerate even when the contract already has a persisted PDF. */
|
|
3
|
-
force?: boolean;
|
|
4
|
-
}
|
|
5
|
-
/**
|
|
6
|
-
* Contract generation for a booking
|
|
7
|
-
* (`POST /v1/admin/bookings/:id/generate-contract`):
|
|
8
|
-
*
|
|
9
|
-
* - `preview` runs the server-side preview branch (`{ preview: true }`),
|
|
10
|
-
* which renders the same template + variable build the customer would
|
|
11
|
-
* see at checkout and returns the HTML without persisting anything.
|
|
12
|
-
* - `generate` creates the legal contract row + persists the PDF
|
|
13
|
-
* attachment; pass `{ force: true }` to regenerate an existing PDF.
|
|
14
|
-
*
|
|
15
|
-
* The created contract lives in the legal module — callers that display
|
|
16
|
-
* contract lists should invalidate `legalQueryKeys.contracts()` (from
|
|
17
|
-
* `@voyant-travel/legal-react`) on `generate` success; this package cannot do
|
|
18
|
-
* that without taking a dependency on the legal client.
|
|
19
|
-
*/
|
|
20
|
-
export declare function useBookingContractGenerationMutation(bookingId: string): {
|
|
21
|
-
preview: import("@tanstack/react-query").UseMutationResult<{
|
|
22
|
-
html: string;
|
|
23
|
-
templateName?: string | undefined;
|
|
24
|
-
templateLanguage?: string | undefined;
|
|
25
|
-
}, Error, void, unknown>;
|
|
26
|
-
generate: import("@tanstack/react-query").UseMutationResult<{
|
|
27
|
-
contractId: string;
|
|
28
|
-
attachmentId: string;
|
|
29
|
-
}, Error, GenerateBookingContractInput, unknown>;
|
|
30
|
-
};
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { useMutation } from "@tanstack/react-query";
|
|
3
|
-
import { fetchWithValidation } from "../client.js";
|
|
4
|
-
import { useVoyantBookingsContext } from "../provider.js";
|
|
5
|
-
import { bookingContractPreviewResponse, bookingGenerateContractResponse } from "../schemas.js";
|
|
6
|
-
/**
|
|
7
|
-
* Contract generation for a booking
|
|
8
|
-
* (`POST /v1/admin/bookings/:id/generate-contract`):
|
|
9
|
-
*
|
|
10
|
-
* - `preview` runs the server-side preview branch (`{ preview: true }`),
|
|
11
|
-
* which renders the same template + variable build the customer would
|
|
12
|
-
* see at checkout and returns the HTML without persisting anything.
|
|
13
|
-
* - `generate` creates the legal contract row + persists the PDF
|
|
14
|
-
* attachment; pass `{ force: true }` to regenerate an existing PDF.
|
|
15
|
-
*
|
|
16
|
-
* The created contract lives in the legal module — callers that display
|
|
17
|
-
* contract lists should invalidate `legalQueryKeys.contracts()` (from
|
|
18
|
-
* `@voyant-travel/legal-react`) on `generate` success; this package cannot do
|
|
19
|
-
* that without taking a dependency on the legal client.
|
|
20
|
-
*/
|
|
21
|
-
export function useBookingContractGenerationMutation(bookingId) {
|
|
22
|
-
const { baseUrl, fetcher } = useVoyantBookingsContext();
|
|
23
|
-
const preview = useMutation({
|
|
24
|
-
mutationFn: async () => {
|
|
25
|
-
const { data } = await fetchWithValidation(`/v1/admin/bookings/${bookingId}/generate-contract`, bookingContractPreviewResponse, { baseUrl, fetcher }, { method: "POST", body: JSON.stringify({ preview: true }) });
|
|
26
|
-
return data;
|
|
27
|
-
},
|
|
28
|
-
});
|
|
29
|
-
const generate = useMutation({
|
|
30
|
-
mutationFn: async (input = {}) => {
|
|
31
|
-
const { data } = await fetchWithValidation(`/v1/admin/bookings/${bookingId}/generate-contract`, bookingGenerateContractResponse, { baseUrl, fetcher }, { method: "POST", body: JSON.stringify(input.force ? { force: true } : {}) });
|
|
32
|
-
return data;
|
|
33
|
-
},
|
|
34
|
-
});
|
|
35
|
-
return { preview, generate };
|
|
36
|
-
}
|