-
Sign contract
+
+ {usingSaved ? "Approve signature" : "Sign contract"}
+
- {data.reference} — your signature will be stored securely.
+ {usingSaved
+ ? `${data.reference} — review your saved signature and approve it.`
+ : `${data.reference} — your signature will be stored securely.`}
diff --git a/apps/edr-freight-web/portal/src/services/bookings.service.ts b/apps/edr-freight-web/portal/src/services/bookings.service.ts
index ac6411505..9933fa227 100644
--- a/apps/edr-freight-web/portal/src/services/bookings.service.ts
+++ b/apps/edr-freight-web/portal/src/services/bookings.service.ts
@@ -23,6 +23,11 @@ export interface ContractView {
signedAt: string;
signatureImageUrl?: string | null;
}>;
+ /** Current viewer's reusable saved signature, if they have one. */
+ savedSignature?: {
+ signerDisplayName: string;
+ signatureImageUrl?: string | null;
+ } | null;
}
export interface PriceLineItem {
diff --git a/apps/edr-freight-web/portal/src/services/payments.service.ts b/apps/edr-freight-web/portal/src/services/payments.service.ts
index 3c345f648..119aea4ed 100644
--- a/apps/edr-freight-web/portal/src/services/payments.service.ts
+++ b/apps/edr-freight-web/portal/src/services/payments.service.ts
@@ -1,4 +1,5 @@
import { URL_CONSTANTS } from "@/constants/URLS";
+import { API_BASE_URL } from "@/constants/apiConfig";
import { client } from "../utils/api";
const P = URL_CONSTANTS.PAYMENTS;
@@ -57,7 +58,7 @@ function buildCheckoutUrl(payload: {
method: PaymentMethod;
platform?: PaymentPlatform;
}): string {
- const base = (import.meta.env.VITE_API_URL ?? "").replace(/\/$/, "");
+ const base = API_BASE_URL.replace(/\/$/, "");
const params = new URLSearchParams({
bookingId: payload.bookingId,
method: payload.method,
diff --git a/apps/edr-freight-web/portal/src/services/signatures.service.ts b/apps/edr-freight-web/portal/src/services/signatures.service.ts
new file mode 100644
index 000000000..05ae61ea1
--- /dev/null
+++ b/apps/edr-freight-web/portal/src/services/signatures.service.ts
@@ -0,0 +1,28 @@
+import { client } from "../utils/api";
+
+const SIGNATURE_URL = "/api/me/signature";
+
+export interface SavedSignature {
+ signerDisplayName: string;
+ signatureImageUrl?: string | null;
+}
+
+export interface SaveSignaturePayload {
+ signerDisplayName: string;
+ signatureImageBase64: string;
+}
+
+export const signaturesService = {
+ /** The current user's reusable saved signature, or null if none. */
+ getMySignature: async (): Promise
=> {
+ const { data } = await client.get(SIGNATURE_URL);
+ return (data.data ?? data) ?? null;
+ },
+
+ saveMySignature: async (
+ payload: SaveSignaturePayload,
+ ): Promise => {
+ const { data } = await client.put(SIGNATURE_URL, payload);
+ return (data.data ?? data) ?? null;
+ },
+};
diff --git a/apps/edr-freight-web/portal/src/utils/api.ts b/apps/edr-freight-web/portal/src/utils/api.ts
index 446eec591..289709906 100644
--- a/apps/edr-freight-web/portal/src/utils/api.ts
+++ b/apps/edr-freight-web/portal/src/utils/api.ts
@@ -1,9 +1,10 @@
import { UseQueryOptions, QueryObserverOptions } from "@tanstack/react-query";
import axios, { AxiosError, InternalAxiosRequestConfig } from "axios";
import { URL_CONSTANTS } from "@/constants/URLS";
+import { API_BASE_URL } from "@/constants/apiConfig";
const client = axios.create({
- baseURL: import.meta.env.VITE_API_URL,
+ baseURL: API_BASE_URL,
});
function getCookie(name: string): string | undefined {
diff --git a/apps/edr-freight-web/portal/vite.config.ts b/apps/edr-freight-web/portal/vite.config.ts
index 8d01990aa..99e2a41d5 100644
--- a/apps/edr-freight-web/portal/vite.config.ts
+++ b/apps/edr-freight-web/portal/vite.config.ts
@@ -1,21 +1,38 @@
import path from "node:path";
import { fileURLToPath } from "node:url";
-import { defineConfig } from "vite";
+import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
+// Pin Mantine to this app's copy. pnpm can install a second @mantine/core under
+// @edr/ui-common (linked to react@18) while the portal uses react@19 — dedupe
+// alone does not merge those into one module in production builds.
+const mantineCore = path.resolve(__dirname, "node_modules/@mantine/core");
+const mantineHooks = path.resolve(__dirname, "node_modules/@mantine/hooks");
+
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
+ // Resolve from TS source so Vite gets ESM named exports (dist is CommonJS).
+ "@edr/types": path.resolve(__dirname, "../../../packages/types/src/index.ts"),
+ "@mantine/core": mantineCore,
+ "@mantine/hooks": mantineHooks,
},
+ dedupe: ["react", "react-dom", "@mantine/core", "@mantine/hooks"],
+ },
+ optimizeDeps: {
+ include: ["@mantine/core", "@mantine/hooks", "@edr/ui-common"],
},
server: {
port: 5173,
host: "0.0.0.0",
},
+ test: {
+ environment: "node",
+ },
});
diff --git a/packages/types/src/common/payments.ts b/packages/types/src/common/payments.ts
index 6439395f4..ebc6e34dd 100644
--- a/packages/types/src/common/payments.ts
+++ b/packages/types/src/common/payments.ts
@@ -137,7 +137,7 @@ export interface ConfirmPaymentRequest {
}
/** Response of `POST /payments/initiate` and shape of intent lookups. */
-export interface PaymentIntentSnapshot {
+export type PaymentIntentSnapshot ={
intentId: string;
service: PaymentService;
referenceType: PaymentReferenceType;
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index 19e4ecde6..b4fd73d81 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -2,12 +2,5 @@ export * from "./common/index";
export * from "./freight/index";
export * as Freight from "./freight/index";
export * as Passenger from "./passenger/index";
-export type {
- PaymentEvent,
- PaymentEventType,
- PaymentFailedEvent,
- PaymentSucceededEvent,
- PaymentIntentSnapshot,
- InitiatePaymentRequest,
-} from "./common/payments";
+export type { PaymentEvent, PaymentEventType, PaymentFailedEvent, PaymentSucceededEvent, PaymentIntentSnapshot, InitiatePaymentRequest } from "./common/payments";
export { PaymentReferenceType, PaymentService } from "./common/payments";
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 750c9ec01..c1b29edac 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -436,7 +436,7 @@ importers:
version: 10.2.0(@nestjs/common@11.1.24(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))
'@nestjs/microservices':
specifier: ^11.1.24
- version: 11.1.24(@nestjs/common@11.1.24(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.24)(amqp-connection-manager@5.0.0(amqplib@0.10.9))(amqplib@0.10.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ version: 11.1.24(@nestjs/common@11.1.24(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.24)(amqp-connection-manager@5.0.0(amqplib@2.0.1))(amqplib@2.0.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/passport':
specifier: ^10.0.3
version: 10.0.3(@nestjs/common@11.1.24(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0)